From e7ee97074bc439bffafc7675fa22dd972789ebaf Mon Sep 17 00:00:00 2001 From: neoarz Date: Sun, 28 Sep 2025 22:48:10 -0400 Subject: [PATCH 01/15] refactor(fun): cogs into one group Combined all fun commands into a single 'fun' GroupCog with subcommands, replacing individual cogs for coinflip, eightball, minesweeper, randomfact, and rockpaperscissors. Updated bot.py to load the fun cog as a package and adjusted help.py to reflect the new command structure. This improves organization and discoverability of fun commands. --- bot.py | 42 ++++++++++++++++---------- cogs/fun/__init__.py | 55 +++++++++++++++++++++++++++++++++++ cogs/fun/coinflip.py | 20 ++++--------- cogs/fun/eightball.py | 24 +++------------ cogs/fun/minesweeper.py | 23 ++++----------- cogs/fun/randomfact.py | 15 +++------- cogs/fun/rockpaperscissors.py | 14 +++------ cogs/general/help.py | 14 +++++---- 8 files changed, 114 insertions(+), 93 deletions(-) create mode 100644 cogs/fun/__init__.py diff --git a/bot.py b/bot.py index d75f222..9f1f3d5 100644 --- a/bot.py +++ b/bot.py @@ -86,21 +86,33 @@ class DiscordBot(commands.Bot): for folder in os.listdir(cogs_path): folder_path = os.path.join(cogs_path, folder) if os.path.isdir(folder_path) and not folder.startswith('__'): - for file in os.listdir(folder_path): - if file.endswith(".py") and not file.startswith('__'): - extension = file[:-3] - full_name = f"{folder}.{extension}".lower() - if extension.lower() in disabled_cogs or full_name in disabled_cogs: - self.logger.info(f"Skipped disabled extension '{full_name}'") - continue - try: - await self.load_extension(f"cogs.{folder}.{extension}") - self.logger.info(f"Loaded extension '{folder}.{extension}'") - except Exception as e: - exception = f"{type(e).__name__}: {e}" - self.logger.error( - f"Failed to load extension {folder}.{extension}\n{exception}" - ) + init_file = os.path.join(folder_path, "__init__.py") + if os.path.exists(init_file): + try: + await self.load_extension(f"cogs.{folder}") + if folder != "fun": + self.logger.info(f"Loaded extension '{folder}'") + except Exception as e: + exception = f"{type(e).__name__}: {e}" + self.logger.error( + f"Failed to load extension {folder}\n{exception}" + ) + else: + for file in os.listdir(folder_path): + if file.endswith(".py") and not file.startswith('__'): + extension = file[:-3] + full_name = f"{folder}.{extension}".lower() + if extension.lower() in disabled_cogs or full_name in disabled_cogs: + self.logger.info(f"Skipped disabled extension '{full_name}'") + continue + try: + await self.load_extension(f"cogs.{folder}.{extension}") + self.logger.info(f"Loaded extension '{folder}.{extension}'") + except Exception as e: + exception = f"{type(e).__name__}: {e}" + self.logger.error( + f"Failed to load extension {folder}.{extension}\n{exception}" + ) for file in os.listdir(cogs_path): if file.endswith(".py") and not file.startswith('__'): diff --git a/cogs/fun/__init__.py b/cogs/fun/__init__.py new file mode 100644 index 0000000..8cb0e7c --- /dev/null +++ b/cogs/fun/__init__.py @@ -0,0 +1,55 @@ +import discord +from discord.ext import commands +from discord.ext.commands import Context + +from .coinflip import coinflip_command +from .eightball import eightball_command +from .minesweeper import minesweeper_command +from .randomfact import randomfact_command +from .rockpaperscissors import rps_command + +class Fun(commands.GroupCog, name="fun"): + def __init__(self, bot) -> None: + self.bot = bot + super().__init__() + + @commands.hybrid_command( + name="coinflip", + description="Make a coin flip, but give your bet before." + ) + async def coinflip(self, context): + return await coinflip_command()(self, context) + + @commands.hybrid_command( + name="8ball", + description="Ask any question to the bot.", + ) + async def eight_ball(self, context, *, question: str): + return await eightball_command()(self, context, question=question) + + @commands.hybrid_command( + name="minesweeper", + description="Play a buttoned minesweeper mini-game." + ) + async def minesweeper(self, context): + return await minesweeper_command()(self, context) + + @commands.hybrid_command(name="randomfact", description="Get a random fact.") + async def randomfact(self, context): + return await randomfact_command()(self, context) + + @commands.hybrid_command( + name="rps", description="Play the rock paper scissors game against the bot." + ) + async def rock_paper_scissors(self, context): + return await rps_command()(self, context) + +async def setup(bot) -> None: + cog = Fun(bot) + await bot.add_cog(cog) + + bot.logger.info("Loaded extension 'fun.coinflip'") + bot.logger.info("Loaded extension 'fun.8ball'") + bot.logger.info("Loaded extension 'fun.minesweeper'") + bot.logger.info("Loaded extension 'fun.randomfact'") + bot.logger.info("Loaded extension 'fun.rps'") diff --git a/cogs/fun/coinflip.py b/cogs/fun/coinflip.py index 0827a3a..2f68de6 100644 --- a/cogs/fun/coinflip.py +++ b/cogs/fun/coinflip.py @@ -1,7 +1,6 @@ import random import discord from discord.ext import commands -from discord.ext.commands import Context class Choice(discord.ui.View): def __init__(self) -> None: @@ -22,18 +21,12 @@ class Choice(discord.ui.View): self.value = "tails" self.stop() -class CoinFlip(commands.Cog, name="coinflip"): - def __init__(self, bot) -> None: - self.bot = bot - +def coinflip_command(): @commands.hybrid_command( - name="coinflip", description="Make a coin flip, but give your bet before." + name="coinflip", + description="Make a coin flip, but give your bet before." ) - async def coinflip(self, context: Context) -> None: - """ - Make a coin flip, but give your bet before. - :param context: The hybrid command context. - """ + async def coinflip(self, context): buttons = Choice() embed = discord.Embed( title="Coinflip", @@ -59,6 +52,5 @@ class CoinFlip(commands.Cog, name="coinflip"): ) embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp") await message.edit(embed=embed, view=None, content=None) - -async def setup(bot) -> None: - await bot.add_cog(CoinFlip(bot)) \ No newline at end of file + + return coinflip diff --git a/cogs/fun/eightball.py b/cogs/fun/eightball.py index 330ade1..0afd508 100644 --- a/cogs/fun/eightball.py +++ b/cogs/fun/eightball.py @@ -1,26 +1,12 @@ import random -import discord -from discord import app_commands from discord.ext import commands -from discord.ext.commands import Context - - -class EightBall(commands.Cog, name="8ball"): - def __init__(self, bot) -> None: - self.bot = bot +def eightball_command(): @commands.hybrid_command( name="8ball", description="Ask any question to the bot.", ) - @app_commands.describe(question="The question you want to ask.") - async def eight_ball(self, context: Context, *, question: str) -> None: - """ - Ask any question to the bot. - - :param context: The hybrid command context. - :param question: The question that should be asked by the user. - """ + async def eight_ball(self, context, *, question: str): answers = [ "It is certain.", "It is decidedly so.", @@ -51,7 +37,5 @@ class EightBall(commands.Cog, name="8ball"): embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp") embed.set_footer(text=f"The question was: {question}") await context.send(embed=embed) - - -async def setup(bot) -> None: - await bot.add_cog(EightBall(bot)) \ No newline at end of file + + return eight_ball diff --git a/cogs/fun/minesweeper.py b/cogs/fun/minesweeper.py index e81f86c..70c0de9 100644 --- a/cogs/fun/minesweeper.py +++ b/cogs/fun/minesweeper.py @@ -2,8 +2,6 @@ import random from itertools import repeat import discord from discord.ext import commands -from discord.ext.commands import Context - class RowButton(discord.ui.Button): def __init__(self, ctx, label, custom_id, bombs, board): @@ -66,7 +64,6 @@ class RowButton(discord.ui.Button): await interaction.edit_original_response(view=view) - class MsView(discord.ui.View): def __init__(self, ctx, options, bombs, board): super().__init__() @@ -155,21 +152,13 @@ class MsView(discord.ui.View): self.GetBoardPos(pos) ] = bombemo - -class Minesweeper(commands.Cog, name="minesweeper"): - def __init__(self, bot) -> None: - self.bot = bot - +def minesweeper_command(): @commands.hybrid_command( name="minesweeper", description="Play a buttoned minesweeper mini-game." ) - async def minesweeper(self, context: Context) -> None: - """ - Play a buttoned minesweeper mini-game. - :param context: The hybrid command context. - """ - board = [["឵឵ "] * 5 for _ in range(5)] # Unicode block character, usually doesnt show up in Discord or github, search up invisible character on google + async def minesweeper(self, context): + board = [["឵឵ "] * 5 for _ in range(5)] bombs = 0 bombpositions = [] for x in repeat(None, random.randint(4, 11)): @@ -197,7 +186,5 @@ class Minesweeper(commands.Cog, name="minesweeper"): view = MsView(context, ExtractBlocks(), bombpositions, board) message = await context.send(embed=embed, view=view) view.message = message - - -async def setup(bot) -> None: - await bot.add_cog(Minesweeper(bot)) + + return minesweeper diff --git a/cogs/fun/randomfact.py b/cogs/fun/randomfact.py index fb211ca..013b389 100644 --- a/cogs/fun/randomfact.py +++ b/cogs/fun/randomfact.py @@ -1,15 +1,10 @@ import aiohttp import discord from discord.ext import commands -from discord.ext.commands import Context - - -class RandomFact(commands.Cog, name="randomfact"): - def __init__(self, bot) -> None: - self.bot = bot +def randomfact_command(): @commands.hybrid_command(name="randomfact", description="Get a random fact.") - async def randomfact(self, context: Context) -> None: + async def randomfact(self, context): async with aiohttp.ClientSession() as session: async with session.get( "https://uselessfacts.jsph.pl/random.json?language=en" @@ -30,7 +25,5 @@ class RandomFact(commands.Cog, name="randomfact"): ) embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp") await context.send(embed=embed) - - -async def setup(bot) -> None: - await bot.add_cog(RandomFact(bot)) \ No newline at end of file + + return randomfact diff --git a/cogs/fun/rockpaperscissors.py b/cogs/fun/rockpaperscissors.py index 9d369b5..b9bb943 100644 --- a/cogs/fun/rockpaperscissors.py +++ b/cogs/fun/rockpaperscissors.py @@ -1,7 +1,6 @@ import random import discord from discord.ext import commands -from discord.ext.commands import Context class RockPaperScissors(discord.ui.Select): def __init__(self) -> None: @@ -39,7 +38,6 @@ class RockPaperScissors(discord.ui.Select): winner = (3 + user_choice_index - bot_choice_index) % 3 - # Get the user mention user_mention = interaction.user.mention if winner == 0: @@ -61,14 +59,11 @@ class RockPaperScissorsView(discord.ui.View): super().__init__() self.add_item(RockPaperScissors()) -class RPS(commands.Cog, name="rps"): - def __init__(self, bot) -> None: - self.bot = bot - +def rps_command(): @commands.hybrid_command( name="rps", description="Play the rock paper scissors game against the bot." ) - async def rock_paper_scissors(self, context: Context) -> None: + async def rock_paper_scissors(self, context): view = RockPaperScissorsView() embed = discord.Embed( title="Rock Paper Scissors", @@ -77,6 +72,5 @@ class RPS(commands.Cog, name="rps"): ) embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp") await context.send(embed=embed, view=view) - -async def setup(bot) -> None: - await bot.add_cog(RPS(bot)) \ No newline at end of file + + return rock_paper_scissors diff --git a/cogs/general/help.py b/cogs/general/help.py index 536462f..1a687d0 100644 --- a/cogs/general/help.py +++ b/cogs/general/help.py @@ -47,11 +47,7 @@ class Help(commands.Cog, name="help"): # "context_menus": "general", # Fun Commands - "randomfact": "fun", - "coinflip": "fun", - "rps": "fun", - "8ball": "fun", - "minesweeper": "fun", + "fun": "fun", # Moderation Commands "kick": "moderation", @@ -183,6 +179,14 @@ class Help(commands.Cog, name="help"): description = app_command.description.partition("\n")[0] if getattr(app_command, "description", None) else "No description available" commands_in_category.append((app_command.name, description)) seen_names.add(app_command.name) + + if hasattr(app_command, 'commands') and category == "fun": + for subcommand in app_command.commands: + if subcommand.name in seen_names: + continue + sub_desc = subcommand.description.partition("\n")[0] if getattr(subcommand, "description", None) else "No description available" + commands_in_category.append((f"{app_command.name} {subcommand.name}", sub_desc)) + seen_names.add(f"{app_command.name} {subcommand.name}") if not commands_in_category: embed = discord.Embed( From 72cdd9b403b9cb173379af2a11ae15fda0d7f845 Mon Sep 17 00:00:00 2001 From: neoarz Date: Sun, 28 Sep 2025 22:53:25 -0400 Subject: [PATCH 02/15] refactor(general): commands into GroupCog Migrated general commands (ping, uptime, botinfo, serverinfo, feedback) into a single GroupCog in cogs/general/__init__.py for better organization and maintainability. Converted individual command files to export command functions instead of Cogs. Updated bot.py to load the new general extension. Renamed help.py for consistency. --- bot.py | 2 +- cogs/general/__init__.py | 59 ++++++++++++++++++++++++++++++++++++++ cogs/general/botinfo.py | 19 +++--------- cogs/general/feedback.py | 20 +++---------- cogs/general/ping.py | 20 +++---------- cogs/general/serverinfo.py | 20 ++++--------- cogs/general/uptime.py | 21 +++----------- cogs/{general => }/help.py | 0 8 files changed, 81 insertions(+), 80 deletions(-) create mode 100644 cogs/general/__init__.py rename cogs/{general => }/help.py (100%) diff --git a/bot.py b/bot.py index 9f1f3d5..fb9603e 100644 --- a/bot.py +++ b/bot.py @@ -90,7 +90,7 @@ class DiscordBot(commands.Bot): if os.path.exists(init_file): try: await self.load_extension(f"cogs.{folder}") - if folder != "fun": + if folder not in ["fun", "general"]: self.logger.info(f"Loaded extension '{folder}'") except Exception as e: exception = f"{type(e).__name__}: {e}" diff --git a/cogs/general/__init__.py b/cogs/general/__init__.py new file mode 100644 index 0000000..22716d0 --- /dev/null +++ b/cogs/general/__init__.py @@ -0,0 +1,59 @@ +import discord +from discord.ext import commands +from discord.ext.commands import Context + +from .ping import ping_command +from .uptime import uptime_command +from .botinfo import botinfo_command +from .serverinfo import serverinfo_command +from .feedback import feedback_command + +class General(commands.GroupCog, name="general"): + def __init__(self, bot) -> None: + self.bot = bot + super().__init__() + + @commands.hybrid_command( + name="ping", + description="Check if the bot is alive.", + ) + async def ping(self, context): + return await ping_command()(self, context) + + @commands.hybrid_command( + name="uptime", + description="Check how long the bot has been running.", + ) + async def uptime(self, context): + return await uptime_command()(self, context) + + @commands.hybrid_command( + name="botinfo", + description="Get some useful (or not) information about the bot.", + ) + async def botinfo(self, context): + return await botinfo_command()(self, context) + + @commands.hybrid_command( + name="serverinfo", + description="Get some useful (or not) information about the server.", + ) + async def serverinfo(self, context): + return await serverinfo_command()(self, context) + + @commands.hybrid_command( + name="feedback", + description="Submit a feedback for the owners of the bot" + ) + async def feedback(self, context): + return await feedback_command()(self, context) + +async def setup(bot) -> None: + cog = General(bot) + await bot.add_cog(cog) + + bot.logger.info("Loaded extension 'general.ping'") + bot.logger.info("Loaded extension 'general.uptime'") + bot.logger.info("Loaded extension 'general.botinfo'") + bot.logger.info("Loaded extension 'general.serverinfo'") + bot.logger.info("Loaded extension 'general.feedback'") diff --git a/cogs/general/botinfo.py b/cogs/general/botinfo.py index 4f49368..0cb2fd5 100644 --- a/cogs/general/botinfo.py +++ b/cogs/general/botinfo.py @@ -1,23 +1,13 @@ import platform import discord from discord.ext import commands -from discord.ext.commands import Context - - -class BotInfo(commands.Cog, name="botinfo"): - def __init__(self, bot) -> None: - self.bot = bot +def botinfo_command(): @commands.hybrid_command( name="botinfo", description="Get some useful (or not) information about the bot.", ) - async def botinfo(self, context: Context) -> None: - """ - Get some useful (or not) information about the bot. - - :param context: The hybrid command context. - """ + async def botinfo(self, context): embed = discord.Embed( title="Syntrel Discord Bot", color=0x7289DA, @@ -36,6 +26,5 @@ class BotInfo(commands.Cog, name="botinfo"): await context.interaction.response.send_message(embed=embed, ephemeral=True) else: await context.send(embed=embed) - -async def setup(bot) -> None: - await bot.add_cog(BotInfo(bot)) \ No newline at end of file + + return botinfo \ No newline at end of file diff --git a/cogs/general/feedback.py b/cogs/general/feedback.py index 2a31971..98b6b7f 100644 --- a/cogs/general/feedback.py +++ b/cogs/general/feedback.py @@ -2,7 +2,6 @@ import discord from discord import app_commands from discord.ext import commands - class FeedbackForm(discord.ui.Modal, title="Feeedback"): feedback = discord.ui.TextInput( label="What do you think about this bot?", @@ -17,20 +16,11 @@ class FeedbackForm(discord.ui.Modal, title="Feeedback"): self.answer = str(self.feedback) self.stop() - -class Feedback(commands.Cog, name="feedback"): - def __init__(self, bot) -> None: - self.bot = bot - +def feedback_command(): @app_commands.command( name="feedback", description="Submit a feedback for the owners of the bot" ) - async def feedback(self, interaction: discord.Interaction) -> None: - """ - Submit a feedback for the owners of the bot. - - :param interaction: The application command interaction. - """ + async def feedback(self, interaction: discord.Interaction): feedback_form = FeedbackForm() await interaction.response.send_modal(feedback_form) @@ -53,7 +43,5 @@ class Feedback(commands.Cog, name="feedback"): color=0x7289DA, ).set_author(name="Feedback System", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") ) - - -async def setup(bot) -> None: - await bot.add_cog(Feedback(bot)) \ No newline at end of file + + return feedback \ No newline at end of file diff --git a/cogs/general/ping.py b/cogs/general/ping.py index 0d97c7b..7d3ec13 100644 --- a/cogs/general/ping.py +++ b/cogs/general/ping.py @@ -1,22 +1,12 @@ import discord from discord.ext import commands -from discord.ext.commands import Context - - -class Ping(commands.Cog, name="ping"): - def __init__(self, bot) -> None: - self.bot = bot +def ping_command(): @commands.hybrid_command( name="ping", description="Check if the bot is alive.", ) - async def ping(self, context: Context) -> None: - """ - Check if the bot is alive. - - :param context: The hybrid command context. - """ + async def ping(self, context): embed = discord.Embed( title="🏓 Pong!", description=f"The bot latency is {round(self.bot.latency * 1000)}ms.", @@ -31,7 +21,5 @@ class Ping(commands.Cog, name="ping"): await inter.followup.send(embed=embed, ephemeral=True) else: await context.send(embed=embed) - - -async def setup(bot) -> None: - await bot.add_cog(Ping(bot)) + + return ping diff --git a/cogs/general/serverinfo.py b/cogs/general/serverinfo.py index f0cf8a6..1d30497 100644 --- a/cogs/general/serverinfo.py +++ b/cogs/general/serverinfo.py @@ -1,22 +1,13 @@ import discord from discord.ext import commands -from discord.ext.commands import Context - -class ServerInfo(commands.Cog, name="serverinfo"): - def __init__(self, bot) -> None: - self.bot = bot +def serverinfo_command(): @commands.hybrid_command( name="serverinfo", description="Get some useful (or not) information about the server.", ) - @commands.guild_only() # This decorator ensures the command only works in servers - async def serverinfo(self, context: Context) -> None: - """ - Get some useful (or not) information about the server. - :param context: The hybrid command context. - """ - # Additional check (though @commands.guild_only() should handle this) + @commands.guild_only() + async def serverinfo(self, context): if context.guild is None: await context.send("This command can only be used in a server, not in DMs!") return @@ -53,6 +44,5 @@ class ServerInfo(commands.Cog, name="serverinfo"): await context.interaction.response.send_message(embed=embed, ephemeral=True) else: await context.send(embed=embed) - -async def setup(bot) -> None: - await bot.add_cog(ServerInfo(bot)) \ No newline at end of file + + return serverinfo \ No newline at end of file diff --git a/cogs/general/uptime.py b/cogs/general/uptime.py index 654bd3b..b3aee27 100644 --- a/cogs/general/uptime.py +++ b/cogs/general/uptime.py @@ -1,7 +1,5 @@ import discord from discord.ext import commands -from discord.ext.commands import Context - class UptimeView(discord.ui.View): def __init__(self, bot): @@ -18,21 +16,12 @@ class UptimeView(discord.ui.View): embed.set_author(name="Uptime", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") await interaction.response.edit_message(embed=embed, view=self) - -class Uptime(commands.Cog, name="uptime"): - def __init__(self, bot) -> None: - self.bot = bot - +def uptime_command(): @commands.hybrid_command( name="uptime", description="Check how long the bot has been running.", ) - async def uptime(self, context: Context) -> None: - """ - Check how long the bot has been running. - - :param context: The hybrid command context. - """ + async def uptime(self, context): embed = discord.Embed( title="Bot Uptime", description=f"The bot has been running for **{self.bot.get_uptime()}**", @@ -48,7 +37,5 @@ class Uptime(commands.Cog, name="uptime"): await inter.followup.send(embed=embed, view=view, ephemeral=True) else: await context.send(embed=embed, view=view) - - -async def setup(bot) -> None: - await bot.add_cog(Uptime(bot)) + + return uptime diff --git a/cogs/general/help.py b/cogs/help.py similarity index 100% rename from cogs/general/help.py rename to cogs/help.py From 51393ece856508e8fb578e2c838e915f1f13fe16 Mon Sep 17 00:00:00 2001 From: neoarz Date: Sun, 28 Sep 2025 22:57:26 -0400 Subject: [PATCH 03/15] refactor(idevice): commands into a single GroupCog Merged idevice-related commands into a unified GroupCog in cogs/idevice/__init__.py, replacing individual Cog classes with command factory functions. Updated bot.py and help.py to support the new structure and improved command categorization. This refactor simplifies extension loading and command management for idevice troubleshooting features. --- bot.py | 2 +- cogs/help.py | 22 ++++-------- cogs/idevice/__init__.py | 59 +++++++++++++++++++++++++++++++ cogs/idevice/developermode.py | 13 +++---- cogs/idevice/error_codes.py | 65 ++++++++++++++--------------------- cogs/idevice/idevice.py | 14 +++----- cogs/idevice/mountddi.py | 20 ++++------- cogs/idevice/noapps.py | 13 +++---- 8 files changed, 110 insertions(+), 98 deletions(-) create mode 100644 cogs/idevice/__init__.py diff --git a/bot.py b/bot.py index fb9603e..54754d3 100644 --- a/bot.py +++ b/bot.py @@ -90,7 +90,7 @@ class DiscordBot(commands.Bot): if os.path.exists(init_file): try: await self.load_extension(f"cogs.{folder}") - if folder not in ["fun", "general"]: + if folder not in ["fun", "general", "idevice"]: self.logger.info(f"Loaded extension '{folder}'") except Exception as e: exception = f"{type(e).__name__}: {e}" diff --git a/cogs/help.py b/cogs/help.py index 1a687d0..ae4b4b2 100644 --- a/cogs/help.py +++ b/cogs/help.py @@ -38,17 +38,14 @@ class Help(commands.Cog, name="help"): category_mapping = { # General Commands - "help": "general", - "botinfo": "general", - "serverinfo": "general", - "ping": "general", - "feedback": "general", - "uptime": "general", - # "context_menus": "general", - + "general": "general", + # Fun Commands "fun": "fun", + # idevice Commands + "idevice": "idevice", + # Moderation Commands "kick": "moderation", "ban": "moderation", @@ -70,13 +67,6 @@ class Help(commands.Cog, name="help"): "afc": "sidestore", "udid": "sidestore", - # idevice Commands - "idevice": "idevice", - "noapps": "idevice", - "errorcodes": "idevice", - "developermode": "idevice", - "mountddi": "idevice", - # Owner Commands "sync": "owner", "cog_management": "owner", @@ -180,7 +170,7 @@ class Help(commands.Cog, name="help"): commands_in_category.append((app_command.name, description)) seen_names.add(app_command.name) - if hasattr(app_command, 'commands') and category == "fun": + if hasattr(app_command, 'commands') and category in ["fun", "general", "idevice"]: for subcommand in app_command.commands: if subcommand.name in seen_names: continue diff --git a/cogs/idevice/__init__.py b/cogs/idevice/__init__.py new file mode 100644 index 0000000..3bdda61 --- /dev/null +++ b/cogs/idevice/__init__.py @@ -0,0 +1,59 @@ +import discord +from discord.ext import commands +from discord.ext.commands import Context + +from .idevice import idevice_command +from .error_codes import errorcodes_command +from .developermode import developermode_command +from .noapps import noapps_command +from .mountddi import mountddi_command + +class Idevice(commands.GroupCog, name="idevice"): + def __init__(self, bot) -> None: + self.bot = bot + super().__init__() + + @commands.hybrid_command( + name="idevice", + description="Get help with idevice commands and troubleshooting." + ) + async def idevice(self, context): + return await idevice_command()(self, context) + + @commands.hybrid_command( + name="errorcodes", + description="Look up error codes and their meanings." + ) + async def errorcodes(self, context, *, error_code: str = None): + return await errorcodes_command()(self, context, error_code=error_code) + + @commands.hybrid_command( + name="developermode", + description="How to turn on developer mode" + ) + async def developermode(self, context): + return await developermode_command()(self, context) + + @commands.hybrid_command( + name="noapps", + description="Help when apps aren't showing in installed apps view" + ) + async def noapps(self, context): + return await noapps_command()(self, context) + + @commands.hybrid_command( + name="mountddi", + description="How to manually mount DDI" + ) + async def mountddi(self, context): + return await mountddi_command()(self, context) + +async def setup(bot) -> None: + cog = Idevice(bot) + await bot.add_cog(cog) + + bot.logger.info("Loaded extension 'idevice.idevice'") + bot.logger.info("Loaded extension 'idevice.errorcodes'") + bot.logger.info("Loaded extension 'idevice.developermode'") + bot.logger.info("Loaded extension 'idevice.noapps'") + bot.logger.info("Loaded extension 'idevice.mountddi'") diff --git a/cogs/idevice/developermode.py b/cogs/idevice/developermode.py index 1be3ba7..aad0595 100644 --- a/cogs/idevice/developermode.py +++ b/cogs/idevice/developermode.py @@ -5,14 +5,11 @@ from discord.ext.commands import Context import time -class Developermode(commands.Cog, name="developermode"): - def __init__(self, bot) -> None: - self.bot = bot - +def developermode_command(): @commands.hybrid_command( name="developermode", description="How to turn on developer mode" ) - async def developermode(self, context: Context) -> None: + async def developermode(self, context): embed = discord.Embed( color=0xfa8c4a, description=( @@ -40,7 +37,5 @@ class Developermode(commands.Cog, name="developermode"): await context.interaction.response.send_message(embed=embed, view=view) else: await context.send(embed=embed, view=view) - - -async def setup(bot) -> None: - await bot.add_cog(Developermode(bot)) + + return developermode diff --git a/cogs/idevice/error_codes.py b/cogs/idevice/error_codes.py index cc616f2..0a1560f 100644 --- a/cogs/idevice/error_codes.py +++ b/cogs/idevice/error_codes.py @@ -6,54 +6,41 @@ from discord.ext import commands from discord.ext.commands import Context -class ErrorCodes(commands.Cog, name="errorcodes"): - def __init__(self, bot) -> None: - self.bot = bot - self.errors = self.load_errors() - self.key_to_data = {error['name']: (error['description'], error['code']) for error in self.errors} - self.code_to_key = {error['code']: error['name'] for error in self.errors} - - def load_errors(self): - json_path = os.path.join(os.path.dirname(__file__), 'files/errorcodes.json') - try: - with open(json_path, 'r', encoding='utf-8') as f: - return json.load(f) - except FileNotFoundError: - self.bot.logger.error(f"Error codes JSON file not found: {json_path}") - return [] - except json.JSONDecodeError as e: - self.bot.logger.error(f"Error parsing error codes JSON: {e}") - return [] - - async def errorcode_autocomplete(self, interaction: discord.Interaction, current: str): - current_lower = current.lower() - items = [] - for key, (title, code) in self.key_to_data.items(): - if not current or current_lower in key.lower() or current_lower in title.lower() or current_lower in str(code): - items.append(app_commands.Choice(name=f"{key} » {title} ({code})", value=key)) - if len(items) >= 25: - break - return items - - @commands.hybrid_command(name="errorcode", description="Look up an idevice error code by name or number") +def errorcodes_command(): + @commands.hybrid_command(name="errorcodes", description="Look up an idevice error code by name or number") @app_commands.describe(name="Start typing to search all error names and codes") - @app_commands.autocomplete(name=errorcode_autocomplete) - async def errorcode(self, context: Context, name: str): + async def errorcodes(self, context, name: str): + def load_errors(): + json_path = os.path.join(os.path.dirname(__file__), 'files/errorcodes.json') + try: + with open(json_path, 'r', encoding='utf-8') as f: + return json.load(f) + except FileNotFoundError: + self.bot.logger.error(f"Error codes JSON file not found: {json_path}") + return [] + except json.JSONDecodeError as e: + self.bot.logger.error(f"Error parsing error codes JSON: {e}") + return [] + + errors = load_errors() + key_to_data = {error['name']: (error['description'], error['code']) for error in errors} + code_to_key = {error['code']: error['name'] for error in errors} + key = name - if key not in self.key_to_data: + if key not in key_to_data: try: num = int(name) - key = self.code_to_key.get(num) + key = code_to_key.get(num) except ValueError: key = None - if key is None or key not in self.key_to_data: + if key is None or key not in key_to_data: if context.interaction: await context.interaction.response.send_message("Error not found.", ephemeral=True) else: await context.send("Error not found.") return - title, code = self.key_to_data[key] + title, code = key_to_data[key] embed = discord.Embed( description=f"## Error Code: {code}\n\n**Name**: `{key}`\n**Description**: {title}", @@ -73,9 +60,7 @@ class ErrorCodes(commands.Cog, name="errorcodes"): await context.interaction.response.send_message(embed=embed, view=view) else: await context.send(embed=embed, view=view) - -async def setup(bot) -> None: - cog = ErrorCodes(bot) - await bot.add_cog(cog) + + return errorcodes diff --git a/cogs/idevice/idevice.py b/cogs/idevice/idevice.py index 75c8390..44caa79 100644 --- a/cogs/idevice/idevice.py +++ b/cogs/idevice/idevice.py @@ -253,14 +253,11 @@ class ideviceView(discord.ui.View): self.add_item(ideviceSelect(bot)) -class idevice(commands.Cog, name="idevice"): - def __init__(self, bot) -> None: - self.bot = bot - +def idevice_command(): @commands.hybrid_command( name="idevice", description="idevice troubleshooting and help" ) - async def idevice(self, context: Context) -> None: + async def idevice(self, context): embed = discord.Embed( title="idevice Commands", description="Choose a command from the dropdown below to get help with specific issues:", @@ -274,8 +271,5 @@ class idevice(commands.Cog, name="idevice"): await context.interaction.response.send_message(embed=embed, view=view, ephemeral=True) else: await context.send(embed=embed, view=view) - - - -async def setup(bot) -> None: - await bot.add_cog(idevice(bot)) \ No newline at end of file + + return idevice \ No newline at end of file diff --git a/cogs/idevice/mountddi.py b/cogs/idevice/mountddi.py index adebf13..505b808 100644 --- a/cogs/idevice/mountddi.py +++ b/cogs/idevice/mountddi.py @@ -4,16 +4,13 @@ from discord.ext.commands import Context import os -class Mountddi(commands.Cog, name="mountddi"): - def __init__(self, bot) -> None: - self.bot = bot - +def mountddi_command(): @commands.hybrid_command( name="mountddi", description="How to manually mount DDI" ) - async def mountddi(self, ctx: Context) -> None: - await ctx.defer() + async def mountddi(self, context): + await context.defer() embed = discord.Embed( color=0xfa8c4a, @@ -46,15 +43,12 @@ class Mountddi(commands.Cog, name="mountddi"): emoji="<:githubicon:1417717356846776340>" )) - ddi_file_path = os.path.join(os.path.dirname(__file__), 'files/DDI.zip') file = discord.File(ddi_file_path, filename='DDI.zip') if os.path.exists(ddi_file_path) else None if file: - await ctx.send(embed=embed, view=view, file=file) + await context.send(embed=embed, view=view, file=file) else: - await ctx.send(embed=embed, view=view) - - -async def setup(bot) -> None: - await bot.add_cog(Mountddi(bot)) + await context.send(embed=embed, view=view) + + return mountddi diff --git a/cogs/idevice/noapps.py b/cogs/idevice/noapps.py index 2e54b72..386c27b 100644 --- a/cogs/idevice/noapps.py +++ b/cogs/idevice/noapps.py @@ -5,14 +5,11 @@ from discord.ext.commands import Context import time -class Noapps(commands.Cog, name="noapps"): - def __init__(self, bot) -> None: - self.bot = bot - +def noapps_command(): @commands.hybrid_command( name="noapps", description="Help when apps aren't showing in installed apps view" ) - async def noapps(self, context: Context) -> None: + async def noapps(self, context): embed = discord.Embed( color=0xfa8c4a, description=( @@ -41,7 +38,5 @@ class Noapps(commands.Cog, name="noapps"): await context.interaction.response.send_message(embed=embed, view=view) else: await context.send(embed=embed, view=view) - - -async def setup(bot) -> None: - await bot.add_cog(Noapps(bot)) + + return noapps From e5ea7f1dac048590777e36edbf27abda1d1383b9 Mon Sep 17 00:00:00 2001 From: neoarz Date: Sun, 28 Sep 2025 23:07:46 -0400 Subject: [PATCH 04/15] refactor(moderation): commands into group cog Converted individual moderation command cogs (ban, kick, purge, warnings, archive, hackban, nick) into command factory functions and registered them under a new Moderation GroupCog in cogs/moderation/__init__.py. Updated bot.py and help.py to support the new moderation group and category. This improves organization and enables grouped moderation commands. --- bot.py | 2 +- cogs/help.py | 8 +--- cogs/moderation/__init__.py | 77 +++++++++++++++++++++++++++++++++++++ cogs/moderation/archive.py | 13 ++----- cogs/moderation/ban.py | 15 +++----- cogs/moderation/hackban.py | 15 +++----- cogs/moderation/kick.py | 15 +++----- cogs/moderation/nick.py | 15 +++----- cogs/moderation/purge.py | 19 ++------- cogs/moderation/warnings.py | 12 ++---- 10 files changed, 112 insertions(+), 79 deletions(-) create mode 100644 cogs/moderation/__init__.py diff --git a/bot.py b/bot.py index 54754d3..6c7e635 100644 --- a/bot.py +++ b/bot.py @@ -90,7 +90,7 @@ class DiscordBot(commands.Bot): if os.path.exists(init_file): try: await self.load_extension(f"cogs.{folder}") - if folder not in ["fun", "general", "idevice"]: + if folder not in ["fun", "general", "idevice", "miscellaneous", "moderation"]: self.logger.info(f"Loaded extension '{folder}'") except Exception as e: exception = f"{type(e).__name__}: {e}" diff --git a/cogs/help.py b/cogs/help.py index ae4b4b2..50941a3 100644 --- a/cogs/help.py +++ b/cogs/help.py @@ -79,11 +79,7 @@ class Help(commands.Cog, name="help"): "translate": "utilities", # Miscellaneous Commands - "keanu": "miscellaneous", - "labubu": "miscellaneous", - "piracy": "miscellaneous", - "tryitandsee": "miscellaneous", - "rr": "miscellaneous", + "miscellaneous": "miscellaneous", } category_descriptions = { @@ -170,7 +166,7 @@ class Help(commands.Cog, name="help"): commands_in_category.append((app_command.name, description)) seen_names.add(app_command.name) - if hasattr(app_command, 'commands') and category in ["fun", "general", "idevice"]: + if hasattr(app_command, 'commands') and category in ["fun", "general", "idevice", "miscellaneous", "moderation"]: for subcommand in app_command.commands: if subcommand.name in seen_names: continue diff --git a/cogs/moderation/__init__.py b/cogs/moderation/__init__.py new file mode 100644 index 0000000..0553ba1 --- /dev/null +++ b/cogs/moderation/__init__.py @@ -0,0 +1,77 @@ +import discord +from discord.ext import commands +from discord.ext.commands import Context + +from .ban import ban_command +from .kick import kick_command +from .purge import purge_command +from .warnings import warnings_command +from .archive import archive_command +from .hackban import hackban_command +from .nick import nick_command + +class Moderation(commands.GroupCog, name="moderation"): + def __init__(self, bot) -> None: + self.bot = bot + super().__init__() + + @commands.hybrid_command( + name="ban", + description="Bans a user from the server." + ) + 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.hybrid_command( + name="kick", + description="Kicks a user from the server." + ) + async def kick(self, context, user: discord.User, *, reason: str = "Not specified"): + return await kick_command()(self, context, user=user, reason=reason) + + @commands.hybrid_command( + name="purge", + description="Delete a number of messages." + ) + async def purge(self, context, amount: int): + return await purge_command()(self, context, amount=amount) + + @commands.hybrid_command( + name="warnings", + description="Manage warnings of a user on a server." + ) + async def warnings(self, context): + return await warnings_command()(self, context) + + @commands.hybrid_command( + name="archive", + description="Archives in a text file the last messages with a chosen limit of messages." + ) + async def archive(self, context, limit: int = 10): + return await archive_command()(self, context, limit=limit) + + @commands.hybrid_command( + name="hackban", + description="Bans a user without the user having to be in the server." + ) + 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.hybrid_command( + name="nick", + description="Change the nickname of a user on a server." + ) + async def nick(self, context, user: discord.User, *, nickname: str = None): + return await nick_command()(self, context, user=user, nickname=nickname) + +async def setup(bot) -> None: + cog = Moderation(bot) + await bot.add_cog(cog) + + bot.logger.info("Loaded extension 'moderation.ban'") + bot.logger.info("Loaded extension 'moderation.kick'") + bot.logger.info("Loaded extension 'moderation.purge'") + bot.logger.info("Loaded extension 'moderation.warnings'") + bot.logger.info("Loaded extension 'moderation.archive'") + bot.logger.info("Loaded extension 'moderation.hackban'") + bot.logger.info("Loaded extension 'moderation.nick'") diff --git a/cogs/moderation/archive.py b/cogs/moderation/archive.py index 5430086..3c0617d 100644 --- a/cogs/moderation/archive.py +++ b/cogs/moderation/archive.py @@ -6,10 +6,7 @@ from discord.ext import commands from discord.ext.commands import Context -class Archive(commands.Cog, name="archive"): - def __init__(self, bot) -> None: - self.bot = bot - +def archive_command(): @commands.hybrid_command( name="archive", description="Archives in a text file the last messages with a chosen limit of messages.", @@ -18,7 +15,7 @@ class Archive(commands.Cog, name="archive"): @app_commands.describe( limit="The limit of messages that should be archived.", ) - async def archive(self, context: Context, limit: int = 10) -> None: + async def archive(self, context, limit: int = 10): """ Archives in a text file the last messages with a chosen limit of messages. This command requires the MESSAGE_CONTENT intent to work properly. @@ -54,7 +51,5 @@ class Archive(commands.Cog, name="archive"): f = discord.File(log_file) await context.send(file=f) os.remove(log_file) - - -async def setup(bot) -> None: - await bot.add_cog(Archive(bot)) \ No newline at end of file + + return archive \ No newline at end of file diff --git a/cogs/moderation/ban.py b/cogs/moderation/ban.py index fb3405f..d5ef222 100644 --- a/cogs/moderation/ban.py +++ b/cogs/moderation/ban.py @@ -4,10 +4,7 @@ from discord.ext import commands from discord.ext.commands import Context -class Ban(commands.Cog, name="ban"): - def __init__(self, bot) -> None: - self.bot = bot - +def ban_command(): @commands.hybrid_command( name="ban", description="Bans a user from the server.", @@ -27,8 +24,8 @@ class Ban(commands.Cog, name="ban"): app_commands.Choice(name="Last 7 days", value="7d"), ]) async def ban( - self, context: Context, user: discord.User, *, reason: str = "Not specified", delete_messages: str = "none" - ) -> None: + self, context, user: discord.User, *, reason: str = "Not specified", delete_messages: str = "none" + ): try: member = context.guild.get_member(user.id) if not member: @@ -211,7 +208,5 @@ class Ban(commands.Cog, name="ban"): "7d": "Last 7 days" } return time_formats.get(delete_option, "Unknown time period") - - -async def setup(bot) -> None: - await bot.add_cog(Ban(bot)) \ No newline at end of file + + return ban \ No newline at end of file diff --git a/cogs/moderation/hackban.py b/cogs/moderation/hackban.py index 4109db8..009e260 100644 --- a/cogs/moderation/hackban.py +++ b/cogs/moderation/hackban.py @@ -4,10 +4,7 @@ from discord.ext import commands from discord.ext.commands import Context -class HackBan(commands.Cog, name="hackban"): - def __init__(self, bot) -> None: - self.bot = bot - +def hackban_command(): @commands.hybrid_command( name="hackban", description="Bans a user without the user having to be in the server.", @@ -19,8 +16,8 @@ class HackBan(commands.Cog, name="hackban"): reason="The reason why the user should be banned.", ) async def hackban( - self, context: Context, user_id: str, *, reason: str = "Not specified" - ) -> None: + self, context, user_id: str, *, reason: str = "Not specified" + ): """ Bans a user without the user having to be in the server. @@ -47,7 +44,5 @@ class HackBan(commands.Cog, name="hackban"): color=0xE02B2B, ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") await context.send(embed=embed) - - -async def setup(bot) -> None: - await bot.add_cog(HackBan(bot)) + + return hackban diff --git a/cogs/moderation/kick.py b/cogs/moderation/kick.py index 2bdb2a5..6c91ed8 100644 --- a/cogs/moderation/kick.py +++ b/cogs/moderation/kick.py @@ -4,10 +4,7 @@ from discord.ext import commands from discord.ext.commands import Context -class Kick(commands.Cog, name="kick"): - def __init__(self, bot) -> None: - self.bot = bot - +def kick_command(): @commands.hybrid_command( name="kick", description="Kicks a user from the server.", @@ -17,8 +14,8 @@ class Kick(commands.Cog, name="kick"): reason="The reason why the user should be kicked.", ) async def kick( - self, context: Context, user: discord.User, *, reason: str = "Not specified" - ) -> None: + self, context, user: discord.User, *, reason: str = "Not specified" + ): try: member = context.guild.get_member(user.id) if not member: @@ -122,7 +119,5 @@ class Kick(commands.Cog, name="kick"): color=0xE02B2B, ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") await context.send(embed=embed, ephemeral=True) - - -async def setup(bot) -> None: - await bot.add_cog(Kick(bot)) + + return kick diff --git a/cogs/moderation/nick.py b/cogs/moderation/nick.py index 3eb7565..489a75d 100644 --- a/cogs/moderation/nick.py +++ b/cogs/moderation/nick.py @@ -4,10 +4,7 @@ from discord.ext import commands from discord.ext.commands import Context -class Nick(commands.Cog, name="nick"): - def __init__(self, bot) -> None: - self.bot = bot - +def nick_command(): @commands.hybrid_command( name="nick", description="Change the nickname of a user on a server.", @@ -17,8 +14,8 @@ class Nick(commands.Cog, name="nick"): nickname="The new nickname that should be set.", ) async def nick( - self, context: Context, user: discord.User, *, nickname: str = None - ) -> None: + self, context, user: discord.User, *, nickname: str = None + ): """ Change the nickname of a user on a server. @@ -60,7 +57,5 @@ class Nick(commands.Cog, name="nick"): color=0xE02B2B, ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") await context.send(embed=embed, ephemeral=True) - - -async def setup(bot) -> None: - await bot.add_cog(Nick(bot)) + + return nick diff --git a/cogs/moderation/purge.py b/cogs/moderation/purge.py index c56e73e..a18b185 100644 --- a/cogs/moderation/purge.py +++ b/cogs/moderation/purge.py @@ -4,10 +4,7 @@ from discord.ext import commands from discord.ext.commands import Context -class Purge(commands.Cog, name="purge"): - def __init__(self, bot) -> None: - self.bot = bot - +def purge_command(): @commands.hybrid_command( name="purge", description="Delete a number of messages.", @@ -15,13 +12,7 @@ class Purge(commands.Cog, name="purge"): @commands.has_guild_permissions(manage_messages=True) @commands.bot_has_permissions(manage_messages=True) @app_commands.describe(amount="The amount of messages that should be deleted.") - async def purge(self, context: Context, amount: int) -> None: - """ - Delete a number of messages. - - :param context: The hybrid command context. - :param amount: The number of messages that should be deleted. - """ + async def purge(self, context, amount: int): await context.send("Deleting messages...") purged_messages = await context.channel.purge(limit=amount + 1) embed = discord.Embed( @@ -31,7 +22,5 @@ class Purge(commands.Cog, name="purge"): ) embed.set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") await context.channel.send(embed=embed) - - -async def setup(bot) -> None: - await bot.add_cog(Purge(bot)) + + return purge diff --git a/cogs/moderation/warnings.py b/cogs/moderation/warnings.py index dc8f4bf..c803185 100644 --- a/cogs/moderation/warnings.py +++ b/cogs/moderation/warnings.py @@ -4,11 +4,8 @@ from discord.ext import commands from discord.ext.commands import Context -class Warnings(commands.Cog, name="warnings"): - def __init__(self, bot) -> None: - self.bot = bot - - async def send_embed(self, context: Context, embed: discord.Embed, *, ephemeral: bool = False) -> None: +def warnings_command(): + async def send_embed(context, embed: discord.Embed, *, ephemeral: bool = False) -> None: interaction = getattr(context, "interaction", None) if interaction is not None: if interaction.response.is_done(): @@ -22,7 +19,7 @@ class Warnings(commands.Cog, name="warnings"): name="warning", description="Manage warnings of a user on a server.", ) - async def warning(self, context: Context) -> None: + async def warning(self, context) -> None: """ Manage warnings of a user on a server. @@ -170,5 +167,4 @@ class Warnings(commands.Cog, name="warnings"): -async def setup(bot) -> None: - await bot.add_cog(Warnings(bot)) + return warning From 5e2999e5652d0abb376ca0780e07210fcc3d1bae Mon Sep 17 00:00:00 2001 From: neoarz Date: Sun, 28 Sep 2025 23:08:07 -0400 Subject: [PATCH 05/15] refactor(miscellaneous): cogs to command functions Converted individual miscellaneous cogs (keanu, labubu, piracy, rickroll, tryitandsee) from class-based to function-based command definitions. Added a new __init__.py to group these commands under a single Miscellaneous GroupCog for easier management and setup. --- cogs/miscellaneous/__init__.py | 59 +++++++++++++++++++++++++++++++ cogs/miscellaneous/keanu.py | 13 +++---- cogs/miscellaneous/labubu.py | 13 +++---- cogs/miscellaneous/piracy.py | 13 +++---- cogs/miscellaneous/rickroll.py | 13 +++---- cogs/miscellaneous/tryitandsee.py | 13 +++---- 6 files changed, 79 insertions(+), 45 deletions(-) create mode 100644 cogs/miscellaneous/__init__.py diff --git a/cogs/miscellaneous/__init__.py b/cogs/miscellaneous/__init__.py new file mode 100644 index 0000000..216489d --- /dev/null +++ b/cogs/miscellaneous/__init__.py @@ -0,0 +1,59 @@ +import discord +from discord.ext import commands +from discord.ext.commands import Context + +from .rickroll import rr_command +from .labubu import labubu_command +from .tryitandsee import tryitandsee_command +from .piracy import piracy_command +from .keanu import keanu_command + +class Miscellaneous(commands.GroupCog, name="miscellaneous"): + def __init__(self, bot) -> None: + self.bot = bot + super().__init__() + + @commands.hybrid_command( + name="rr", + description="Rickroll" + ) + async def rr(self, context): + return await rr_command()(self, context) + + @commands.hybrid_command( + name="labubu", + description="Labubu ASCII art" + ) + async def labubu(self, context): + return await labubu_command()(self, context) + + @commands.hybrid_command( + name="tryitandsee", + description="Try it and see" + ) + async def tryitandsee(self, context): + return await tryitandsee_command()(self, context) + + @commands.hybrid_command( + name="piracy", + description="FBI Anti Piracy Warning" + ) + async def piracy(self, context): + return await piracy_command()(self, context) + + @commands.hybrid_command( + name="keanu", + description="Reeves" + ) + async def keanu(self, context): + return await keanu_command()(self, context) + +async def setup(bot) -> None: + cog = Miscellaneous(bot) + await bot.add_cog(cog) + + bot.logger.info("Loaded extension 'miscellaneous.rr'") + bot.logger.info("Loaded extension 'miscellaneous.labubu'") + bot.logger.info("Loaded extension 'miscellaneous.tryitandsee'") + bot.logger.info("Loaded extension 'miscellaneous.piracy'") + bot.logger.info("Loaded extension 'miscellaneous.keanu'") diff --git a/cogs/miscellaneous/keanu.py b/cogs/miscellaneous/keanu.py index bcd462a..9384484 100644 --- a/cogs/miscellaneous/keanu.py +++ b/cogs/miscellaneous/keanu.py @@ -4,15 +4,12 @@ from discord.ext.commands import Context import random -class Keanu(commands.Cog, name="keanu"): - def __init__(self, bot) -> None: - self.bot = bot - +def keanu_command(): @commands.hybrid_command( name="keanu", description="Reeves", ) - async def keanu(self, context: Context) -> None: + async def keanu(self, context): images = [ "https://yes.nighty.works/raw/z0HqUM.png", "https://yes.nighty.works/raw/1Jc0j6.avif", @@ -48,7 +45,5 @@ class Keanu(commands.Cog, name="keanu"): await inter.followup.send(embed=embed, ephemeral=True) else: await context.send(embed=embed) - - -async def setup(bot) -> None: - await bot.add_cog(Keanu(bot)) + + return keanu diff --git a/cogs/miscellaneous/labubu.py b/cogs/miscellaneous/labubu.py index 28f2f19..4f2d9bc 100644 --- a/cogs/miscellaneous/labubu.py +++ b/cogs/miscellaneous/labubu.py @@ -3,15 +3,12 @@ from discord.ext import commands from discord.ext.commands import Context -class Labubu(commands.Cog, name="labubu"): - def __init__(self, bot) -> None: - self.bot = bot - +def labubu_command(): @commands.hybrid_command( name="labubu", description="Labubu ASCII art", ) - async def labubu(self, context: Context) -> None: + async def labubu(self, context): labubu_art = """⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠛⠀⠙⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠗⠀⠀⣀⣄⠀⢿⣿⣿⣿⠟⠁⢠⡆⠉⠙⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠀⠀⣴⣿⡟⠀⠘⣿⣿⠋⠀⠀⠀⢠⣶⡀⠈⢻⣿⣿⣿⣿⣿⣿⣿⣿ @@ -63,7 +60,5 @@ class Labubu(commands.Cog, name="labubu"): await inter.followup.send(embed=embed, ephemeral=True) else: await context.send(embed=embed) - - -async def setup(bot) -> None: - await bot.add_cog(Labubu(bot)) + + return labubu diff --git a/cogs/miscellaneous/piracy.py b/cogs/miscellaneous/piracy.py index fd02dc1..9fe5b34 100644 --- a/cogs/miscellaneous/piracy.py +++ b/cogs/miscellaneous/piracy.py @@ -3,15 +3,12 @@ from discord.ext import commands from discord.ext.commands import Context -class Piracy(commands.Cog, name="piracy"): - def __init__(self, bot) -> None: - self.bot = bot - +def piracy_command(): @commands.hybrid_command( name="piracy", description="FBI Anti Piracy Warning", ) - async def piracy(self, context: Context) -> None: + async def piracy(self, context): embed = discord.Embed( color=0xE02B2B, ) @@ -27,7 +24,5 @@ class Piracy(commands.Cog, name="piracy"): await inter.followup.send(embed=embed, ephemeral=True) else: await context.send(embed=embed) - - -async def setup(bot) -> None: - await bot.add_cog(Piracy(bot)) + + return piracy diff --git a/cogs/miscellaneous/rickroll.py b/cogs/miscellaneous/rickroll.py index 0cdf1ad..fbf83ae 100644 --- a/cogs/miscellaneous/rickroll.py +++ b/cogs/miscellaneous/rickroll.py @@ -3,15 +3,12 @@ from discord.ext import commands from discord.ext.commands import Context -class Rr(commands.Cog, name="rr"): - def __init__(self, bot) -> None: - self.bot = bot - +def rr_command(): @commands.hybrid_command( name="rr", description="Rickroll", ) - async def rr(self, context: Context) -> None: + async def rr(self, context): gif_url = "https://yes.nighty.works/raw/JzjMcs.gif" embed = discord.Embed( @@ -28,7 +25,5 @@ class Rr(commands.Cog, name="rr"): await inter.followup.send(embed=embed, ephemeral=True) else: await context.send(embed=embed) - - -async def setup(bot) -> None: - await bot.add_cog(Rr(bot)) + + return rr diff --git a/cogs/miscellaneous/tryitandsee.py b/cogs/miscellaneous/tryitandsee.py index 6ac67f0..461a77f 100644 --- a/cogs/miscellaneous/tryitandsee.py +++ b/cogs/miscellaneous/tryitandsee.py @@ -3,15 +3,12 @@ from discord.ext import commands from discord.ext.commands import Context -class TryItAndSee(commands.Cog, name="tryitandsee"): - def __init__(self, bot) -> None: - self.bot = bot - +def tryitandsee_command(): @commands.hybrid_command( name="tryitandsee", description="Try it and see", ) - async def tryitandsee(self, context: Context) -> None: + async def tryitandsee(self, context): gif_url = "https://yes.nighty.works/raw/1BQP8c.gif" embed = discord.Embed( @@ -28,7 +25,5 @@ class TryItAndSee(commands.Cog, name="tryitandsee"): await inter.followup.send(embed=embed, ephemeral=True) else: await context.send(embed=embed) - - -async def setup(bot) -> None: - await bot.add_cog(TryItAndSee(bot)) + + return tryitandsee From 3bc5ebe192f1c401f6a5fc5787e0f73579907b8c Mon Sep 17 00:00:00 2001 From: neoarz Date: Sun, 28 Sep 2025 23:28:53 -0400 Subject: [PATCH 06/15] refactor(sidestore): commands into single GroupCog Converted individual sidestore command cogs into functions and grouped them under a new Sidestore GroupCog in cogs/sidestore/__init__.py. Updated bot.py and help.py to recognize the new 'sidestore' category. This simplifies command registration and improves maintainability. --- bot.py | 2 +- cogs/help.py | 2 +- cogs/sidestore/__init__.py | 104 ++++++++++++++++++++++++++++++++++++ cogs/sidestore/afc.py | 10 ++-- cogs/sidestore/code.py | 10 ++-- cogs/sidestore/crash.py | 10 ++-- cogs/sidestore/half.py | 10 ++-- cogs/sidestore/pairing.py | 10 ++-- cogs/sidestore/refresh.py | 10 ++-- cogs/sidestore/server.py | 10 ++-- cogs/sidestore/sidestore.py | 13 ++--- cogs/sidestore/sparse.py | 10 ++-- cogs/sidestore/udid.py | 10 ++-- 13 files changed, 137 insertions(+), 74 deletions(-) create mode 100644 cogs/sidestore/__init__.py diff --git a/bot.py b/bot.py index 6c7e635..b30784b 100644 --- a/bot.py +++ b/bot.py @@ -90,7 +90,7 @@ class DiscordBot(commands.Bot): if os.path.exists(init_file): try: await self.load_extension(f"cogs.{folder}") - if folder not in ["fun", "general", "idevice", "miscellaneous", "moderation"]: + if folder not in ["fun", "general", "idevice", "miscellaneous", "moderation", "owner", "sidestore"]: self.logger.info(f"Loaded extension '{folder}'") except Exception as e: exception = f"{type(e).__name__}: {e}" diff --git a/cogs/help.py b/cogs/help.py index 50941a3..876ca57 100644 --- a/cogs/help.py +++ b/cogs/help.py @@ -166,7 +166,7 @@ class Help(commands.Cog, name="help"): commands_in_category.append((app_command.name, description)) seen_names.add(app_command.name) - if hasattr(app_command, 'commands') and category in ["fun", "general", "idevice", "miscellaneous", "moderation"]: + if hasattr(app_command, 'commands') and category in ["fun", "general", "idevice", "miscellaneous", "moderation", "owner", "sidestore"]: for subcommand in app_command.commands: if subcommand.name in seen_names: continue diff --git a/cogs/sidestore/__init__.py b/cogs/sidestore/__init__.py new file mode 100644 index 0000000..3e6ce1c --- /dev/null +++ b/cogs/sidestore/__init__.py @@ -0,0 +1,104 @@ +import discord +from discord.ext import commands +from discord.ext.commands import Context + +from .sidestore import sidestore_command +from .refresh import refresh_command +from .code import code_command +from .crash import crash_command +from .pairing import pairing_command +from .server import server_command +from .afc import afc_command +from .udid import udid_command +from .half import half_command +from .sparse import sparse_command + +class Sidestore(commands.GroupCog, name="sidestore"): + def __init__(self, bot) -> None: + self.bot = bot + super().__init__() + + @commands.hybrid_command( + name="sidestore", + description="SideStore troubleshooting help" + ) + async def sidestore(self, context): + return await sidestore_command()(self, context) + + @commands.hybrid_command( + name="refresh", + description="Help with refreshing or installing apps" + ) + async def refresh(self, context): + return await refresh_command()(self, context) + + @commands.hybrid_command( + name="code", + description="No code received when signing in with Apple ID" + ) + async def code(self, context): + return await code_command()(self, context) + + @commands.hybrid_command( + name="crash", + description="Help with SideStore crashing issues" + ) + async def crash(self, context): + return await crash_command()(self, context) + + @commands.hybrid_command( + name="pairing", + description="Help with pairing file issues" + ) + async def pairing(self, context): + return await pairing_command()(self, context) + + @commands.hybrid_command( + name="server", + description="Help with anisette server issues" + ) + async def server(self, context): + return await server_command()(self, context) + + @commands.hybrid_command( + name="afc", + description="Help with AFC Connection Failure issues" + ) + async def afc(self, context): + return await afc_command()(self, context) + + @commands.hybrid_command( + name="udid", + description="SideStore could not determine device UDID" + ) + async def udid(self, context): + return await udid_command()(self, context) + + @commands.hybrid_command( + name="half", + description="Help with half-installed apps" + ) + async def half(self, context): + return await half_command()(self, context) + + @commands.hybrid_command( + name="sparse", + description="Help with sparse bundle issues" + ) + async def sparse(self, context): + return await sparse_command()(self, context) + +async def setup(bot) -> None: + cog = Sidestore(bot) + await bot.add_cog(cog) + + bot.logger.info("Loaded extension 'sidestore.sidestore'") + bot.logger.info("Loaded extension 'sidestore.refresh'") + bot.logger.info("Loaded extension 'sidestore.code'") + bot.logger.info("Loaded extension 'sidestore.crash'") + bot.logger.info("Loaded extension 'sidestore.pairing'") + bot.logger.info("Loaded extension 'sidestore.server'") + bot.logger.info("Loaded extension 'sidestore.afc'") + bot.logger.info("Loaded extension 'sidestore.udid'") + bot.logger.info("Loaded extension 'sidestore.half'") + bot.logger.info("Loaded extension 'sidestore.sparse'") diff --git a/cogs/sidestore/afc.py b/cogs/sidestore/afc.py index 05d218e..5017fea 100644 --- a/cogs/sidestore/afc.py +++ b/cogs/sidestore/afc.py @@ -5,14 +5,11 @@ from discord.ext.commands import Context import time -class Afc(commands.Cog, name="afc"): - def __init__(self, bot) -> None: - self.bot = bot - +def afc_command(): @commands.hybrid_command( name="afc", description="Help with AFC Connection Failure issues" ) - async def afc(self, context: Context) -> None: + async def afc(self, context): embed = discord.Embed( color=0x8e82f9, description=( @@ -45,5 +42,4 @@ class Afc(commands.Cog, name="afc"): await context.send(embed=embed, view=view) -async def setup(bot) -> None: - await bot.add_cog(Afc(bot)) + return afc diff --git a/cogs/sidestore/code.py b/cogs/sidestore/code.py index 494fde6..d641036 100644 --- a/cogs/sidestore/code.py +++ b/cogs/sidestore/code.py @@ -5,14 +5,11 @@ from discord.ext.commands import Context import time -class Code(commands.Cog, name="code"): - def __init__(self, bot) -> None: - self.bot = bot - +def code_command(): @commands.hybrid_command( name="code", description="No code received when signing in with Apple ID" ) - async def code(self, context: Context) -> None: + async def code(self, context): embed = discord.Embed( color=0x8e82f9, description=( @@ -59,5 +56,4 @@ class Code(commands.Cog, name="code"): await context.send(embed=embed, view=view) -async def setup(bot) -> None: - await bot.add_cog(Code(bot)) + return code diff --git a/cogs/sidestore/crash.py b/cogs/sidestore/crash.py index 2f0daaf..36f99e4 100644 --- a/cogs/sidestore/crash.py +++ b/cogs/sidestore/crash.py @@ -5,14 +5,11 @@ from discord.ext.commands import Context import time -class Crash(commands.Cog, name="crash"): - def __init__(self, bot) -> None: - self.bot = bot - +def crash_command(): @commands.hybrid_command( name="crash", description="Help with SideStore crashing issues" ) - async def crash(self, context: Context) -> None: + async def crash(self, context): embed = discord.Embed( color=0x8e82f9, description=( @@ -43,5 +40,4 @@ class Crash(commands.Cog, name="crash"): await context.send(embed=embed, view=view) -async def setup(bot) -> None: - await bot.add_cog(Crash(bot)) + return crash diff --git a/cogs/sidestore/half.py b/cogs/sidestore/half.py index 96fa8fe..00889c8 100644 --- a/cogs/sidestore/half.py +++ b/cogs/sidestore/half.py @@ -5,14 +5,11 @@ from discord.ext.commands import Context import time -class Half(commands.Cog, name="half"): - def __init__(self, bot) -> None: - self.bot = bot - +def half_command(): @commands.hybrid_command( name="half", description="Help when apps get stuck installing" ) - async def half(self, context: Context) -> None: + async def half(self, context): embed = discord.Embed( color=0x8e82f9, description=( @@ -52,5 +49,4 @@ class Half(commands.Cog, name="half"): await context.send(embed=embed, view=view) -async def setup(bot) -> None: - await bot.add_cog(Half(bot)) + return half diff --git a/cogs/sidestore/pairing.py b/cogs/sidestore/pairing.py index 28f6e57..22730c8 100644 --- a/cogs/sidestore/pairing.py +++ b/cogs/sidestore/pairing.py @@ -5,14 +5,11 @@ from discord.ext.commands import Context import time -class Pairing(commands.Cog, name="pairing"): - def __init__(self, bot) -> None: - self.bot = bot - +def pairing_command(): @commands.hybrid_command( name="pairing", description="Help with pairing file issues" ) - async def pairing(self, context: Context) -> None: + async def pairing(self, context): embed = discord.Embed( color=0x8e82f9, description=( @@ -57,5 +54,4 @@ class Pairing(commands.Cog, name="pairing"): await context.send(embed=embed, view=view) -async def setup(bot) -> None: - await bot.add_cog(Pairing(bot)) + return pairing diff --git a/cogs/sidestore/refresh.py b/cogs/sidestore/refresh.py index cbe5b43..08a47fe 100644 --- a/cogs/sidestore/refresh.py +++ b/cogs/sidestore/refresh.py @@ -5,14 +5,11 @@ from discord.ext.commands import Context import time -class Refresh(commands.Cog, name="refresh"): - def __init__(self, bot) -> None: - self.bot = bot - +def refresh_command(): @commands.hybrid_command( name="refresh", description="Help with refreshing or installing apps" ) - async def refresh(self, context: Context) -> None: + async def refresh(self, context): embed = discord.Embed( color=0x8e82f9, description=( @@ -47,5 +44,4 @@ class Refresh(commands.Cog, name="refresh"): await context.send(embed=embed, view=view) -async def setup(bot) -> None: - await bot.add_cog(Refresh(bot)) + return refresh diff --git a/cogs/sidestore/server.py b/cogs/sidestore/server.py index 37e7fb6..4d2a6cf 100644 --- a/cogs/sidestore/server.py +++ b/cogs/sidestore/server.py @@ -5,14 +5,11 @@ from discord.ext.commands import Context import time -class Server(commands.Cog, name="server"): - def __init__(self, bot) -> None: - self.bot = bot - +def server_command(): @commands.hybrid_command( name="server", description="Help with anisette server issues" ) - async def server(self, context: Context) -> None: + async def server(self, context): embed = discord.Embed( color=0x8e82f9, description=( @@ -51,5 +48,4 @@ class Server(commands.Cog, name="server"): await context.send(embed=embed, view=view) -async def setup(bot) -> None: - await bot.add_cog(Server(bot)) + return server diff --git a/cogs/sidestore/sidestore.py b/cogs/sidestore/sidestore.py index f84a6c7..8433b37 100644 --- a/cogs/sidestore/sidestore.py +++ b/cogs/sidestore/sidestore.py @@ -107,14 +107,11 @@ class SidestoreView(discord.ui.View): self.add_item(SidestoreSelect(bot)) -class Sidestore(commands.Cog, name="sidestore"): - def __init__(self, bot) -> None: - self.bot = bot - +def sidestore_command(): @commands.hybrid_command( name="sidestore", description="SideStore troubleshooting and help" ) - async def sidestore(self, context: Context) -> None: + async def sidestore(self, context): embed = discord.Embed( title="SideStore Commands", description="Choose a command from the dropdown below to get help with specific issues:", @@ -128,7 +125,5 @@ class Sidestore(commands.Cog, name="sidestore"): await context.interaction.response.send_message(embed=embed, view=view, ephemeral=True) else: await context.send(embed=embed, view=view) - - -async def setup(bot) -> None: - await bot.add_cog(Sidestore(bot)) + + return sidestore diff --git a/cogs/sidestore/sparse.py b/cogs/sidestore/sparse.py index 4c2dd01..e039bc8 100644 --- a/cogs/sidestore/sparse.py +++ b/cogs/sidestore/sparse.py @@ -5,14 +5,11 @@ from discord.ext.commands import Context import time -class Sparse(commands.Cog, name="sparse"): - def __init__(self, bot) -> None: - self.bot = bot - +def sparse_command(): @commands.hybrid_command( name="sparse", description="Information about SparseRestore exploit" ) - async def sparse(self, context: Context) -> None: + async def sparse(self, context): embed = discord.Embed( color=0x8e82f9, description=( @@ -46,5 +43,4 @@ class Sparse(commands.Cog, name="sparse"): await context.send(embed=embed, view=view) -async def setup(bot) -> None: - await bot.add_cog(Sparse(bot)) + return sparse diff --git a/cogs/sidestore/udid.py b/cogs/sidestore/udid.py index f15f649..660d5e1 100644 --- a/cogs/sidestore/udid.py +++ b/cogs/sidestore/udid.py @@ -5,14 +5,11 @@ from discord.ext.commands import Context import time -class Udid(commands.Cog, name="udid"): - def __init__(self, bot) -> None: - self.bot = bot - +def udid_command(): @commands.hybrid_command( name="udid", description="SideStore could not determine device UDID" ) - async def udid(self, context: Context) -> None: + async def udid(self, context): embed = discord.Embed( color=0x8e82f9, description=( @@ -45,5 +42,4 @@ class Udid(commands.Cog, name="udid"): await context.send(embed=embed, view=view) -async def setup(bot) -> None: - await bot.add_cog(Udid(bot)) + return udid From a5eff449ebdb12e18b4c1d915a9dfb75c7d020b6 Mon Sep 17 00:00:00 2001 From: neoarz Date: Sun, 28 Sep 2025 23:35:39 -0400 Subject: [PATCH 07/15] refactor(utility): make command group Introduces a new 'utilities' cog and a 'translate' command for translating text between languages. Refactors the translate functionality from a class-based cog to a function-based command, updates bot and help modules to register the new cog, and ensures proper language autocomplete and error handling. --- bot.py | 2 +- cogs/help.py | 2 +- cogs/utilities/__init__.py | 23 ++++++++++++++++++++ cogs/utilities/translate.py | 43 +++++++++++++++++-------------------- 4 files changed, 45 insertions(+), 25 deletions(-) create mode 100644 cogs/utilities/__init__.py diff --git a/bot.py b/bot.py index b30784b..b2501c7 100644 --- a/bot.py +++ b/bot.py @@ -90,7 +90,7 @@ class DiscordBot(commands.Bot): if os.path.exists(init_file): try: await self.load_extension(f"cogs.{folder}") - if folder not in ["fun", "general", "idevice", "miscellaneous", "moderation", "owner", "sidestore"]: + if folder not in ["fun", "general", "idevice", "miscellaneous", "moderation", "owner", "sidestore", "utilities"]: self.logger.info(f"Loaded extension '{folder}'") except Exception as e: exception = f"{type(e).__name__}: {e}" diff --git a/cogs/help.py b/cogs/help.py index 876ca57..3bb9cd2 100644 --- a/cogs/help.py +++ b/cogs/help.py @@ -166,7 +166,7 @@ class Help(commands.Cog, name="help"): commands_in_category.append((app_command.name, description)) seen_names.add(app_command.name) - if hasattr(app_command, 'commands') and category in ["fun", "general", "idevice", "miscellaneous", "moderation", "owner", "sidestore"]: + if hasattr(app_command, 'commands') and category in ["fun", "general", "idevice", "miscellaneous", "moderation", "owner", "sidestore", "utilities"]: for subcommand in app_command.commands: if subcommand.name in seen_names: continue diff --git a/cogs/utilities/__init__.py b/cogs/utilities/__init__.py new file mode 100644 index 0000000..ec862e4 --- /dev/null +++ b/cogs/utilities/__init__.py @@ -0,0 +1,23 @@ +import discord +from discord.ext import commands +from discord.ext.commands import Context + +from .translate import translate_command + +class Utilities(commands.GroupCog, name="utilities"): + def __init__(self, bot) -> None: + self.bot = bot + super().__init__() + + @commands.hybrid_command( + name="translate", + description="Translate text to another language" + ) + async def translate(self, context, text: str = None, to_lang: str = "en", from_lang: str = None): + return await translate_command()(self, context, text=text, to_lang=to_lang, from_lang=from_lang) + +async def setup(bot) -> None: + cog = Utilities(bot) + await bot.add_cog(cog) + + bot.logger.info("Loaded extension 'utilities.translate'") diff --git a/cogs/utilities/translate.py b/cogs/utilities/translate.py index 45b99b8..8af6b40 100644 --- a/cogs/utilities/translate.py +++ b/cogs/utilities/translate.py @@ -9,10 +9,8 @@ import json import urllib.parse -class Translate(commands.Cog, name="translate"): - def __init__(self, bot) -> None: - self.bot = bot - self.languages = { +def translate_command(): + languages = { "auto": "Auto-detect", "en": "English", "es": "Spanish", @@ -136,7 +134,7 @@ class Translate(commands.Cog, name="translate"): "lv": "Latvian", } - async def send_embed(self, context: Context, embed: discord.Embed, *, ephemeral: bool = False) -> None: + async def send_embed(context, embed: discord.Embed, *, ephemeral: bool = False) -> None: interaction = getattr(context, "interaction", None) if interaction is not None: if interaction.response.is_done(): @@ -146,7 +144,7 @@ class Translate(commands.Cog, name="translate"): else: await context.send(embed=embed) - async def _translate_with_google_web(self, text: str, from_lang: str = "auto", to_lang: str = "en") -> dict: + async def _translate_with_google_web(text: str, from_lang: str = "auto", to_lang: str = "en") -> dict: try: base_url = "https://translate.googleapis.com/translate_a/single" @@ -196,11 +194,11 @@ class Translate(commands.Cog, name="translate"): except Exception: return None - async def language_autocomplete(self, interaction: discord.Interaction, current: str) -> list[app_commands.Choice[str]]: + async def language_autocomplete(interaction: discord.Interaction, current: str) -> list[app_commands.Choice[str]]: current = current.lower() choices = [] - for code, name in self.languages.items(): + for code, name in languages.items(): if current in code.lower() or current in name.lower(): display_name = f"{code} - {name}" if len(display_name) > 100: @@ -213,7 +211,7 @@ class Translate(commands.Cog, name="translate"): if not choices: popular = ["en", "es", "fr", "de", "it", "pt", "ru", "ja", "ko", "zh-CN"] for code in popular: - name = self.languages.get(code, code) + name = languages.get(code, code) choices.append(app_commands.Choice(name=f"{code} - {name}", value=code)) return choices @@ -229,7 +227,7 @@ class Translate(commands.Cog, name="translate"): ) @app_commands.autocomplete(to_lang=language_autocomplete) @app_commands.autocomplete(from_lang=language_autocomplete) - async def translate(self, context: Context, text: str = None, to_lang: str = "en", from_lang: str = None): + async def translate(self, context, text: str = None, to_lang: str = "en", from_lang: str = None): if not text or not text.strip(): if context.message and context.message.reference and context.message.reference.resolved: replied_message = context.message.reference.resolved @@ -241,7 +239,7 @@ class Translate(commands.Cog, name="translate"): description="The replied message has no text content to translate.", color=0xE02B2B, ).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") - await self.send_embed(context, embed, ephemeral=True) + await send_embed(context, embed, ephemeral=True) return else: embed = discord.Embed( @@ -249,34 +247,34 @@ class Translate(commands.Cog, name="translate"): description="Please provide text to translate or reply to a message with text.", color=0xE02B2B, ).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") - await self.send_embed(context, embed, ephemeral=True) + await send_embed(context, embed, ephemeral=True) return - if to_lang not in self.languages: + if to_lang not in languages: embed = discord.Embed( title="Error", description=f"Invalid target language code: `{to_lang}`. Use the autocomplete feature to see available languages.", color=0xE02B2B, ).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") - await self.send_embed(context, embed, ephemeral=True) + await send_embed(context, embed, ephemeral=True) return - if from_lang and from_lang not in self.languages: + if from_lang and from_lang not in languages: embed = discord.Embed( title="Error", description=f"Invalid source language code: `{from_lang}`. Use the autocomplete feature to see available languages.", color=0xE02B2B, ).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") - await self.send_embed(context, embed, ephemeral=True) + await send_embed(context, embed, ephemeral=True) return - result = await self._translate_with_google_web(text, from_lang or "auto", to_lang) + result = await _translate_with_google_web(text, from_lang or "auto", to_lang) if result and result.get("translatedText"): detected_lang = result.get("detectedSourceLanguage", from_lang or "auto") - from_lang_name = self.languages.get(detected_lang, detected_lang) - to_lang_name = self.languages.get(to_lang, to_lang) + from_lang_name = languages.get(detected_lang, detected_lang) + to_lang_name = languages.get(to_lang, to_lang) embed = discord.Embed( title="Translation", @@ -286,18 +284,17 @@ class Translate(commands.Cog, name="translate"): embed.set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") embed.set_footer(text=f"{from_lang_name} » {to_lang_name}") - await self.send_embed(context, embed) + await send_embed(context, embed) else: embed = discord.Embed( title="Error", description="Translation failed. Please try again later.", color=0xE02B2B, ).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") - await self.send_embed(context, embed, ephemeral=True) + await send_embed(context, embed, ephemeral=True) -async def setup(bot): - await bot.add_cog(Translate(bot)) \ No newline at end of file + return translate \ No newline at end of file From b6c74828f190da536486a83c7f22d6b4f59574ee Mon Sep 17 00:00:00 2001 From: neoarz Date: Sun, 28 Sep 2025 23:41:42 -0400 Subject: [PATCH 08/15] refactor: extension loading and help category mapping Simplifies extension loading log conditions to only exclude 'owner' and streamlines help command category mapping by grouping commands under their respective __init__.py structures. Also updates subcommand handling to exclude only 'owner' from group checks. --- bot.py | 2 +- cogs/help.py | 47 +++++++++++------------------------------------ 2 files changed, 12 insertions(+), 37 deletions(-) diff --git a/bot.py b/bot.py index b2501c7..457f0a2 100644 --- a/bot.py +++ b/bot.py @@ -90,7 +90,7 @@ class DiscordBot(commands.Bot): if os.path.exists(init_file): try: await self.load_extension(f"cogs.{folder}") - if folder not in ["fun", "general", "idevice", "miscellaneous", "moderation", "owner", "sidestore", "utilities"]: + if folder not in ["owner"]: self.logger.info(f"Loaded extension '{folder}'") except Exception as e: exception = f"{type(e).__name__}: {e}" diff --git a/cogs/help.py b/cogs/help.py index 3bb9cd2..937e051 100644 --- a/cogs/help.py +++ b/cogs/help.py @@ -37,49 +37,24 @@ class Help(commands.Cog, name="help"): async def help(self, context: Context, category: str = None) -> None: category_mapping = { - # General Commands + # Command Groups (using __init__.py structure) "general": "general", - - # Fun Commands "fun": "fun", - - # idevice Commands "idevice": "idevice", - - # Moderation Commands - "kick": "moderation", - "ban": "moderation", - "nick": "moderation", - "purge": "moderation", - "hackban": "moderation", - "warnings": "moderation", - "archive": "moderation", - - # SideStore Commands + "miscellaneous": "miscellaneous", + "moderation": "moderation", "sidestore": "sidestore", - "refresh": "sidestore", - "code": "sidestore", - "crash": "sidestore", - "pairing": "sidestore", - "server": "sidestore", - "half": "sidestore", - "sparse": "sidestore", - "afc": "sidestore", - "udid": "sidestore", + "utilities": "utilities", - # Owner Commands + # Individual Owner Commands (exception) "sync": "owner", - "cog_management": "owner", + "logs": "owner", + "invite": "owner", + "load": "owner", + "unload": "owner", + "reload": "owner", "shutdown": "owner", "say": "owner", - "invite": "owner", - "logs": "owner", - - # Utilities Commands - "translate": "utilities", - - # Miscellaneous Commands - "miscellaneous": "miscellaneous", } category_descriptions = { @@ -166,7 +141,7 @@ class Help(commands.Cog, name="help"): commands_in_category.append((app_command.name, description)) seen_names.add(app_command.name) - if hasattr(app_command, 'commands') and category in ["fun", "general", "idevice", "miscellaneous", "moderation", "owner", "sidestore", "utilities"]: + if hasattr(app_command, 'commands') and category not in ["owner"]: for subcommand in app_command.commands: if subcommand.name in seen_names: continue From a7cdeee47058ba3f9549abbee9d3c9cf6b4a8275 Mon Sep 17 00:00:00 2001 From: neoarz Date: Mon, 29 Sep 2025 08:14:33 -0400 Subject: [PATCH 09/15] refactor(SideStore): help command structure Reorganized SideStore commands to use a group structure, added individual subcommands for troubleshooting topics, and updated the help command to provide a dropdown for specific issues. This improves command discoverability and user experience. --- cogs/help.py | 2 - cogs/sidestore/__init__.py | 87 ++++++++++++++++++++++++++++++++++--- cogs/sidestore/sidestore.py | 2 +- 3 files changed, 82 insertions(+), 9 deletions(-) diff --git a/cogs/help.py b/cogs/help.py index 937e051..6f68742 100644 --- a/cogs/help.py +++ b/cogs/help.py @@ -37,7 +37,6 @@ class Help(commands.Cog, name="help"): async def help(self, context: Context, category: str = None) -> None: category_mapping = { - # Command Groups (using __init__.py structure) "general": "general", "fun": "fun", "idevice": "idevice", @@ -46,7 +45,6 @@ class Help(commands.Cog, name="help"): "sidestore": "sidestore", "utilities": "utilities", - # Individual Owner Commands (exception) "sync": "owner", "logs": "owner", "invite": "owner", diff --git a/cogs/sidestore/__init__.py b/cogs/sidestore/__init__.py index 3e6ce1c..b9c1f26 100644 --- a/cogs/sidestore/__init__.py +++ b/cogs/sidestore/__init__.py @@ -1,8 +1,9 @@ import discord +from discord import app_commands from discord.ext import commands from discord.ext.commands import Context -from .sidestore import sidestore_command +from .sidestore import SidestoreView from .refresh import refresh_command from .code import code_command from .crash import crash_command @@ -18,12 +19,86 @@ class Sidestore(commands.GroupCog, name="sidestore"): self.bot = bot super().__init__() - @commands.hybrid_command( - name="sidestore", + @commands.group(name="sidestore", invoke_without_command=True) + async def sidestore_group(self, context: Context): + embed = discord.Embed( + title="SideStore Commands", + description="Choose a command from the dropdown below to get help with specific issues:", + color=0x8e82f9 + ) + embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true") + view = SidestoreView(self.bot) + await context.send(embed=embed, view=view) + + @sidestore_group.command(name="help") + async def sidestore_group_help(self, context: Context): + embed = discord.Embed( + title="SideStore Commands", + description="Choose a command from the dropdown below to get help with specific issues:", + color=0x8e82f9 + ) + embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true") + view = SidestoreView(self.bot) + await context.send(embed=embed, view=view) + + async def _invoke_hybrid(self, context: Context, name: str): + command = self.bot.get_command(name) + if command is not None: + await context.invoke(command) + else: + await context.send(f"Unknown SideStore command: {name}") + + @sidestore_group.command(name="refresh") + async def sidestore_group_refresh(self, context: Context): + await self._invoke_hybrid(context, "refresh") + + @sidestore_group.command(name="code") + async def sidestore_group_code(self, context: Context): + await self._invoke_hybrid(context, "code") + + @sidestore_group.command(name="crash") + async def sidestore_group_crash(self, context: Context): + await self._invoke_hybrid(context, "crash") + + @sidestore_group.command(name="pairing") + async def sidestore_group_pairing(self, context: Context): + await self._invoke_hybrid(context, "pairing") + + @sidestore_group.command(name="server") + async def sidestore_group_server(self, context: Context): + await self._invoke_hybrid(context, "server") + + @sidestore_group.command(name="afc") + async def sidestore_group_afc(self, context: Context): + await self._invoke_hybrid(context, "afc") + + @sidestore_group.command(name="udid") + async def sidestore_group_udid(self, context: Context): + await self._invoke_hybrid(context, "udid") + + @sidestore_group.command(name="half") + async def sidestore_group_half(self, context: Context): + await self._invoke_hybrid(context, "half") + + @sidestore_group.command(name="sparse") + async def sidestore_group_sparse(self, context: Context): + await self._invoke_hybrid(context, "sparse") + + @app_commands.command( + name="help", description="SideStore troubleshooting help" ) - async def sidestore(self, context): - return await sidestore_command()(self, context) + async def help(self, interaction: discord.Interaction): + embed = discord.Embed( + title="SideStore Commands", + description="Choose a command from the dropdown below to get help with specific issues:", + color=0x8e82f9 + ) + embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true") + + view = SidestoreView(self.bot) + + await interaction.response.send_message(embed=embed, view=view, ephemeral=True) @commands.hybrid_command( name="refresh", @@ -92,7 +167,7 @@ async def setup(bot) -> None: cog = Sidestore(bot) await bot.add_cog(cog) - bot.logger.info("Loaded extension 'sidestore.sidestore'") + bot.logger.info("Loaded extension 'sidestore.help'") bot.logger.info("Loaded extension 'sidestore.refresh'") bot.logger.info("Loaded extension 'sidestore.code'") bot.logger.info("Loaded extension 'sidestore.crash'") diff --git a/cogs/sidestore/sidestore.py b/cogs/sidestore/sidestore.py index 8433b37..09f6ea1 100644 --- a/cogs/sidestore/sidestore.py +++ b/cogs/sidestore/sidestore.py @@ -109,7 +109,7 @@ class SidestoreView(discord.ui.View): def sidestore_command(): @commands.hybrid_command( - name="sidestore", description="SideStore troubleshooting and help" + name="help", description="SideStore troubleshooting and help" ) async def sidestore(self, context): embed = discord.Embed( From aea98a95d5dca3c69b29aaea4cbd3adaba5e24ae Mon Sep 17 00:00:00 2001 From: neoarz Date: Mon, 29 Sep 2025 08:24:14 -0400 Subject: [PATCH 10/15] refactor(idevice): cog commands and add help group Reworked the idevice cog to use a command group with subcommands for help, errorcodes, developermode, noapps, and mountddi. Added a dropdown view for help commands and improved logging for extension loading. Updated references to ideviceView and adjusted extension load conditions in bot.py. --- bot.py | 2 +- cogs/idevice/__init__.py | 67 +++++++++++++++++++++++++++++++++++----- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/bot.py b/bot.py index 457f0a2..f9cfe5d 100644 --- a/bot.py +++ b/bot.py @@ -90,7 +90,7 @@ class DiscordBot(commands.Bot): if os.path.exists(init_file): try: await self.load_extension(f"cogs.{folder}") - if folder not in ["owner"]: + if folder in ["owner", "help"]: self.logger.info(f"Loaded extension '{folder}'") except Exception as e: exception = f"{type(e).__name__}: {e}" diff --git a/cogs/idevice/__init__.py b/cogs/idevice/__init__.py index 3bdda61..2f3cf5e 100644 --- a/cogs/idevice/__init__.py +++ b/cogs/idevice/__init__.py @@ -1,8 +1,9 @@ import discord +from discord import app_commands from discord.ext import commands from discord.ext.commands import Context -from .idevice import idevice_command +from .idevice import ideviceView from .error_codes import errorcodes_command from .developermode import developermode_command from .noapps import noapps_command @@ -13,12 +14,64 @@ class Idevice(commands.GroupCog, name="idevice"): self.bot = bot super().__init__() - @commands.hybrid_command( - name="idevice", - description="Get help with idevice commands and troubleshooting." + @commands.group(name="idevice", invoke_without_command=True) + async def idevice_group(self, context: Context): + embed = discord.Embed( + title="idevice Commands", + description="Choose a command from the dropdown below to get help with specific issues:", + color=0xfa8c4a + ) + embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png") + view = ideviceView(self.bot) + await context.send(embed=embed, view=view) + + @idevice_group.command(name="help") + async def idevice_group_help(self, context: Context): + embed = discord.Embed( + title="idevice Commands", + description="Choose a command from the dropdown below to get help with specific issues:", + color=0xfa8c4a + ) + embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png") + view = ideviceView(self.bot) + await context.send(embed=embed, view=view) + + async def _invoke_hybrid(self, context: Context, name: str): + command = self.bot.get_command(name) + if command is not None: + await context.invoke(command) + else: + await context.send(f"Unknown idevice command: {name}") + + @idevice_group.command(name="errorcodes") + async def idevice_group_errorcodes(self, context: Context): + await self._invoke_hybrid(context, "errorcodes") + + @idevice_group.command(name="developermode") + async def idevice_group_developermode(self, context: Context): + await self._invoke_hybrid(context, "developermode") + + @idevice_group.command(name="noapps") + async def idevice_group_noapps(self, context: Context): + await self._invoke_hybrid(context, "noapps") + + @idevice_group.command(name="mountddi") + async def idevice_group_mountddi(self, context: Context): + await self._invoke_hybrid(context, "mountddi") + + @app_commands.command( + name="help", + description="idevice troubleshooting help" ) - async def idevice(self, context): - return await idevice_command()(self, context) + async def help(self, interaction: discord.Interaction): + embed = discord.Embed( + title="idevice Commands", + description="Choose a command from the dropdown below to get help with specific issues:", + color=0xfa8c4a + ) + embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png") + view = ideviceView(self.bot) + await interaction.response.send_message(embed=embed, view=view, ephemeral=True) @commands.hybrid_command( name="errorcodes", @@ -52,7 +105,7 @@ async def setup(bot) -> None: cog = Idevice(bot) await bot.add_cog(cog) - bot.logger.info("Loaded extension 'idevice.idevice'") + bot.logger.info("Loaded extension 'idevice.help'") bot.logger.info("Loaded extension 'idevice.errorcodes'") bot.logger.info("Loaded extension 'idevice.developermode'") bot.logger.info("Loaded extension 'idevice.noapps'") From 49fcd821a4b72006abfc58632f82e63f957929cd Mon Sep 17 00:00:00 2001 From: neoarz Date: Mon, 29 Sep 2025 08:27:59 -0400 Subject: [PATCH 11/15] refactor(fun): command group with subcommands Introduces a 'fun' command group with subcommands for coinflip, 8ball, minesweeper, randomfact, and rps. Provides an embed listing available subcommands and routes each to its respective handler for improved command organization. --- cogs/fun/__init__.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/cogs/fun/__init__.py b/cogs/fun/__init__.py index 8cb0e7c..a08fc44 100644 --- a/cogs/fun/__init__.py +++ b/cogs/fun/__init__.py @@ -13,6 +13,44 @@ class Fun(commands.GroupCog, name="fun"): self.bot = bot super().__init__() + @commands.group(name="fun", invoke_without_command=True) + async def fun_group(self, context: Context): + embed = discord.Embed( + title="Fun Commands", + description="Use `.fun ` or slash `/fun `.", + color=0x7289DA + ) + embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp") + embed.add_field(name="Available", value="coinflip, 8ball, minesweeper, randomfact, rps", 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 fun command: {name}") + + @fun_group.command(name="coinflip") + async def fun_group_coinflip(self, context: Context): + await self._invoke_hybrid(context, "coinflip") + + @fun_group.command(name="8ball") + async def fun_group_8ball(self, context: Context, *, question: str): + await self._invoke_hybrid(context, "8ball", question=question) + + @fun_group.command(name="minesweeper") + async def fun_group_minesweeper(self, context: Context): + await self._invoke_hybrid(context, "minesweeper") + + @fun_group.command(name="randomfact") + async def fun_group_randomfact(self, context: Context): + await self._invoke_hybrid(context, "randomfact") + + @fun_group.command(name="rps") + async def fun_group_rps(self, context: Context): + await self._invoke_hybrid(context, "rps") + @commands.hybrid_command( name="coinflip", description="Make a coin flip, but give your bet before." From 2973d0fd254eec4be8832a15f72cbbd61d6fb57f Mon Sep 17 00:00:00 2001 From: neoarz Date: Mon, 29 Sep 2025 08:33:01 -0400 Subject: [PATCH 12/15] refactor(general): command groups Introduces a 'general' command group with subcommands for ping, uptime, botinfo, serverinfo, and feedback. Provides an embed listing available subcommands and routes each to their respective command handlers for improved organization and discoverability. --- cogs/general/__init__.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/cogs/general/__init__.py b/cogs/general/__init__.py index 22716d0..6e92d77 100644 --- a/cogs/general/__init__.py +++ b/cogs/general/__init__.py @@ -13,6 +13,44 @@ class General(commands.GroupCog, name="general"): self.bot = bot super().__init__() + @commands.group(name="general", invoke_without_command=True) + async def general_group(self, context: Context): + embed = discord.Embed( + title="General Commands", + description="Use `.general ` or `/general `.", + color=0x7289DA + ) + embed.set_author(name="General", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + embed.add_field(name="Available", value="ping, uptime, botinfo, serverinfo, feedback", inline=False) + await context.send(embed=embed) + + async def _invoke_hybrid(self, context: Context, name: str): + command = self.bot.get_command(name) + if command is not None: + await context.invoke(command) + else: + await context.send(f"Unknown general command: {name}") + + @general_group.command(name="ping") + async def general_group_ping(self, context: Context): + await self._invoke_hybrid(context, "ping") + + @general_group.command(name="uptime") + async def general_group_uptime(self, context: Context): + await self._invoke_hybrid(context, "uptime") + + @general_group.command(name="botinfo") + async def general_group_botinfo(self, context: Context): + await self._invoke_hybrid(context, "botinfo") + + @general_group.command(name="serverinfo") + async def general_group_serverinfo(self, context: Context): + await self._invoke_hybrid(context, "serverinfo") + + @general_group.command(name="feedback") + async def general_group_feedback(self, context: Context): + await self._invoke_hybrid(context, "feedback") + @commands.hybrid_command( name="ping", description="Check if the bot is alive.", From a67e80a6eb4aab60a5805cfd67a496c0bb8b6004 Mon Sep 17 00:00:00 2001 From: neoarz Date: Mon, 29 Sep 2025 08:46:21 -0400 Subject: [PATCH 13/15] refactor: group commands for miscellaneous and utilities cogs Introduces group commands for both miscellaneous and utilities cogs, allowing users to access subcommands via `.miscellaneous ` and `.utilities `. Adds embedded help messages and hybrid invocation logic for easier command management and user guidance. --- cogs/miscellaneous/__init__.py | 38 ++++++++++++++++++++++++++++++++++ cogs/utilities/__init__.py | 21 +++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/cogs/miscellaneous/__init__.py b/cogs/miscellaneous/__init__.py index 216489d..8d91534 100644 --- a/cogs/miscellaneous/__init__.py +++ b/cogs/miscellaneous/__init__.py @@ -13,6 +13,44 @@ class Miscellaneous(commands.GroupCog, name="miscellaneous"): self.bot = bot super().__init__() + @commands.group(name="miscellaneous", invoke_without_command=True) + async def miscellaneous_group(self, context: Context): + embed = discord.Embed( + title="Miscellaneous Commands", + description="Use `.miscellaneous ` or `/miscellaneous `.", + color=0x7289DA + ) + embed.set_author(name="Miscellaneous", icon_url="https://yes.nighty.works/raw/YxMC0r.png") + embed.add_field(name="Available", value="rr, labubu, tryitandsee, piracy, keanu", inline=False) + await context.send(embed=embed) + + async def _invoke_hybrid(self, context: Context, name: str): + command = self.bot.get_command(name) + if command is not None: + await context.invoke(command) + else: + await context.send(f"Unknown miscellaneous command: {name}") + + @miscellaneous_group.command(name="rr") + async def miscellaneous_group_rr(self, context: Context): + await self._invoke_hybrid(context, "rr") + + @miscellaneous_group.command(name="labubu") + async def miscellaneous_group_labubu(self, context: Context): + await self._invoke_hybrid(context, "labubu") + + @miscellaneous_group.command(name="tryitandsee") + async def miscellaneous_group_tryitandsee(self, context: Context): + await self._invoke_hybrid(context, "tryitandsee") + + @miscellaneous_group.command(name="piracy") + async def miscellaneous_group_piracy(self, context: Context): + await self._invoke_hybrid(context, "piracy") + + @miscellaneous_group.command(name="keanu") + async def miscellaneous_group_keanu(self, context: Context): + await self._invoke_hybrid(context, "keanu") + @commands.hybrid_command( name="rr", description="Rickroll" diff --git a/cogs/utilities/__init__.py b/cogs/utilities/__init__.py index ec862e4..7b5d12d 100644 --- a/cogs/utilities/__init__.py +++ b/cogs/utilities/__init__.py @@ -9,6 +9,27 @@ class Utilities(commands.GroupCog, name="utilities"): self.bot = bot super().__init__() + @commands.group(name="utilities", invoke_without_command=True) + async def utilities_group(self, context: Context): + embed = discord.Embed( + title="Utilities Commands", + description="Use `.utilities ` or `/utilities `.", + color=0x7289DA + ) + embed.add_field(name="Available", value="translate", 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 utilities command: {name}") + + @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.hybrid_command( name="translate", description="Translate text to another language" From f8123b43a2bd7feeadd36d510d7ff96f3434700c Mon Sep 17 00:00:00 2001 From: neoarz Date: Mon, 29 Sep 2025 09:19:00 -0400 Subject: [PATCH 14/15] feat: Add aliases and update group names for cogs Renamed 'miscellaneous' and 'utilities' cog group names to 'misc' and 'utils', respectively. Added aliases for command groups and updated help descriptions and embed author icons for consistency and clarity. --- cogs/miscellaneous/__init__.py | 6 +++--- cogs/utilities/__init__.py | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/cogs/miscellaneous/__init__.py b/cogs/miscellaneous/__init__.py index 8d91534..da7828c 100644 --- a/cogs/miscellaneous/__init__.py +++ b/cogs/miscellaneous/__init__.py @@ -8,16 +8,16 @@ from .tryitandsee import tryitandsee_command from .piracy import piracy_command from .keanu import keanu_command -class Miscellaneous(commands.GroupCog, name="miscellaneous"): +class Miscellaneous(commands.GroupCog, name="misc"): def __init__(self, bot) -> None: self.bot = bot super().__init__() - @commands.group(name="miscellaneous", invoke_without_command=True) + @commands.group(name="miscellaneous", aliases=["misc"], invoke_without_command=True) async def miscellaneous_group(self, context: Context): embed = discord.Embed( title="Miscellaneous Commands", - description="Use `.miscellaneous ` or `/miscellaneous `.", + description="Use `.misc ` or `/misc `.", color=0x7289DA ) embed.set_author(name="Miscellaneous", icon_url="https://yes.nighty.works/raw/YxMC0r.png") diff --git a/cogs/utilities/__init__.py b/cogs/utilities/__init__.py index 7b5d12d..3364dce 100644 --- a/cogs/utilities/__init__.py +++ b/cogs/utilities/__init__.py @@ -4,18 +4,19 @@ from discord.ext.commands import Context from .translate import translate_command -class Utilities(commands.GroupCog, name="utilities"): +class Utilities(commands.GroupCog, name="utils"): def __init__(self, bot) -> None: self.bot = bot super().__init__() - @commands.group(name="utilities", invoke_without_command=True) + @commands.group(name="utilities", aliases=["utils"], invoke_without_command=True) async def utilities_group(self, context: Context): embed = discord.Embed( title="Utilities Commands", - description="Use `.utilities ` or `/utilities `.", + description="Use `.utils ` or `/utils `.", color=0x7289DA ) + embed.set_author(name="Utilities", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") embed.add_field(name="Available", value="translate", inline=False) await context.send(embed=embed) From 3f4254dcdcc6c5e0542931f9737123a5ef2b0128 Mon Sep 17 00:00:00 2001 From: neoarz Date: Mon, 29 Sep 2025 09:59:56 -0400 Subject: [PATCH 15/15] 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"