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.
This commit is contained in:
neoarz
2025-09-29 09:59:56 -04:00
parent f8123b43a2
commit 3f4254dcdc
8 changed files with 154 additions and 0 deletions

2
bot.py
View File

@@ -283,6 +283,8 @@ class DiscordBot(commands.Bot):
color=0xE02B2B, color=0xE02B2B,
) )
await context.send(embed=embed) await context.send(embed=embed)
elif isinstance(error, commands.CheckFailure):
return
else: else:
raise error raise error

View File

@@ -31,6 +31,16 @@ class Fun(commands.GroupCog, name="fun"):
else: else:
await context.send(f"Unknown fun command: {name}") 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") @fun_group.command(name="coinflip")
async def fun_group_coinflip(self, context: Context): async def fun_group_coinflip(self, context: Context):
await self._invoke_hybrid(context, "coinflip") await self._invoke_hybrid(context, "coinflip")
@@ -51,6 +61,7 @@ class Fun(commands.GroupCog, name="fun"):
async def fun_group_rps(self, context: Context): async def fun_group_rps(self, context: Context):
await self._invoke_hybrid(context, "rps") await self._invoke_hybrid(context, "rps")
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="coinflip", name="coinflip",
description="Make a coin flip, but give your bet before." 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): async def coinflip(self, context):
return await coinflip_command()(self, context) return await coinflip_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="8ball", name="8ball",
description="Ask any question to the bot.", 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): async def eight_ball(self, context, *, question: str):
return await eightball_command()(self, context, question=question) return await eightball_command()(self, context, question=question)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="minesweeper", name="minesweeper",
description="Play a buttoned minesweeper mini-game." description="Play a buttoned minesweeper mini-game."
@@ -72,10 +85,12 @@ class Fun(commands.GroupCog, name="fun"):
async def minesweeper(self, context): async def minesweeper(self, context):
return await minesweeper_command()(self, context) return await minesweeper_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command(name="randomfact", description="Get a random fact.") @commands.hybrid_command(name="randomfact", description="Get a random fact.")
async def randomfact(self, context): async def randomfact(self, context):
return await randomfact_command()(self, context) return await randomfact_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="rps", description="Play the rock paper scissors game against the bot." name="rps", description="Play the rock paper scissors game against the bot."
) )

View File

@@ -8,6 +8,17 @@ from .botinfo import botinfo_command
from .serverinfo import serverinfo_command from .serverinfo import serverinfo_command
from .feedback import feedback_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"): class General(commands.GroupCog, name="general"):
def __init__(self, bot) -> None: def __init__(self, bot) -> None:
self.bot = bot self.bot = bot
@@ -51,6 +62,7 @@ class General(commands.GroupCog, name="general"):
async def general_group_feedback(self, context: Context): async def general_group_feedback(self, context: Context):
await self._invoke_hybrid(context, "feedback") await self._invoke_hybrid(context, "feedback")
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="ping", name="ping",
description="Check if the bot is alive.", description="Check if the bot is alive.",
@@ -58,6 +70,7 @@ class General(commands.GroupCog, name="general"):
async def ping(self, context): async def ping(self, context):
return await ping_command()(self, context) return await ping_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="uptime", name="uptime",
description="Check how long the bot has been running.", description="Check how long the bot has been running.",
@@ -65,6 +78,7 @@ class General(commands.GroupCog, name="general"):
async def uptime(self, context): async def uptime(self, context):
return await uptime_command()(self, context) return await uptime_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="botinfo", name="botinfo",
description="Get some useful (or not) information about the bot.", 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): async def botinfo(self, context):
return await botinfo_command()(self, context) return await botinfo_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="serverinfo", name="serverinfo",
description="Get some useful (or not) information about the server.", 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): async def serverinfo(self, context):
return await serverinfo_command()(self, context) return await serverinfo_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="feedback", name="feedback",
description="Submit a feedback for the owners of the bot" description="Submit a feedback for the owners of the bot"

View File

@@ -43,6 +43,16 @@ class Idevice(commands.GroupCog, name="idevice"):
else: else:
await context.send(f"Unknown idevice command: {name}") 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") @idevice_group.command(name="errorcodes")
async def idevice_group_errorcodes(self, context: Context): async def idevice_group_errorcodes(self, context: Context):
await self._invoke_hybrid(context, "errorcodes") await self._invoke_hybrid(context, "errorcodes")
@@ -73,6 +83,7 @@ class Idevice(commands.GroupCog, name="idevice"):
view = ideviceView(self.bot) view = ideviceView(self.bot)
await interaction.response.send_message(embed=embed, view=view, ephemeral=True) await interaction.response.send_message(embed=embed, view=view, ephemeral=True)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="errorcodes", name="errorcodes",
description="Look up error codes and their meanings." 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): async def errorcodes(self, context, *, error_code: str = None):
return await errorcodes_command()(self, context, error_code=error_code) return await errorcodes_command()(self, context, error_code=error_code)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="developermode", name="developermode",
description="How to turn on developer mode" description="How to turn on developer mode"
@@ -87,6 +99,7 @@ class Idevice(commands.GroupCog, name="idevice"):
async def developermode(self, context): async def developermode(self, context):
return await developermode_command()(self, context) return await developermode_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="noapps", name="noapps",
description="Help when apps aren't showing in installed apps view" 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): async def noapps(self, context):
return await noapps_command()(self, context) return await noapps_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="mountddi", name="mountddi",
description="How to manually mount DDI" description="How to manually mount DDI"

View File

@@ -31,6 +31,16 @@ class Miscellaneous(commands.GroupCog, name="misc"):
else: else:
await context.send(f"Unknown miscellaneous command: {name}") 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") @miscellaneous_group.command(name="rr")
async def miscellaneous_group_rr(self, context: Context): async def miscellaneous_group_rr(self, context: Context):
await self._invoke_hybrid(context, "rr") await self._invoke_hybrid(context, "rr")
@@ -51,6 +61,7 @@ class Miscellaneous(commands.GroupCog, name="misc"):
async def miscellaneous_group_keanu(self, context: Context): async def miscellaneous_group_keanu(self, context: Context):
await self._invoke_hybrid(context, "keanu") await self._invoke_hybrid(context, "keanu")
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="rr", name="rr",
description="Rickroll" description="Rickroll"
@@ -58,6 +69,7 @@ class Miscellaneous(commands.GroupCog, name="misc"):
async def rr(self, context): async def rr(self, context):
return await rr_command()(self, context) return await rr_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="labubu", name="labubu",
description="Labubu ASCII art" description="Labubu ASCII art"
@@ -65,6 +77,7 @@ class Miscellaneous(commands.GroupCog, name="misc"):
async def labubu(self, context): async def labubu(self, context):
return await labubu_command()(self, context) return await labubu_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="tryitandsee", name="tryitandsee",
description="Try it and see" description="Try it and see"
@@ -72,6 +85,7 @@ class Miscellaneous(commands.GroupCog, name="misc"):
async def tryitandsee(self, context): async def tryitandsee(self, context):
return await tryitandsee_command()(self, context) return await tryitandsee_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="piracy", name="piracy",
description="FBI Anti Piracy Warning" description="FBI Anti Piracy Warning"
@@ -79,6 +93,7 @@ class Miscellaneous(commands.GroupCog, name="misc"):
async def piracy(self, context): async def piracy(self, context):
return await piracy_command()(self, context) return await piracy_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="keanu", name="keanu",
description="Reeves" description="Reeves"

View File

@@ -15,6 +15,62 @@ class Moderation(commands.GroupCog, name="moderation"):
self.bot = bot self.bot = bot
super().__init__() 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 <subcommand>` or `/moderation <subcommand>`.",
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( @commands.hybrid_command(
name="ban", name="ban",
description="Bans a user from the server." 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"): 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) return await ban_command()(self, context, user=user, reason=reason, delete_messages=delete_messages)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="kick", name="kick",
description="Kicks a user from the server." 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"): async def kick(self, context, user: discord.User, *, reason: str = "Not specified"):
return await kick_command()(self, context, user=user, reason=reason) return await kick_command()(self, context, user=user, reason=reason)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="purge", name="purge",
description="Delete a number of messages." description="Delete a number of messages."
@@ -36,6 +94,7 @@ class Moderation(commands.GroupCog, name="moderation"):
async def purge(self, context, amount: int): async def purge(self, context, amount: int):
return await purge_command()(self, context, amount=amount) return await purge_command()(self, context, amount=amount)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="warnings", name="warnings",
description="Manage warnings of a user on a server." description="Manage warnings of a user on a server."
@@ -43,6 +102,7 @@ class Moderation(commands.GroupCog, name="moderation"):
async def warnings(self, context): async def warnings(self, context):
return await warnings_command()(self, context) return await warnings_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="archive", name="archive",
description="Archives in a text file the last messages with a chosen limit of messages." 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): async def archive(self, context, limit: int = 10):
return await archive_command()(self, context, limit=limit) return await archive_command()(self, context, limit=limit)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="hackban", name="hackban",
description="Bans a user without the user having to be in the server." 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"): async def hackban(self, context, user_id: int, *, reason: str = "Not specified"):
return await hackban_command()(self, context, user_id=user_id, reason=reason) return await hackban_command()(self, context, user_id=user_id, reason=reason)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="nick", name="nick",
description="Change the nickname of a user on a server." description="Change the nickname of a user on a server."

View File

@@ -48,6 +48,16 @@ class Sidestore(commands.GroupCog, name="sidestore"):
else: else:
await context.send(f"Unknown SideStore command: {name}") 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") @sidestore_group.command(name="refresh")
async def sidestore_group_refresh(self, context: Context): async def sidestore_group_refresh(self, context: Context):
await self._invoke_hybrid(context, "refresh") 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) await interaction.response.send_message(embed=embed, view=view, ephemeral=True)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="refresh", name="refresh",
description="Help with refreshing or installing apps" description="Help with refreshing or installing apps"
@@ -107,6 +118,7 @@ class Sidestore(commands.GroupCog, name="sidestore"):
async def refresh(self, context): async def refresh(self, context):
return await refresh_command()(self, context) return await refresh_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="code", name="code",
description="No code received when signing in with Apple ID" 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): async def code(self, context):
return await code_command()(self, context) return await code_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="crash", name="crash",
description="Help with SideStore crashing issues" description="Help with SideStore crashing issues"
@@ -121,6 +134,7 @@ class Sidestore(commands.GroupCog, name="sidestore"):
async def crash(self, context): async def crash(self, context):
return await crash_command()(self, context) return await crash_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="pairing", name="pairing",
description="Help with pairing file issues" description="Help with pairing file issues"
@@ -128,6 +142,7 @@ class Sidestore(commands.GroupCog, name="sidestore"):
async def pairing(self, context): async def pairing(self, context):
return await pairing_command()(self, context) return await pairing_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="server", name="server",
description="Help with anisette server issues" description="Help with anisette server issues"
@@ -135,6 +150,7 @@ class Sidestore(commands.GroupCog, name="sidestore"):
async def server(self, context): async def server(self, context):
return await server_command()(self, context) return await server_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="afc", name="afc",
description="Help with AFC Connection Failure issues" description="Help with AFC Connection Failure issues"
@@ -142,6 +158,7 @@ class Sidestore(commands.GroupCog, name="sidestore"):
async def afc(self, context): async def afc(self, context):
return await afc_command()(self, context) return await afc_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="udid", name="udid",
description="SideStore could not determine device UDID" description="SideStore could not determine device UDID"
@@ -149,6 +166,7 @@ class Sidestore(commands.GroupCog, name="sidestore"):
async def udid(self, context): async def udid(self, context):
return await udid_command()(self, context) return await udid_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="half", name="half",
description="Help with half-installed apps" description="Help with half-installed apps"
@@ -156,6 +174,7 @@ class Sidestore(commands.GroupCog, name="sidestore"):
async def half(self, context): async def half(self, context):
return await half_command()(self, context) return await half_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="sparse", name="sparse",
description="Help with sparse bundle issues" description="Help with sparse bundle issues"

View File

@@ -27,10 +27,21 @@ class Utilities(commands.GroupCog, name="utils"):
else: else:
await context.send(f"Unknown utilities command: {name}") 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") @utilities_group.command(name="translate")
async def utilities_group_translate(self, context: Context, text: str = None, to_lang: str = "en", from_lang: str = None): 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) await self._invoke_hybrid(context, "translate", text=text, to_lang=to_lang, from_lang=from_lang)
@commands.check(_require_group_prefix)
@commands.hybrid_command( @commands.hybrid_command(
name="translate", name="translate",
description="Translate text to another language" description="Translate text to another language"