diff --git a/bot.py b/bot.py index d75f222..3670617 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 in ["owner", "help"]: + 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('__'): @@ -271,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 new file mode 100644 index 0000000..18420de --- /dev/null +++ b/cogs/fun/__init__.py @@ -0,0 +1,108 @@ +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.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}") + + 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") + + @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.check(_require_group_prefix) + @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.check(_require_group_prefix) + @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.check(_require_group_prefix) + @commands.hybrid_command( + name="minesweeper", + description="Play a buttoned minesweeper mini-game." + ) + 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." + ) + 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/__init__.py b/cogs/general/__init__.py new file mode 100644 index 0000000..e37c04c --- /dev/null +++ b/cogs/general/__init__.py @@ -0,0 +1,113 @@ +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 + + +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 + 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.check(_require_group_prefix) + @commands.hybrid_command( + name="ping", + description="Check if the bot is alive.", + ) + 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.", + ) + 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.", + ) + 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.", + ) + 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" + ) + 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 79% rename from cogs/general/help.py rename to cogs/help.py index 536462f..6f68742 100644 --- a/cogs/general/help.py +++ b/cogs/help.py @@ -37,67 +37,22 @@ class Help(commands.Cog, name="help"): async def help(self, context: Context, category: str = None) -> None: category_mapping = { - # General Commands - "help": "general", - "botinfo": "general", - "serverinfo": "general", - "ping": "general", - "feedback": "general", - "uptime": "general", - # "context_menus": "general", - - # Fun Commands - "randomfact": "fun", - "coinflip": "fun", - "rps": "fun", - "8ball": "fun", - "minesweeper": "fun", - - # Moderation Commands - "kick": "moderation", - "ban": "moderation", - "nick": "moderation", - "purge": "moderation", - "hackban": "moderation", - "warnings": "moderation", - "archive": "moderation", - - # SideStore Commands - "sidestore": "sidestore", - "refresh": "sidestore", - "code": "sidestore", - "crash": "sidestore", - "pairing": "sidestore", - "server": "sidestore", - "half": "sidestore", - "sparse": "sidestore", - "afc": "sidestore", - "udid": "sidestore", - - # idevice Commands + "general": "general", + "fun": "fun", "idevice": "idevice", - "noapps": "idevice", - "errorcodes": "idevice", - "developermode": "idevice", - "mountddi": "idevice", + "miscellaneous": "miscellaneous", + "moderation": "moderation", + "sidestore": "sidestore", + "utilities": "utilities", - # Owner Commands "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 - "keanu": "miscellaneous", - "labubu": "miscellaneous", - "piracy": "miscellaneous", - "tryitandsee": "miscellaneous", - "rr": "miscellaneous", } category_descriptions = { @@ -183,6 +138,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 not in ["owner"]: + 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( diff --git a/cogs/idevice/__init__.py b/cogs/idevice/__init__.py new file mode 100644 index 0000000..de3ad81 --- /dev/null +++ b/cogs/idevice/__init__.py @@ -0,0 +1,126 @@ +import discord +from discord import app_commands +from discord.ext import commands +from discord.ext.commands import Context + +from .idevice import ideviceView +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.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}") + + 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") + + @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 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.check(_require_group_prefix) + @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.check(_require_group_prefix) + @commands.hybrid_command( + name="developermode", + description="How to turn on developer mode" + ) + 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" + ) + 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" + ) + 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.help'") + 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 diff --git a/cogs/miscellaneous/__init__.py b/cogs/miscellaneous/__init__.py new file mode 100644 index 0000000..1314059 --- /dev/null +++ b/cogs/miscellaneous/__init__.py @@ -0,0 +1,112 @@ +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="misc"): + def __init__(self, bot) -> None: + self.bot = bot + super().__init__() + + @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 `.misc ` or `/misc `.", + 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}") + + 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") + + @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.check(_require_group_prefix) + @commands.hybrid_command( + name="rr", + description="Rickroll" + ) + 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" + ) + 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" + ) + 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" + ) + async def piracy(self, context): + return await piracy_command()(self, context) + + @commands.check(_require_group_prefix) + @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 diff --git a/cogs/moderation/__init__.py b/cogs/moderation/__init__.py new file mode 100644 index 0000000..459ae0d --- /dev/null +++ b/cogs/moderation/__init__.py @@ -0,0 +1,139 @@ +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.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." + ) + 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." + ) + 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." + ) + 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." + ) + 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." + ) + 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." + ) + 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." + ) + 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 diff --git a/cogs/sidestore/__init__.py b/cogs/sidestore/__init__.py new file mode 100644 index 0000000..6c28437 --- /dev/null +++ b/cogs/sidestore/__init__.py @@ -0,0 +1,198 @@ +import discord +from discord import app_commands +from discord.ext import commands +from discord.ext.commands import Context + +from .sidestore import SidestoreView +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.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}") + + 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") + + @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 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.check(_require_group_prefix) + @commands.hybrid_command( + name="refresh", + description="Help with refreshing or installing apps" + ) + 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" + ) + 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" + ) + 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" + ) + 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" + ) + 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" + ) + 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" + ) + 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" + ) + 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" + ) + 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.help'") + 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..09f6ea1 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" + name="help", 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 diff --git a/cogs/utilities/__init__.py b/cogs/utilities/__init__.py new file mode 100644 index 0000000..dd23422 --- /dev/null +++ b/cogs/utilities/__init__.py @@ -0,0 +1,56 @@ +import discord +from discord.ext import commands +from discord.ext.commands import Context + +from .translate import translate_command + +class Utilities(commands.GroupCog, name="utils"): + def __init__(self, bot) -> None: + self.bot = bot + super().__init__() + + @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 `.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) + + 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}") + + 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" + ) + 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