From 3f4254dcdcc6c5e0542931f9737123a5ef2b0128 Mon Sep 17 00:00:00 2001 From: neoarz Date: Mon, 29 Sep 2025 09:59:56 -0400 Subject: [PATCH] feat: Require group prefix for hybrid commands Added a _require_group_prefix check to all hybrid commands in each cog to ensure commands are only executed when invoked with the appropriate group prefix. Also updated error handling in bot.py to silently ignore CheckFailure errors. This improves command organization and prevents accidental command execution outside their intended context. --- bot.py | 2 ++ cogs/fun/__init__.py | 15 ++++++++ cogs/general/__init__.py | 16 +++++++++ cogs/idevice/__init__.py | 14 ++++++++ cogs/miscellaneous/__init__.py | 15 ++++++++ cogs/moderation/__init__.py | 62 ++++++++++++++++++++++++++++++++++ cogs/sidestore/__init__.py | 19 +++++++++++ cogs/utilities/__init__.py | 11 ++++++ 8 files changed, 154 insertions(+) diff --git a/bot.py b/bot.py index f9cfe5d..3670617 100644 --- a/bot.py +++ b/bot.py @@ -283,6 +283,8 @@ class DiscordBot(commands.Bot): color=0xE02B2B, ) await context.send(embed=embed) + elif isinstance(error, commands.CheckFailure): + return else: raise error diff --git a/cogs/fun/__init__.py b/cogs/fun/__init__.py index a08fc44..18420de 100644 --- a/cogs/fun/__init__.py +++ b/cogs/fun/__init__.py @@ -31,6 +31,16 @@ class Fun(commands.GroupCog, name="fun"): else: await context.send(f"Unknown fun command: {name}") + def _require_group_prefix(context: Context) -> bool: + if getattr(context, "interaction", None): + return True + group = getattr(getattr(context, "cog", None), "qualified_name", "").lower() + if not group: + return True + prefix = context.prefix or "" + content = context.message.content.strip().lower() + return content.startswith(f"{prefix}{group} ") + @fun_group.command(name="coinflip") async def fun_group_coinflip(self, context: Context): await self._invoke_hybrid(context, "coinflip") @@ -51,6 +61,7 @@ class Fun(commands.GroupCog, name="fun"): async def fun_group_rps(self, context: Context): await self._invoke_hybrid(context, "rps") + @commands.check(_require_group_prefix) @commands.hybrid_command( name="coinflip", description="Make a coin flip, but give your bet before." @@ -58,6 +69,7 @@ class Fun(commands.GroupCog, name="fun"): async def coinflip(self, context): return await coinflip_command()(self, context) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="8ball", description="Ask any question to the bot.", @@ -65,6 +77,7 @@ class Fun(commands.GroupCog, name="fun"): async def eight_ball(self, context, *, question: str): return await eightball_command()(self, context, question=question) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="minesweeper", description="Play a buttoned minesweeper mini-game." @@ -72,10 +85,12 @@ class Fun(commands.GroupCog, name="fun"): async def minesweeper(self, context): return await minesweeper_command()(self, context) + @commands.check(_require_group_prefix) @commands.hybrid_command(name="randomfact", description="Get a random fact.") async def randomfact(self, context): return await randomfact_command()(self, context) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="rps", description="Play the rock paper scissors game against the bot." ) diff --git a/cogs/general/__init__.py b/cogs/general/__init__.py index 6e92d77..e37c04c 100644 --- a/cogs/general/__init__.py +++ b/cogs/general/__init__.py @@ -8,6 +8,17 @@ from .botinfo import botinfo_command from .serverinfo import serverinfo_command from .feedback import feedback_command + +def _require_group_prefix(context: Context) -> bool: + if getattr(context, "interaction", None): + return True + group = getattr(getattr(context, "cog", None), "qualified_name", "").lower() + if not group: + return True + prefix = context.prefix or "" + content = context.message.content.strip().lower() + return content.startswith(f"{prefix}{group} ") + class General(commands.GroupCog, name="general"): def __init__(self, bot) -> None: self.bot = bot @@ -51,6 +62,7 @@ class General(commands.GroupCog, name="general"): async def general_group_feedback(self, context: Context): await self._invoke_hybrid(context, "feedback") + @commands.check(_require_group_prefix) @commands.hybrid_command( name="ping", description="Check if the bot is alive.", @@ -58,6 +70,7 @@ class General(commands.GroupCog, name="general"): async def ping(self, context): return await ping_command()(self, context) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="uptime", description="Check how long the bot has been running.", @@ -65,6 +78,7 @@ class General(commands.GroupCog, name="general"): async def uptime(self, context): return await uptime_command()(self, context) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="botinfo", description="Get some useful (or not) information about the bot.", @@ -72,6 +86,7 @@ class General(commands.GroupCog, name="general"): async def botinfo(self, context): return await botinfo_command()(self, context) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="serverinfo", description="Get some useful (or not) information about the server.", @@ -79,6 +94,7 @@ class General(commands.GroupCog, name="general"): async def serverinfo(self, context): return await serverinfo_command()(self, context) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="feedback", description="Submit a feedback for the owners of the bot" diff --git a/cogs/idevice/__init__.py b/cogs/idevice/__init__.py index 2f3cf5e..de3ad81 100644 --- a/cogs/idevice/__init__.py +++ b/cogs/idevice/__init__.py @@ -43,6 +43,16 @@ class Idevice(commands.GroupCog, name="idevice"): else: await context.send(f"Unknown idevice command: {name}") + def _require_group_prefix(context: Context) -> bool: + if getattr(context, "interaction", None): + return True + group = getattr(getattr(context, "cog", None), "qualified_name", "").lower() + if not group: + return True + prefix = context.prefix or "" + content = context.message.content.strip().lower() + return content.startswith(f"{prefix}{group} ") + @idevice_group.command(name="errorcodes") async def idevice_group_errorcodes(self, context: Context): await self._invoke_hybrid(context, "errorcodes") @@ -73,6 +83,7 @@ class Idevice(commands.GroupCog, name="idevice"): view = ideviceView(self.bot) await interaction.response.send_message(embed=embed, view=view, ephemeral=True) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="errorcodes", description="Look up error codes and their meanings." @@ -80,6 +91,7 @@ class Idevice(commands.GroupCog, name="idevice"): async def errorcodes(self, context, *, error_code: str = None): return await errorcodes_command()(self, context, error_code=error_code) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="developermode", description="How to turn on developer mode" @@ -87,6 +99,7 @@ class Idevice(commands.GroupCog, name="idevice"): async def developermode(self, context): return await developermode_command()(self, context) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="noapps", description="Help when apps aren't showing in installed apps view" @@ -94,6 +107,7 @@ class Idevice(commands.GroupCog, name="idevice"): async def noapps(self, context): return await noapps_command()(self, context) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="mountddi", description="How to manually mount DDI" diff --git a/cogs/miscellaneous/__init__.py b/cogs/miscellaneous/__init__.py index da7828c..1314059 100644 --- a/cogs/miscellaneous/__init__.py +++ b/cogs/miscellaneous/__init__.py @@ -31,6 +31,16 @@ class Miscellaneous(commands.GroupCog, name="misc"): else: await context.send(f"Unknown miscellaneous command: {name}") + def _require_group_prefix(context: Context) -> bool: + if getattr(context, "interaction", None): + return True + group = getattr(getattr(context, "cog", None), "qualified_name", "").lower() + if not group: + return True + prefix = context.prefix or "" + content = context.message.content.strip().lower() + return content.startswith(f"{prefix}{group} ") + @miscellaneous_group.command(name="rr") async def miscellaneous_group_rr(self, context: Context): await self._invoke_hybrid(context, "rr") @@ -51,6 +61,7 @@ class Miscellaneous(commands.GroupCog, name="misc"): async def miscellaneous_group_keanu(self, context: Context): await self._invoke_hybrid(context, "keanu") + @commands.check(_require_group_prefix) @commands.hybrid_command( name="rr", description="Rickroll" @@ -58,6 +69,7 @@ class Miscellaneous(commands.GroupCog, name="misc"): async def rr(self, context): return await rr_command()(self, context) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="labubu", description="Labubu ASCII art" @@ -65,6 +77,7 @@ class Miscellaneous(commands.GroupCog, name="misc"): async def labubu(self, context): return await labubu_command()(self, context) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="tryitandsee", description="Try it and see" @@ -72,6 +85,7 @@ class Miscellaneous(commands.GroupCog, name="misc"): async def tryitandsee(self, context): return await tryitandsee_command()(self, context) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="piracy", description="FBI Anti Piracy Warning" @@ -79,6 +93,7 @@ class Miscellaneous(commands.GroupCog, name="misc"): async def piracy(self, context): return await piracy_command()(self, context) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="keanu", description="Reeves" diff --git a/cogs/moderation/__init__.py b/cogs/moderation/__init__.py index 0553ba1..459ae0d 100644 --- a/cogs/moderation/__init__.py +++ b/cogs/moderation/__init__.py @@ -15,6 +15,62 @@ class Moderation(commands.GroupCog, name="moderation"): self.bot = bot super().__init__() + @commands.group(name="moderation", invoke_without_command=True) + async def moderation_group(self, context: Context): + embed = discord.Embed( + title="Moderation Commands", + description="Use `.moderation ` or `/moderation `.", + color=0x7289DA + ) + embed.add_field(name="Available", value="ban, kick, purge, warnings, archive, hackban, nick", inline=False) + await context.send(embed=embed) + + async def _invoke_hybrid(self, context: Context, name: str, **kwargs): + command = self.bot.get_command(name) + if command is not None: + await context.invoke(command, **kwargs) + else: + await context.send(f"Unknown moderation command: {name}") + + def _require_group_prefix(context: Context) -> bool: + if getattr(context, "interaction", None): + return True + group = getattr(getattr(context, "cog", None), "qualified_name", "").lower() + if not group: + return True + prefix = context.prefix or "" + content = context.message.content.strip().lower() + return content.startswith(f"{prefix}{group} ") + + @moderation_group.command(name="ban") + async def moderation_group_ban(self, context: Context, user: discord.User, *, reason: str = "Not specified", delete_messages: str = "none"): + await self._invoke_hybrid(context, "ban", user=user, reason=reason, delete_messages=delete_messages) + + @moderation_group.command(name="kick") + async def moderation_group_kick(self, context: Context, user: discord.User, *, reason: str = "Not specified"): + await self._invoke_hybrid(context, "kick", user=user, reason=reason) + + @moderation_group.command(name="purge") + async def moderation_group_purge(self, context: Context, amount: int): + await self._invoke_hybrid(context, "purge", amount=amount) + + @moderation_group.command(name="warnings") + async def moderation_group_warnings(self, context: Context): + await self._invoke_hybrid(context, "warnings") + + @moderation_group.command(name="archive") + async def moderation_group_archive(self, context: Context, limit: int = 10): + await self._invoke_hybrid(context, "archive", limit=limit) + + @moderation_group.command(name="hackban") + async def moderation_group_hackban(self, context: Context, user_id: int, *, reason: str = "Not specified"): + await self._invoke_hybrid(context, "hackban", user_id=user_id, reason=reason) + + @moderation_group.command(name="nick") + async def moderation_group_nick(self, context: Context, user: discord.User, *, nickname: str = None): + await self._invoke_hybrid(context, "nick", user=user, nickname=nickname) + + @commands.check(_require_group_prefix) @commands.hybrid_command( name="ban", description="Bans a user from the server." @@ -22,6 +78,7 @@ class Moderation(commands.GroupCog, name="moderation"): async def ban(self, context, user: discord.User, *, reason: str = "Not specified", delete_messages: str = "none"): return await ban_command()(self, context, user=user, reason=reason, delete_messages=delete_messages) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="kick", description="Kicks a user from the server." @@ -29,6 +86,7 @@ class Moderation(commands.GroupCog, name="moderation"): async def kick(self, context, user: discord.User, *, reason: str = "Not specified"): return await kick_command()(self, context, user=user, reason=reason) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="purge", description="Delete a number of messages." @@ -36,6 +94,7 @@ class Moderation(commands.GroupCog, name="moderation"): async def purge(self, context, amount: int): return await purge_command()(self, context, amount=amount) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="warnings", description="Manage warnings of a user on a server." @@ -43,6 +102,7 @@ class Moderation(commands.GroupCog, name="moderation"): async def warnings(self, context): return await warnings_command()(self, context) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="archive", description="Archives in a text file the last messages with a chosen limit of messages." @@ -50,6 +110,7 @@ class Moderation(commands.GroupCog, name="moderation"): async def archive(self, context, limit: int = 10): return await archive_command()(self, context, limit=limit) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="hackban", description="Bans a user without the user having to be in the server." @@ -57,6 +118,7 @@ class Moderation(commands.GroupCog, name="moderation"): async def hackban(self, context, user_id: int, *, reason: str = "Not specified"): return await hackban_command()(self, context, user_id=user_id, reason=reason) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="nick", description="Change the nickname of a user on a server." diff --git a/cogs/sidestore/__init__.py b/cogs/sidestore/__init__.py index b9c1f26..6c28437 100644 --- a/cogs/sidestore/__init__.py +++ b/cogs/sidestore/__init__.py @@ -48,6 +48,16 @@ class Sidestore(commands.GroupCog, name="sidestore"): else: await context.send(f"Unknown SideStore command: {name}") + def _require_group_prefix(context: Context) -> bool: + if getattr(context, "interaction", None): + return True + group = getattr(getattr(context, "cog", None), "qualified_name", "").lower() + if not group: + return True + prefix = context.prefix or "" + content = context.message.content.strip().lower() + return content.startswith(f"{prefix}{group} ") + @sidestore_group.command(name="refresh") async def sidestore_group_refresh(self, context: Context): await self._invoke_hybrid(context, "refresh") @@ -100,6 +110,7 @@ class Sidestore(commands.GroupCog, name="sidestore"): await interaction.response.send_message(embed=embed, view=view, ephemeral=True) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="refresh", description="Help with refreshing or installing apps" @@ -107,6 +118,7 @@ class Sidestore(commands.GroupCog, name="sidestore"): async def refresh(self, context): return await refresh_command()(self, context) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="code", description="No code received when signing in with Apple ID" @@ -114,6 +126,7 @@ class Sidestore(commands.GroupCog, name="sidestore"): async def code(self, context): return await code_command()(self, context) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="crash", description="Help with SideStore crashing issues" @@ -121,6 +134,7 @@ class Sidestore(commands.GroupCog, name="sidestore"): async def crash(self, context): return await crash_command()(self, context) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="pairing", description="Help with pairing file issues" @@ -128,6 +142,7 @@ class Sidestore(commands.GroupCog, name="sidestore"): async def pairing(self, context): return await pairing_command()(self, context) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="server", description="Help with anisette server issues" @@ -135,6 +150,7 @@ class Sidestore(commands.GroupCog, name="sidestore"): async def server(self, context): return await server_command()(self, context) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="afc", description="Help with AFC Connection Failure issues" @@ -142,6 +158,7 @@ class Sidestore(commands.GroupCog, name="sidestore"): async def afc(self, context): return await afc_command()(self, context) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="udid", description="SideStore could not determine device UDID" @@ -149,6 +166,7 @@ class Sidestore(commands.GroupCog, name="sidestore"): async def udid(self, context): return await udid_command()(self, context) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="half", description="Help with half-installed apps" @@ -156,6 +174,7 @@ class Sidestore(commands.GroupCog, name="sidestore"): async def half(self, context): return await half_command()(self, context) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="sparse", description="Help with sparse bundle issues" diff --git a/cogs/utilities/__init__.py b/cogs/utilities/__init__.py index 3364dce..dd23422 100644 --- a/cogs/utilities/__init__.py +++ b/cogs/utilities/__init__.py @@ -27,10 +27,21 @@ class Utilities(commands.GroupCog, name="utils"): else: await context.send(f"Unknown utilities command: {name}") + def _require_group_prefix(context: Context) -> bool: + if getattr(context, "interaction", None): + return True + group = getattr(getattr(context, "cog", None), "qualified_name", "").lower() + if not group: + return True + prefix = context.prefix or "" + content = context.message.content.strip().lower() + return content.startswith(f"{prefix}{group} ") + @utilities_group.command(name="translate") async def utilities_group_translate(self, context: Context, text: str = None, to_lang: str = "en", from_lang: str = None): await self._invoke_hybrid(context, "translate", text=text, to_lang=to_lang, from_lang=from_lang) + @commands.check(_require_group_prefix) @commands.hybrid_command( name="translate", description="Translate text to another language"