Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
198 changes: 197 additions & 1 deletion cogs/modmail.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -535,6 +535,195 @@ 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 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
arg point to.
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grammar error: "arg point to" should be "arg points to" (subject-verb agreement).

Suggested change
arg point to.
arg points to.

Copilot uses AI. Check for mistakes.

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.",
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling error: "dont" should be "don't".

Suggested change
description="You dont have any args at the moment.",
description="You don't have any args at the moment.",

Copilot uses AI. Check for mistakes.
)
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)
Comment on lines +619 to +653
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing validation: The args_add command should validate that the arg name doesn't conflict with existing command names or aliases, similar to how snippet_add does. This prevents creating args with names that could conflict with bot commands.

Copilot uses AI. Check for mistakes.

@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)
Comment on lines +690 to +725
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing validation: The args_rename command should validate that the new name doesn't conflict with existing command names or aliases, similar to how snippet_rename does (lines 494-516 in this file). This prevents creating args with names that could conflict with bot commands.

Copilot uses AI. Check for mistakes.

@commands.command(usage="<category> [options]")
@checks.has_permissions(PermissionLevel.MODERATOR)
@checks.thread_only()
Expand Down Expand Up @@ -1510,6 +1699,9 @@ async def reply(self, ctx, *, msg: str = ""):
automatically embedding image URLs.
"""

if self.bot.args:
msg = UnseenFormatter().format(msg, **self.bot.args)
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent formatter usage: The reply command uses UnseenFormatter which preserves unknown placeholders like {unknown}, while freply, fareply, fpreply, and fpareply use SafeFormatter which replaces them with "". This creates inconsistent behavior when users use args in different reply commands. Consider using UnseenFormatter consistently across all reply commands, or ensure SafeFormatter is used in the reply command as well for consistency.

Suggested change
msg = UnseenFormatter().format(msg, **self.bot.args)
msg = self.bot.formatter.format(msg, **self.bot.args)

Copilot uses AI. Check for mistakes.

# Ensure logs record only the reply text, not the command.
ctx.message.content = msg
async with safe_typing(ctx):
Expand All @@ -1532,6 +1724,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,
Expand All @@ -1558,6 +1751,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,
Expand All @@ -1584,6 +1778,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,
Expand All @@ -1610,6 +1805,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,
Expand Down
2 changes: 1 addition & 1 deletion cogs/utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ async def format_cog_help(self, cog, *, no_cog=False):
return embeds

def process_help_msg(self, help_: str):
return help_.format(prefix=self.context.clean_prefix) if help_ else "No help message."
return help_.replace("{prefix}", self.context.clean_prefix) if help_ else "No help message."

Comment on lines +112 to 113
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing from .format() to .replace() may cause issues if help strings contain other format-style placeholders. Consider using UnseenFormatter here instead for consistency with the args feature implementation, which would handle both {prefix} and leave unknown placeholders intact.

Suggested change
return help_.replace("{prefix}", self.context.clean_prefix) if help_ else "No help message."
if not help_:
return "No help message."
formatter = UnseenFormatter()
return formatter.format(help_, prefix=self.context.clean_prefix)

Copilot uses AI. Check for mistakes.
async def send_bot_help(self, mapping):
embeds = []
Expand Down
1 change: 1 addition & 0 deletions core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ class ConfigManager:
"override_command_level": {},
# threads
"snippets": {},
"args": {},
"notification_squad": {},
"subscriptions": {},
"closures": {},
Expand Down
Loading