From 668256f986928731016ba0c680e7c21fd94b47a7 Mon Sep 17 00:00:00 2001 From: Lemon Rose Date: Fri, 13 Oct 2023 23:48:26 +0530 Subject: [PATCH] [ThreadOpener] tagscript integration --- threadopener/_tagscript.py | 59 ++++++++++++++++++++++++++++++++++++++ threadopener/abc.py | 8 ++++++ threadopener/commands.py | 47 ++++++++++++++++++++++++++++++ threadopener/core.py | 55 +++++++++++++++++++++++++++++++---- 4 files changed, 164 insertions(+), 5 deletions(-) create mode 100644 threadopener/_tagscript.py diff --git a/threadopener/_tagscript.py b/threadopener/_tagscript.py new file mode 100644 index 00000000..c8dfcfa0 --- /dev/null +++ b/threadopener/_tagscript.py @@ -0,0 +1,59 @@ +from typing import List, Final, Dict, Any, final + +import discord +import TagScriptEngine as tse +from redbot.core import commands +from redbot.core.utils.chat_formatting import humanize_number + +thread_message: str = """ +{embed(description):Welcome to the thread.} +{embed(thumbnail):{member(avatar)}} +{embed(color):{color}} +""" + +TAGSCRIPT_LIMIT: Final[int] = 10_000 + +blocks: List[tse.Block] = [ + tse.LooseVariableGetterBlock(), + tse.AssignmentBlock(), + tse.CommandBlock(), + tse.EmbedBlock(), + tse.IfBlock(), +] + +tagscript_engine: tse.Interpreter = tse.Interpreter(blocks) + + +def process_tagscript(content: str, seed_variables: Dict[str, tse.Adapter] = {}) -> Dict[str, Any]: + output: tse.Response = tagscript_engine.process(content, seed_variables) + kwargs: Dict[str, Any] = {} + if output.body: + kwargs["content"] = discord.utils.escape_mentions(output.body[:2000]) + if embed := output.actions.get("embed"): + kwargs["embed"] = embed + return kwargs + + +class TagError(Exception): + """ + Base exception class. + """ + + +@final +class TagCharacterLimitReached(TagError): + """Raised when the TagScript character limit is reached.""" + + def __init__(self, limit: int, length: int): + super().__init__( + f"TagScript cannot be longer than {humanize_number(limit)} (**{humanize_number(length)}**)." + ) + + +class TagscriptConverter(commands.Converter[str]): + async def convert(self, ctx: commands.Context, argument: str) -> str: + try: + await ctx.cog.validate_tagscript(argument) # type: ignore + except TagError as e: + raise commands.BadArgument(str(e)) + return argument diff --git a/threadopener/abc.py b/threadopener/abc.py index 782b5979..ddce358e 100644 --- a/threadopener/abc.py +++ b/threadopener/abc.py @@ -43,6 +43,14 @@ def __init__(self, *_args: Any) -> None: async def format_help_for_context(self, ctx: commands.Context) -> str: raise NotImplementedError() + @abstractmethod + async def red_delete_data_for_user(self, **kwargs: Any) -> None: + raise NotImplementedError() + + @abstractmethod + async def validate_tagscript(self, tagscript: str) -> bool: + raise NotImplementedError() + class CompositeMetaClass(type(commands.Cog), type(ABC)): pass diff --git a/threadopener/commands.py b/threadopener/commands.py index bc025c0d..ad104df0 100644 --- a/threadopener/commands.py +++ b/threadopener/commands.py @@ -29,6 +29,7 @@ from redbot.core.utils.chat_formatting import humanize_list from .abc import MixinMeta +from ._tagscript import TagscriptConverter class Commands(MixinMeta): @@ -111,6 +112,52 @@ async def _slowmode(self, ctx: commands.Context, amount: commands.Range[int, 0, await self.config.guild(ctx.guild).slowmode_delay.set(amount) # type: ignore await ctx.send(f"Slowmode is now {amount}.") + @_thread_opener.group(name="message") # type: ignore + async def _message(self, _: commands.Context): + """ + Manage thread opener notifications when they are opened. + """ + + @_message.command(name="toggle") + async def _message_toggle(self, ctx: commands.Context, toggle: bool): + """Toggle the thread opener notification message.""" + await self.config.guild(ctx.guild).message_toggle.set(toggle) # type: ignore + await ctx.send( + f"ThreadOpener notifications are now {'enabled' if toggle else 'disabled'}." + ) + + @_message.command(name="set") + async def _message_set(self, ctx: commands.Context, *, message: TagscriptConverter): + """ + Change the thread opener notification message. + + (Supports Tagscript) + + **Blocks:** + - [Assugnment Block](https://phen-cogs.readthedocs.io/en/latest/tags/tse_blocks.html#assignment-block) + - [If Block](https://phen-cogs.readthedocs.io/en/latest/tags/tse_blocks.html#if-block) + - [Embed Block](https://phen-cogs.readthedocs.io/en/latest/tags/parsing_blocks.html#embed-block) + - [Command Block](https://phen-cogs.readthedocs.io/en/latest/tags/parsing_blocks.html#command-block) + + **Variable:** + - `{server}`: [Your guild/server.](https://phen-cogs.readthedocs.io/en/latest/tags/default_variables.html#server-block) + - `{author}`: [Author of the message.](https://phen-cogs.readthedocs.io/en/latest/tags/default_variables.html#author-block) + - `{color}`: [botname]'s default color. + + **Example:** + ``` + {embed(description):Welcome to the thread.} + {embed(thumbnail):{member(avatar)}} + {embed(color):{color}} + ``` + """ + if message: + await self.config.member(ctx.author).custom_message.set(message) # type: ignore + await ctx.send("Successfully changed the thread opener notification message.") + else: + await self.config.member(ctx.author).custom_message.clear() # type: ignore + await ctx.send("Successfully reset the thread opener notification message.") + @_thread_opener.command(name="showsettings", aliases=["ss", "show"]) # type: ignore async def _show_settings(self, ctx: commands.Context): """Show ThreadOpener settings.""" diff --git a/threadopener/core.py b/threadopener/core.py index a1dd40a5..ed6841a7 100644 --- a/threadopener/core.py +++ b/threadopener/core.py @@ -23,9 +23,10 @@ """ import logging -from typing import Dict, Final, List, Optional, Tuple, Union +from typing import Dict, Final, List, Optional, Tuple, Union, Any import discord +import TagScriptEngine as tse from redbot.core import Config, commands from redbot.core.bot import Red from redbot.core.utils.chat_formatting import humanize_list @@ -33,6 +34,12 @@ from .abc import CompositeMetaClass from .commands import Commands from .cooldown import ThreadCooldown +from ._tagscript import ( + process_tagscript, + TagCharacterLimitReached, + thread_message, + TAGSCRIPT_LIMIT, +) log: logging.Logger = logging.getLogger("red.seina.threadopener") @@ -57,10 +64,12 @@ def __init__(self, bot: Red) -> None: identifier=69_420_666, force_registration=True, ) - default_guilds: Dict[str, Optional[Union[List[int], bool, int]]] = { + default_guilds: Dict[str, Optional[Union[List[int], Any]]] = { "toggle": False, "channels": [], "slowmode_delay": None, + "message_toggle": False, + "message": thread_message, "auto_archive_duration": 10080, } self.config.register_guild(**default_guilds) @@ -79,6 +88,16 @@ def format_help_for_context(self, ctx: commands.Context) -> str: ] return "\n".join(text) + async def red_delete_data_for_user(self, **kwargs: Any) -> None: + """Nothing to delete.""" + return + + async def validate_tagscript(self, tagscript: str) -> bool: + length = len(tagscript) + if length > TAGSCRIPT_LIMIT: + raise TagCharacterLimitReached(TAGSCRIPT_LIMIT, length) + return True + @commands.Cog.listener() async def on_message(self, message: discord.Message) -> None: if message.guild is None: @@ -117,11 +136,12 @@ async def on_message(self, message: discord.Message) -> None: log.debug(f"{message.channel} ratelimit exhausted, retry after: {retry_after}.") return - slowmode_delay = await self.config.guild(message.guild).slowmode_delay() - auto_archive_duration = await self.config.guild(message.guild).auto_archive_duration() + slowmode_delay: int = await self.config.guild(message.guild).slowmode_delay() + auto_archive_duration: Any = await self.config.guild(message.guild).auto_archive_duration() + message_toggle: bool = await self.config.guild(message.guild).message_toggle() try: - await message.create_thread( + thread = await message.create_thread( name=f"{message.author.global_name}", auto_archive_duration=auto_archive_duration, slowmode_delay=slowmode_delay, @@ -145,3 +165,28 @@ async def on_message(self, message: discord.Message) -> None: f"Guild {message.guild.id} does not have a guild info attached to it.", exc_info=True, ) + else: + if message_toggle: + threadmessage: str = await self.config.guild(message.guild).message() + color = await self.bot.get_embed_color(message.channel) + kwargs = process_tagscript( + threadmessage, + { + "server": tse.GuildAdapter(message.guild), + "author": tse.MemberAdapter(message.author), # type: ignore + "member": tse.MemberAdapter(message.author), # type: ignore + "color": tse.StringAdapter(str(color)), + }, + ) + if not kwargs: + await self.config.guild(message.guild).message.clear() + kwargs = process_tagscript( + thread_message, + { + "server": tse.GuildAdapter(message.guild), + "author": tse.MemberAdapter(message.author), # type: ignore + "member": tse.MemberAdapter(message.author), # type: ignore + "color": tse.StringAdapter(str(color)), + }, + ) + await thread.send(**kwargs)