From 0a4c7417379727d98a12fa8253343009c104244c Mon Sep 17 00:00:00 2001 From: AAA3A <89632044+AAA3A-AAA3A@users.noreply.github.com> Date: Sun, 19 May 2024 22:40:58 +0200 Subject: [PATCH] [Tags] Add Dashboard Integration! --- tags/core.py | 46 ++++---- tags/dashboard_integration.py | 196 ++++++++++++++++++++++++++++++++++ 2 files changed, 222 insertions(+), 20 deletions(-) create mode 100644 tags/dashboard_integration.py diff --git a/tags/core.py b/tags/core.py index 5bef1eb0..690242dc 100644 --- a/tags/core.py +++ b/tags/core.py @@ -29,7 +29,7 @@ import re from collections import defaultdict from operator import itemgetter -from typing import Any, Coroutine, Dict, Final, List, Optional, Tuple, Union +from typing import Any, Coroutine, Dict, Final, List, Optional, Union import aiohttp import discord @@ -42,9 +42,11 @@ from TagScriptEngine import __version__ as tse_version from .abc import CompositeMetaClass +from .dashboard_integration import DashboardIntegration from .errors import MissingTagPermissions, TagCharacterLimitReached from .mixins import Commands, OwnerCommands, Processor from .objects import Tag +from .utils import RequesterType log: logging.Logger = logging.getLogger("red.seina.tags") @@ -56,6 +58,7 @@ class Tags( OwnerCommands, Processor, commands.Cog, + DashboardIntegration, metaclass=CompositeMetaClass, ): """ @@ -64,20 +67,8 @@ class Tags( The TagScript documentation can be found [here](https://seina-cogs.readthedocs.io/en/latest/). """ - __version__: Final[str] = "2.5.0" - __author__: Final[Tuple[str, ...]] = ("inthedark.org", "PhenoM4n4n", "sravan", "npc203") - - def format_help_for_context(self, ctx: commands.Context) -> str: - pre_processed = super().format_help_for_context(ctx) - n = "\n" if "\n\n" not in pre_processed else "" - authors = [f"**{name}**" for name in self.__author__] - text = [ - f"{pre_processed}{n}", - f"Cog Version: **{self.__version__}**", - f"AdvancedTagScriptEngine Version: **{tse_version}**", - f"Author: {humanize_list(authors)}", - ] - return "\n".join(text) + __version__: Final[str] = "2.6.5" + __author__: Final[List[str]] = ["inthedark.org", "PhenoM4n4n", "sravan", "npc203"] def __init__(self, bot: Red) -> None: self.bot: Red = bot @@ -86,12 +77,16 @@ def __init__(self, bot: Red) -> None: identifier=567234895692346562369, force_registration=True, ) - default_guild: Dict[str, Dict[str, Any]] = {"tags": {}} - default_global: Dict[str, Union[Dict[str, Dict[str, Any]], Dict[str, str], bool]] = { + default_guild: Dict[str, Union[Dict[str, Any], int]] = { + "tags": {}, + "max_tags_limit": 250, + } + default_global: Dict[str, Union[Dict[str, Dict[str, Any]], Dict[str, str], bool, int]] = { "tags": {}, "blocks": {}, "async_enabled": False, "dot_parameter": False, + "max_tags_limit": 250, } self.config.register_guild(**default_guild) self.config.register_global(**default_global) @@ -104,7 +99,7 @@ def __init__(self, bot: Red) -> None: self.initialize_task: asyncio.Task = self.create_task(self.initialize()) self.session: aiohttp.ClientSession = aiohttp.ClientSession() - self.docs: List = [] + self.docs: Union[List[str], Dict[str, str]] = [] if bot._cli_flags.logging_level == logging.DEBUG: logging.getLogger("TagScriptEngine").setLevel(logging.DEBUG) @@ -114,7 +109,19 @@ def __init__(self, bot: Red) -> None: super().__init__() + def format_help_for_context(self, ctx: commands.Context) -> str: + pre_processed = super().format_help_for_context(ctx) + n = "\n" if "\n\n" not in str(pre_processed) else "" + text = [ + f"{pre_processed}{n}", + f"Cog Version: **{self.__version__}**", + f"AdvancedTagScriptEngine Version: **{tse_version}**", + f"Author: {humanize_list(self.__author__)}", + ] + return "\n".join(text) + async def cog_unload(self) -> None: + await super().cog_unload() try: await self.__unload() except Exception as e: @@ -125,9 +132,8 @@ async def __unload(self) -> None: if self.initialize_task: self.initialize_task.cancel() await self.session.close() - await super().cog_unload() - async def red_delete_data_for_user(self, *, requester: str, user_id: int) -> None: + async def red_delete_data_for_user(self, *, requester: RequesterType, user_id: int) -> None: if requester not in ("discord_deleted_user", "user"): return guilds_data = await self.config.all_guilds() diff --git a/tags/dashboard_integration.py b/tags/dashboard_integration.py new file mode 100644 index 00000000..8a6f8f66 --- /dev/null +++ b/tags/dashboard_integration.py @@ -0,0 +1,196 @@ +from redbot.core import commands +from redbot.core.bot import Red +import discord +import typing + +import datetime + +from .converters import TagConverter +from .errors import TagFeedbackError +from .objects import Tag + +def dashboard_page(*args, **kwargs): + def decorator(func: typing.Callable): + func.__dashboard_decorator_params__ = (args, kwargs) + return func + + return decorator + + +class DashboardIntegration: + bot: Red + + @commands.Cog.listener() + async def on_dashboard_cog_add(self, dashboard_cog: commands.Cog) -> None: + dashboard_cog.rpc.third_parties_handler.add_third_party(self) + + + async def get_dashboard_page(self, user: discord.User, guild: typing.Optional[discord.Guild], **kwargs) -> typing.Dict[str, typing.Any]: + import wtforms + from markupsafe import Markup + class MarkdownTextAreaField(wtforms.TextAreaField): + def __call__( + self, + disable_toolbar: bool = False, + **kwargs, + ) -> Markup: + if "class" not in kwargs: + kwargs["class"] = "markdown-text-area-field" + else: + kwargs["class"] += " markdown-text-area-field" + if disable_toolbar: + kwargs["class"] += " markdown-text-area-field-toolbar-disabled" + return super().__call__(**kwargs) + class TagForm(kwargs["Form"]): + tag_name: wtforms.StringField = wtforms.StringField("Name", validators=[wtforms.validators.InputRequired(), wtforms.validators.Regexp(r"^[^\s]+$"), wtforms.validators.Length(max=300)]) + tagscript: MarkdownTextAreaField = MarkdownTextAreaField("Script", validators=[wtforms.validators.InputRequired(), wtforms.validators.Length(max=1700), kwargs["DpyObjectConverter"](TagConverter)]) + class TagsForm(kwargs["Form"]): + def __init__(self, tags: typing.Dict[str, str]) -> None: + super().__init__(prefix="tags_form_") + for name, tagscript in tags.items(): + self.tags.append_entry({"tag_name": name, "tagscript": tagscript}) + self.tags.default = [entry for entry in self.tags.entries if entry.csrf_token.data is None] + self.tags.entries = [entry for entry in self.tags.entries if entry.csrf_token.data is not None] + tags: wtforms.FieldList = wtforms.FieldList(wtforms.FormField(TagForm)) + submit: wtforms.SubmitField = wtforms.SubmitField("Save Modifications") + + if guild is not None: + existing_tags = self.guild_tag_cache[guild.id].copy() + else: + existing_tags = self.global_tag_cache.copy() + tags_form: TagsForm = TagsForm({tag.name: tag.tagscript for tag in sorted(existing_tags.values(), key=lambda tag: tag.name)}) + if tags_form.validate_on_submit() and await tags_form.validate_dpy_converters(): + tags = {tag.tag_name.data: tag.tagscript.data for tag in tags_form.tags} + for tag_name, tagscript in tags.items(): + if tag_name not in existing_tags: + __import__ + try: + self.validate_tag_count(guild) + except TagFeedbackError as e: + return {"status": 1, "notifications": [{"message": str(e), "category": "warning"}]} + tag = Tag( + self, + tag_name, + tagscript, + guild_id=guild.id if guild is not None else None, + author_id=user.id, + created_at=datetime.datetime.now(tz=datetime.timezone.utc), + ) + await tag.initialize() + elif tagscript != existing_tags[tag_name].tagscript: + await existing_tags[tag_name].edit_tagscript(tagscript) + for tag_name in existing_tags: + if tag_name not in tags: + await existing_tags[tag_name].delete() + return { + "status": 0, + "notifications": [{"message": "Successfully saved the modifications.", "category": "success"}], + "redirect_url": kwargs["request_url"], + } + + html_form = [ + '
', + f" {tags_form.hidden_tag()}", + ] + for i, tag_form in enumerate(tags_form.tags.default): + html_form.append('
') + if i > 0: + html_form.append('
') + tag_form.tag_name.render_kw, tag_form.tagscript.render_kw = {"class": "form-control form-control-default"}, {"class": "form-control form-control-default"} + html_form.extend( + [ + f" {tag_form.hidden_tag()}", + '
', + '
', + f" {tag_form.tag_name(placeholder='Name')}", + "
", + "
", + '
', + '
', + f" {tag_form.tagscript(rows=4, disable_toolbar=True, placeholder='Script')}", + "
", + "
", + '
', + ' Delete Tag', + "
", + "
", + ] + ) + tags_form.submit.render_kw = {"class": "btn mb-0 bg-gradient-success btn-md w-100 my-4 mb-2"} + html_form.extend( + [ + ' Create Tag' + '
' + f" {tags_form.submit()}", + "
", + "
", + ] + ) + tags_form_str = Markup("\n".join(html_form)) + + return { + "status": 0, + "web_content": { + "source": WEB_CONTENT, + "tags_form": tags_form_str, + "tags_form_length": len(tags_form.tags.default), + }, + } + + @dashboard_page(name=None, description="Manage global Tags.", is_owner=True, methods=("GET", "POST")) + async def dashboard_global_page(self, user: discord.User, **kwargs) -> typing.Dict[str, typing.Any]: + if user.id not in self.bot.owner_ids: + return {"status": 1} + return await self.get_dashboard_page(user=user, guild=None, **kwargs) + + @dashboard_page(name="guild", description="Manage guild Tags.", methods=("GET", "POST")) + async def dashboard_guild_page( + self, user: discord.User, guild: discord.Guild, **kwargs + ) -> typing.Dict[str, typing.Any]: + member = guild.get_member(user.id) + if ( + user.id not in self.bot.owner_ids + and ( + member is None + or not (await self.bot.is_mod(member) or member.guild_permissions.manage_guild) + ) + ): + return {"status": 1} + return await self.get_dashboard_page(user=user, guild=guild, **kwargs) + +WEB_CONTENT = """ + {{ tags_form|safe }} + + +"""