From 312f137f948af66f6089d8d19a349b30021e6dea Mon Sep 17 00:00:00 2001 From: lorenzo132 Date: Sat, 13 Dec 2025 16:00:54 +0100 Subject: [PATCH 1/3] feat: args This add a args feature, for example if your arg is called `argreply` You can do: `?reply Hello, read the following: {argreply} --- bot.py | 4 + cogs/modmail.py | 202 +++++++++++++++++++++++++++++++++++++++++++++++- core/config.py | 1 + 3 files changed, 206 insertions(+), 1 deletion(-) diff --git a/bot.py b/bot.py index 9f3de008a1..0c6e003c47 100644 --- a/bot.py +++ b/bot.py @@ -377,6 +377,10 @@ async def wait_for_connected(self) -> None: def snippets(self) -> typing.Dict[str, str]: return self.config["snippets"] + @property + def args(self) -> typing.Dict[str, str]: + return self.config["args"] + @property def aliases(self) -> typing.Dict[str, str]: return self.config["aliases"] diff --git a/cogs/modmail.py b/cogs/modmail.py index 0e39da920c..43210cec20 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -16,7 +16,7 @@ from dateutil import parser from core import checks -from core.models import DMDisabled, PermissionLevel, SimilarCategoryConverter, getLogger +from core.models import DMDisabled, PermissionLevel, SimilarCategoryConverter, UnseenFormatter, getLogger from core.paginator import EmbedPaginatorSession from core.thread import Thread from core.time import UserFriendlyTime, human_timedelta @@ -535,6 +535,199 @@ async def snippet_rename(self, ctx, name: str.lower, *, value): embed = create_not_found_embed(name, self.bot.snippets.keys(), "Snippet") await ctx.send(embed=embed) + @commands.group(invoke_without_command=True) + @checks.has_permissions(PermissionLevel.SUPPORTER) + async def args(self, ctx, *, name: str.lower = None): + """ + Create dynamic arguments for use in replies. + + When `{prefix}args` is used by itself, this will retrieve + a list of args that are currently set. `{prefix}args name` will show what the + arg point to. + + To create an arg: + - `{prefix}args add arg-name A value.` + + You can use your arg in a reply with `{arg-name}`. + """ + + if name is not None: + if name == "compact": + embeds = [] + + for i, names in enumerate(zip_longest(*(iter(sorted(self.bot.args)),) * 15)): + description = format_description(i, names) + embed = discord.Embed(color=self.bot.main_color, description=description) + embed.set_author( + name="Args", icon_url=self.bot.get_guild_icon(guild=ctx.guild, size=128) + ) + embeds.append(embed) + + session = EmbedPaginatorSession(ctx, *embeds) + await session.run() + return + + if name not in self.bot.args: + embed = create_not_found_embed(name, self.bot.args.keys(), "Arg") + else: + val = self.bot.args[name] + embed = discord.Embed( + title=f'Arg - "{name}":', + description=val, + color=self.bot.main_color, + ) + return await ctx.send(embed=embed) + + if not self.bot.args: + embed = discord.Embed( + color=self.bot.error_color, + description="You dont have any args at the moment.", + ) + embed.set_footer(text=f'Check "{self.bot.prefix}help args add" to add an arg.') + embed.set_author( + name="Args", + icon_url=self.bot.get_guild_icon(guild=ctx.guild, size=128), + ) + return await ctx.send(embed=embed) + + embeds = [discord.Embed(color=self.bot.main_color) for _ in range((len(self.bot.args) // 10) + 1)] + for embed in embeds: + embed.set_author(name="Args", icon_url=self.bot.get_guild_icon(guild=ctx.guild, size=128)) + + for i, arg in enumerate(sorted(self.bot.args.items())): + embeds[i // 10].add_field( + name=arg[0], value=return_or_truncate(arg[1], 350), inline=False + ) + + session = EmbedPaginatorSession(ctx, *embeds) + await session.run() + + @args.command(name="raw") + @checks.has_permissions(PermissionLevel.SUPPORTER) + async def args_raw(self, ctx, *, name: str.lower): + """ + View the raw content of an arg. + """ + if name not in self.bot.args: + embed = create_not_found_embed(name, self.bot.args.keys(), "Arg") + else: + val = truncate(escape_code_block(self.bot.args[name]), 2048 - 7) + embed = discord.Embed( + title=f'Raw arg - "{name}":', + description=f"```\n{val}```", + color=self.bot.main_color, + ) + + return await ctx.send(embed=embed) + + @args.command(name="add", aliases=["create", "make"]) + @checks.has_permissions(PermissionLevel.SUPPORTER) + async def args_add(self, ctx, name: str.lower, *, value: commands.clean_content): + """ + Add an arg. + + Simply to add an arg, do: ``` + {prefix}args add name value + ``` + """ + if name in self.bot.args: + embed = discord.Embed( + title="Error", + color=self.bot.error_color, + description=f"Arg `{name}` already exists.", + ) + return await ctx.send(embed=embed) + + if len(name) > 120: + embed = discord.Embed( + title="Error", + color=self.bot.error_color, + description="Arg names cannot be longer than 120 characters.", + ) + return await ctx.send(embed=embed) + + self.bot.args[name] = value + await self.bot.config.update() + + embed = discord.Embed( + title="Added arg", + color=self.bot.main_color, + description="Successfully created arg.", + ) + return await ctx.send(embed=embed) + + @args.command(name="remove", aliases=["del", "delete"]) + @checks.has_permissions(PermissionLevel.SUPPORTER) + async def args_remove(self, ctx, *, name: str.lower): + """Remove an arg.""" + if name in self.bot.args: + self.bot.args.pop(name) + await self.bot.config.update() + embed = discord.Embed( + title="Removed arg", + color=self.bot.main_color, + description=f"Arg `{name}` is now deleted.", + ) + else: + embed = create_not_found_embed(name, self.bot.args.keys(), "Arg") + await ctx.send(embed=embed) + + @args.command(name="edit") + @checks.has_permissions(PermissionLevel.SUPPORTER) + async def args_edit(self, ctx, name: str.lower, *, value): + """ + Edit an arg. + """ + if name in self.bot.args: + self.bot.args[name] = value + await self.bot.config.update() + + embed = discord.Embed( + title="Edited arg", + color=self.bot.main_color, + description=f'`{name}` will now be replaced with "{value}".', + ) + else: + embed = create_not_found_embed(name, self.bot.args.keys(), "Arg") + await ctx.send(embed=embed) + + @args.command(name="rename") + @checks.has_permissions(PermissionLevel.SUPPORTER) + async def args_rename(self, ctx, name: str.lower, *, value): + """ + Rename an arg. + """ + if name in self.bot.args: + if value in self.bot.args: + embed = discord.Embed( + title="Error", + color=self.bot.error_color, + description=f"Arg `{value}` already exists.", + ) + return await ctx.send(embed=embed) + + if len(value) > 120: + embed = discord.Embed( + title="Error", + color=self.bot.error_color, + description="Arg names cannot be longer than 120 characters.", + ) + return await ctx.send(embed=embed) + + old_arg_value = self.bot.args[name] + self.bot.args.pop(name) + self.bot.args[value] = old_arg_value + await self.bot.config.update() + + embed = discord.Embed( + title="Renamed arg", + color=self.bot.main_color, + description=f'`{name}` has been renamed to "{value}".', + ) + else: + embed = create_not_found_embed(name, self.bot.args.keys(), "Arg") + await ctx.send(embed=embed) + @commands.command(usage=" [options]") @checks.has_permissions(PermissionLevel.MODERATOR) @checks.thread_only() @@ -1510,6 +1703,9 @@ async def reply(self, ctx, *, msg: str = ""): automatically embedding image URLs. """ + if self.bot.args: + msg = UnseenFormatter().format(msg, **self.bot.args) + # Ensure logs record only the reply text, not the command. ctx.message.content = msg async with safe_typing(ctx): @@ -1532,6 +1728,7 @@ async def freply(self, ctx, *, msg: str = ""): """ msg = self.bot.formatter.format( msg, + **self.bot.args, channel=ctx.channel, recipient=ctx.thread.recipient, author=ctx.message.author, @@ -1558,6 +1755,7 @@ async def fareply(self, ctx, *, msg: str = ""): """ msg = self.bot.formatter.format( msg, + **self.bot.args, channel=ctx.channel, recipient=ctx.thread.recipient, author=ctx.message.author, @@ -1584,6 +1782,7 @@ async def fpreply(self, ctx, *, msg: str = ""): """ msg = self.bot.formatter.format( msg, + **self.bot.args, channel=ctx.channel, recipient=ctx.thread.recipient, author=ctx.message.author, @@ -1610,6 +1809,7 @@ async def fpareply(self, ctx, *, msg: str = ""): """ msg = self.bot.formatter.format( msg, + **self.bot.args, channel=ctx.channel, recipient=ctx.thread.recipient, author=ctx.message.author, diff --git a/core/config.py b/core/config.py index 0e45b00175..113e7f17e6 100644 --- a/core/config.py +++ b/core/config.py @@ -182,6 +182,7 @@ class ConfigManager: "override_command_level": {}, # threads "snippets": {}, + "args": {}, "notification_squad": {}, "subscriptions": {}, "closures": {}, From 0bd96b8cacc15631eaeff4bb8e059c5d2b82aa23 Mon Sep 17 00:00:00 2001 From: lorenzo132 Date: Sat, 13 Dec 2025 16:03:02 +0100 Subject: [PATCH 2/3] fix linting --- cogs/modmail.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/cogs/modmail.py b/cogs/modmail.py index 43210cec20..e62291a2fb 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -558,9 +558,7 @@ async def args(self, ctx, *, name: str.lower = None): for i, names in enumerate(zip_longest(*(iter(sorted(self.bot.args)),) * 15)): description = format_description(i, names) embed = discord.Embed(color=self.bot.main_color, description=description) - embed.set_author( - name="Args", icon_url=self.bot.get_guild_icon(guild=ctx.guild, size=128) - ) + embed.set_author(name="Args", icon_url=self.bot.get_guild_icon(guild=ctx.guild, size=128)) embeds.append(embed) session = EmbedPaginatorSession(ctx, *embeds) @@ -595,9 +593,7 @@ async def args(self, ctx, *, name: str.lower = None): embed.set_author(name="Args", icon_url=self.bot.get_guild_icon(guild=ctx.guild, size=128)) for i, arg in enumerate(sorted(self.bot.args.items())): - embeds[i // 10].add_field( - name=arg[0], value=return_or_truncate(arg[1], 350), inline=False - ) + embeds[i // 10].add_field(name=arg[0], value=return_or_truncate(arg[1], 350), inline=False) session = EmbedPaginatorSession(ctx, *embeds) await session.run() From fed5044de499fd208b825c6cff7624f3b547dcbe Mon Sep 17 00:00:00 2001 From: lorenzo132 Date: Sun, 14 Dec 2025 03:42:21 +0100 Subject: [PATCH 3/3] change wording --- cogs/modmail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/modmail.py b/cogs/modmail.py index e62291a2fb..3048f1f4ae 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -539,7 +539,7 @@ async def snippet_rename(self, ctx, name: str.lower, *, value): @checks.has_permissions(PermissionLevel.SUPPORTER) async def args(self, ctx, *, name: str.lower = None): """ - Create dynamic arguments for use in replies. + Create dynamic args for use in replies. When `{prefix}args` is used by itself, this will retrieve a list of args that are currently set. `{prefix}args name` will show what the