From 1eff6c9f53b0cb52d526c86e7d519ffe9b6e07de Mon Sep 17 00:00:00 2001 From: neoarz Date: Sun, 2 Nov 2025 23:32:52 -0500 Subject: [PATCH] chore: ruff formatting --- README.md | 2 +- bot.py | 71 +-- cogs/botinfo.py | 104 +++-- cogs/events/__init__.py | 15 +- cogs/events/baitbot.py | 245 +++++++---- cogs/fun/__init__.py | 21 +- cogs/fun/coinflip.py | 23 +- cogs/fun/eightball.py | 7 +- cogs/fun/minesweeper.py | 100 +++-- cogs/fun/randomfact.py | 7 +- cogs/fun/rockpaperscissors.py | 19 +- cogs/general/__init__.py | 25 +- cogs/general/feedback.py | 24 +- cogs/general/ping.py | 7 +- cogs/general/serverinfo.py | 177 ++++---- cogs/general/uptime.py | 24 +- cogs/general/userinfo.py | 664 ++++++++++++++++------------- cogs/help.py | 146 ++++--- cogs/idevice/__init__.py | 39 +- cogs/idevice/developermode.py | 38 +- cogs/idevice/error_codes.py | 52 ++- cogs/idevice/idevice.py | 196 +++++---- cogs/idevice/mountddi.py | 58 +-- cogs/idevice/noapps.py | 43 +- cogs/media/__init__.py | 43 +- cogs/media/download.py | 313 ++++++++++---- cogs/media/img2gif.py | 107 +++-- cogs/media/mcquote.py | 76 ++-- cogs/media/tts.py | 119 +++--- cogs/media/tweety.py | 297 ++++++++----- cogs/melonx/__init__.py | 52 +-- cogs/melonx/error.py | 47 +- cogs/melonx/gamecrash.py | 61 +-- cogs/melonx/ios26.py | 61 +-- cogs/melonx/legal.py | 88 ++-- cogs/melonx/melonx.py | 79 ++-- cogs/melonx/mods.py | 61 +-- cogs/melonx/requirements.py | 49 ++- cogs/melonx/transfer.py | 72 ++-- cogs/miscellaneous/__init__.py | 96 ++--- cogs/miscellaneous/color.py | 39 +- cogs/miscellaneous/depart.py | 2 +- cogs/miscellaneous/docs.py | 20 +- cogs/miscellaneous/dontasktoask.py | 8 +- cogs/miscellaneous/duck.py | 6 +- cogs/miscellaneous/keanu.py | 10 +- cogs/miscellaneous/labubu.py | 8 +- cogs/miscellaneous/piracy.py | 2 +- cogs/miscellaneous/rickroll.py | 2 +- cogs/miscellaneous/sigma.py | 2 +- cogs/miscellaneous/silly.py | 6 +- cogs/miscellaneous/support.py | 10 +- cogs/miscellaneous/tryitandsee.py | 4 +- cogs/moderation/__init__.py | 107 +++-- cogs/moderation/archive.py | 12 +- cogs/moderation/ban.py | 168 +++++--- cogs/moderation/hackban.py | 14 +- cogs/moderation/kick.py | 86 ++-- cogs/moderation/nick.py | 24 +- cogs/moderation/purge.py | 24 +- cogs/moderation/timeout.py | 74 +++- cogs/moderation/warnings.py | 48 ++- cogs/owner/cog_management.py | 50 ++- cogs/owner/invite.py | 26 +- cogs/owner/logs.py | 76 ++-- cogs/owner/say.py | 46 +- cogs/owner/shutdown.py | 16 +- cogs/owner/sync.py | 37 +- cogs/sidestore/__init__.py | 58 ++- cogs/sidestore/afc.py | 48 ++- cogs/sidestore/code.py | 76 ++-- cogs/sidestore/crash.py | 45 +- cogs/sidestore/half.py | 60 +-- cogs/sidestore/pairing.py | 70 +-- cogs/sidestore/refresh.py | 50 ++- cogs/sidestore/server.py | 58 +-- cogs/sidestore/sidestore.py | 87 ++-- cogs/sidestore/sparse.py | 48 ++- cogs/sidestore/udid.py | 46 +- cogs/utilities/__init__.py | 54 ++- cogs/utilities/codepreview.py | 405 ++++++++++-------- cogs/utilities/dictionary.py | 110 +++-- cogs/utilities/translate.py | 339 ++++++++------- requirements.txt | 3 +- utils/__init__.py | 14 +- utils/ascii_art.py | 33 +- utils/checks.py | 4 +- utils/contributors.py | 67 +-- utils/logging.py | 8 +- utils/signal.py | 2 +- utils/time.py | 2 +- 91 files changed, 3824 insertions(+), 2518 deletions(-) diff --git a/README.md b/README.md index 617c3ab..36f35d8 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ ## Commands -![Total Commands](https://img.shields.io/badge/Total%20Commands-70-5865F2) +![Total Commands](https://img.shields.io/badge/Total%20Commands-71-5865F2) | Command group | Subcommands | | ------------ | --- | diff --git a/bot.py b/bot.py index 5f64987..c55bfdb 100644 --- a/bot.py +++ b/bot.py @@ -26,7 +26,7 @@ intents.dm_messages = True intents.dm_reactions = True intents.dm_typing = True intents.emojis = True -intents.messages = True +intents.messages = True intents.reactions = True intents.typing = True intents.voice_states = True @@ -43,7 +43,6 @@ intents.presences = True intents.members = True - logger = setup_logger() @@ -67,7 +66,7 @@ class DiscordBot(commands.Bot): ) as db: with open( f"{os.path.realpath(os.path.dirname(__file__))}/database/schema.sql", - encoding = "utf-8" + encoding="utf-8", ) as file: await db.executescript(file.read()) await db.commit() @@ -75,11 +74,13 @@ class DiscordBot(commands.Bot): async def load_cogs(self) -> None: cogs_path = f"{os.path.realpath(os.path.dirname(__file__))}/cogs" disabled_env = os.getenv("DISABLED_COGS", "") - disabled_cogs = {entry.strip().lower() for entry in disabled_env.split(",") if entry.strip()} - + disabled_cogs = { + entry.strip().lower() for entry in disabled_env.split(",") if entry.strip() + } + 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('__'): + if os.path.isdir(folder_path) and not folder.startswith("__"): init_file = os.path.join(folder_path, "__init__.py") if os.path.exists(init_file): try: @@ -93,23 +94,30 @@ class DiscordBot(commands.Bot): ) else: for file in os.listdir(folder_path): - if file.endswith(".py") and not file.startswith('__'): + 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}'") + 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}'") + 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('__'): + if file.endswith(".py") and not file.startswith("__"): extension = file[:-3] if extension.lower() in disabled_cogs: self.logger.info(f"Skipped disabled extension '{extension}'") @@ -140,7 +148,7 @@ class DiscordBot(commands.Bot): self.logger.info( f"Running on: {platform.system()} {platform.release()} ({os.name})" ) - + try: app_info = await self.application_info() if app_info.team: @@ -148,11 +156,12 @@ class DiscordBot(commands.Bot): for member in app_info.team.members: self.logger.info(f"Team member: {member.name} (ID: {member.id})") else: - self.logger.info(f"Bot owner: {app_info.owner.name} (ID: {app_info.owner.id})") + self.logger.info( + f"Bot owner: {app_info.owner.name} (ID: {app_info.owner.id})" + ) except Exception as e: self.logger.error(f"Error fetching application info: {e}") - - + await self.init_db() await self.load_cogs() self.status_task.start() @@ -169,20 +178,20 @@ class DiscordBot(commands.Bot): if self._shutdown: return self._shutdown = True - + self.logger.info("Starting shutdown process...") - + if self.status_task and not self.status_task.is_being_cancelled(): self.status_task.cancel() self.logger.info("Status task cancelled") - + if self.database and self.database.connection: try: await self.database.connection.close() self.logger.warning("Database connection closed") except Exception as e: self.logger.error(f"Error closing database connection: {e}") - + try: await super().close() self.logger.critical("Bot shutdown complete") @@ -192,11 +201,13 @@ class DiscordBot(commands.Bot): async def on_message(self, message: discord.Message) -> None: if message.author == self.user or message.author.bot: return - + if self.user in message.mentions: try: emoji_string = "" - self.logger.debug(f"Attempting to react with PandaPing emoji: {emoji_string}") + self.logger.debug( + f"Attempting to react with PandaPing emoji: {emoji_string}" + ) await message.add_reaction(emoji_string) self.logger.debug("Successfully reacted with PandaPing emoji") except Exception as e: @@ -206,17 +217,19 @@ class DiscordBot(commands.Bot): await message.add_reaction("๐Ÿ‘‹") self.logger.debug("Successfully reacted with wave emoji") except Exception as fallback_error: - self.logger.debug(f"Failed to react with fallback emoji: {fallback_error}") + self.logger.debug( + f"Failed to react with fallback emoji: {fallback_error}" + ) await self.process_commands(message) async def on_command_completion(self, context: Context) -> None: full_command_name = context.command.qualified_name split = full_command_name.split(" ") executed_command = str(split[0]) - + if executed_command.lower() in ["shutdown", "say", "embed"]: return - + if context.guild is not None: self.logger.info( f"Executed {executed_command} command in {context.guild.name} (ID: {context.guild.id}) by {context.author} (ID: {context.author.id})" @@ -287,12 +300,12 @@ class DiscordBot(commands.Bot): bot = DiscordBot() if __name__ == "__main__": - os.system('clear' if os.name == 'posix' else 'cls') - + os.system("clear" if os.name == "posix" else "cls") + print(ascii) - + setup_signal_handlers(bot) - + try: bot.run(os.getenv("TOKEN")) except KeyboardInterrupt: diff --git a/cogs/botinfo.py b/cogs/botinfo.py index 6e3735f..1bc576a 100644 --- a/cogs/botinfo.py +++ b/cogs/botinfo.py @@ -28,7 +28,10 @@ class FeedbackForm(discord.ui.Modal, title="Feedback"): title="Thank You!", description="Your feedback has been submitted, the owners have been notified about it.", color=0x7289DA, - ).set_author(name="Feedback System", icon_url="https://yes.nighty.works/raw/gSxqzV.png"), + ).set_author( + name="Feedback System", + icon_url="https://yes.nighty.works/raw/gSxqzV.png", + ), ephemeral=True, ) @@ -38,7 +41,10 @@ class FeedbackForm(discord.ui.Modal, title="Feedback"): title="New Feedback", description=f"{interaction.user} (<@{interaction.user.id}>) has submitted a new feedback:\n```\n{self.feedback.value}\n```", color=0x7289DA, - ).set_author(name="Feedback System", icon_url="https://yes.nighty.works/raw/gSxqzV.png") + ).set_author( + name="Feedback System", + icon_url="https://yes.nighty.works/raw/gSxqzV.png", + ) ) @@ -46,35 +52,37 @@ class BotInfoView(discord.ui.View): def __init__(self, bot): super().__init__(timeout=None) self.bot = bot - + github_emoji = discord.PartialEmoji(name="githubicon", id=1417717356846776340) github_button = discord.ui.Button( label="GitHub", emoji=github_emoji, url="https://github.com/neoarz/syntrel", - style=discord.ButtonStyle.link + style=discord.ButtonStyle.link, ) self.add_item(github_button) - - feedback_emoji = discord.PartialEmoji(name="ThumbsUpBlueEmoji", id=1426066711500554302) + + feedback_emoji = discord.PartialEmoji( + name="ThumbsUpBlueEmoji", id=1426066711500554302 + ) feedback_button = discord.ui.Button( label="Feedback", emoji=feedback_emoji, style=discord.ButtonStyle.secondary, - custom_id="feedback_button" + custom_id="feedback_button", ) feedback_button.callback = self.feedback_callback self.add_item(feedback_button) - + bug_emoji = discord.PartialEmoji(name="BugHunterBadge", id=1425703361625460856) bug_button = discord.ui.Button( label="Bug Report", emoji=bug_emoji, url="https://github.com/neoarz/Syntrel/issues", - style=discord.ButtonStyle.link + style=discord.ButtonStyle.link, ) self.add_item(bug_button) - + async def feedback_callback(self, interaction: discord.Interaction): feedback_form = FeedbackForm(self.bot) await interaction.response.send_modal(feedback_form) @@ -87,34 +95,46 @@ class BotInfo(commands.Cog, name="botinfo"): @commands.Cog.listener() async def on_guild_join(self, guild): channel = guild.system_channel or next( - (c for c in guild.text_channels if c.permissions_for(guild.me).send_messages), - None + ( + c + for c in guild.text_channels + if c.permissions_for(guild.me).send_messages + ), + None, ) if channel: - ny_tz = pytz.timezone('America/New_York') + ny_tz = pytz.timezone("America/New_York") current_time = datetime.now(ny_tz).strftime("%m/%d/%y, %I:%M %p") - + description_text = ( - "Heyooo! I'm Syntrel, a bot made to help with [SideStore](https://discord.gg/3DwCwpBHfv), [MeloNX](https://discord.gg/Q4VkbkYfmk), and [idevice](https://discord.gg/ZnNcrRT3M8). I even have some cool extras! If you encounter any issues, please file a bug report. If you have any feedback or suggestions, simply select \"Feedback\"! <:HeardPanda:1417619745896660992>\n\n" + 'Heyooo! I\'m Syntrel, a bot made to help with [SideStore](https://discord.gg/3DwCwpBHfv), [MeloNX](https://discord.gg/Q4VkbkYfmk), and [idevice](https://discord.gg/ZnNcrRT3M8). I even have some cool extras! If you encounter any issues, please file a bug report. If you have any feedback or suggestions, simply select "Feedback"! <:HeardPanda:1417619745896660992>\n\n' "**New to Syntrel?** Run `/help` to get started and explore all available commands!\n\n" f"**Owner:** [neoarz](https://discordapp.com/users/1015372540937502851)\n" f"**Python Version:** {platform.python_version()}\n" f"**Discord.py Version:** {discord.__version__}\n" f"**Prefix:** / (Slash Commands) or {self.bot.bot_prefix} for normal commands" ) - + embed = discord.Embed( title="Syntrel Discord Bot", description=description_text, color=0x7289DA, ) - embed.set_author(name="Syntrel", icon_url="https://github.com/neoarz/Syntrel/blob/main/assets/icon.png?raw=true") - embed.set_image(url="https://github.com/neoarz/Syntrel/raw/main/assets/bannerdark.png") - embed.set_footer(text=f"neoarz โ€ข {current_time}", icon_url="https://yes.nighty.works/raw/P1Us35.webp") - + embed.set_author( + name="Syntrel", + icon_url="https://github.com/neoarz/Syntrel/blob/main/assets/icon.png?raw=true", + ) + embed.set_image( + url="https://github.com/neoarz/Syntrel/raw/main/assets/bannerdark.png" + ) + embed.set_footer( + text=f"neoarz โ€ข {current_time}", + icon_url="https://yes.nighty.works/raw/P1Us35.webp", + ) + view = BotInfoView(self.bot) - + await channel.send(embed=embed, view=view) else: self.bot.logger.warning(f"Couldn't find a suitable channel in {guild.name}") @@ -128,49 +148,61 @@ class BotInfo(commands.Cog, name="botinfo"): async def botinfo(self, context: Context) -> None: if context.interaction: await context.interaction.response.defer(ephemeral=False) - - ny_tz = pytz.timezone('America/New_York') + + ny_tz = pytz.timezone("America/New_York") current_time = datetime.now(ny_tz).strftime("%m/%d/%y, %I:%M %p") - + description_text = ( - "Heyooo! I'm Syntrel, a bot made to help with [SideStore](https://discord.gg/3DwCwpBHfv), [MeloNX](https://discord.gg/Q4VkbkYfmk), and [idevice](https://discord.gg/ZnNcrRT3M8). I even have some cool extras! If you encounter any issues, please file a bug report. If you have any feedback or suggestions, simply select \"Feedback\"! <:HeardPanda:1417619745896660992>\n\n" + 'Heyooo! I\'m Syntrel, a bot made to help with [SideStore](https://discord.gg/3DwCwpBHfv), [MeloNX](https://discord.gg/Q4VkbkYfmk), and [idevice](https://discord.gg/ZnNcrRT3M8). I even have some cool extras! If you encounter any issues, please file a bug report. If you have any feedback or suggestions, simply select "Feedback"! <:HeardPanda:1417619745896660992>\n\n' "**New to Syntrel?** Run `/help` to get started and explore all available commands!\n\n" f"**Owner:** [neoarz](https://discordapp.com/users/1015372540937502851)\n" f"**Python Version:** {platform.python_version()}\n" f"**Discord.py Version:** {discord.__version__}\n" f"**Prefix:** / (Slash Commands) or {self.bot.bot_prefix} for normal commands" ) - + embed1 = discord.Embed( title="Syntrel Discord Bot", description=description_text, color=0x7289DA, ) - embed1.set_author(name="Syntrel", icon_url="https://github.com/neoarz/Syntrel/blob/main/assets/icon.png?raw=true") - embed1.set_image(url="https://github.com/neoarz/Syntrel/raw/main/assets/bannerdark.png") - embed1.set_footer(text=f"neoarz โ€ข {current_time}", icon_url="https://yes.nighty.works/raw/P1Us35.webp") - + embed1.set_author( + name="Syntrel", + icon_url="https://github.com/neoarz/Syntrel/blob/main/assets/icon.png?raw=true", + ) + embed1.set_image( + url="https://github.com/neoarz/Syntrel/raw/main/assets/bannerdark.png" + ) + embed1.set_footer( + text=f"neoarz โ€ข {current_time}", + icon_url="https://yes.nighty.works/raw/P1Us35.webp", + ) + embed2 = discord.Embed( title="Contributors", description="Giving credit where it's due! ", color=0x7289DA, ) - + contributors_image = generate_contributors_image() - + view = BotInfoView(self.bot) - + if contributors_image: file = discord.File(contributors_image, filename="contributors.png") embed2.set_image(url="attachment://contributors.png") - + if context.interaction: - await context.interaction.followup.send(embeds=[embed1, embed2], file=file, view=view) + await context.interaction.followup.send( + embeds=[embed1, embed2], file=file, view=view + ) else: await context.send(embeds=[embed1, embed2], file=file, view=view) else: if context.interaction: - await context.interaction.followup.send(embeds=[embed1, embed2], view=view) + await context.interaction.followup.send( + embeds=[embed1, embed2], view=view + ) else: await context.send(embeds=[embed1, embed2], view=view) diff --git a/cogs/events/__init__.py b/cogs/events/__init__.py index 3e4921e..191c2b9 100644 --- a/cogs/events/__init__.py +++ b/cogs/events/__init__.py @@ -29,9 +29,11 @@ class Events(commands.GroupCog, name="events"): embed = discord.Embed( title="Events Commands", description="Use `.events ` or `/events `.", - color=0x7289DA + color=0x7289DA, + ) + embed.set_author( + name="Events", icon_url="https://yes.nighty.works/raw/eW5lLm.webp" ) - embed.set_author(name="Events", icon_url="https://yes.nighty.works/raw/eW5lLm.webp") embed.add_field(name="Available", value="baitbot", inline=False) await context.send(embed=embed) @@ -46,12 +48,11 @@ class Events(commands.GroupCog, name="events"): @has_protected_role() async def events_group_baitbot(self, context: Context): await self._invoke_hybrid(context, "baitbot") - + @commands.check(_require_group_prefix) @has_protected_role() @commands.hybrid_command( - name="baitbot", - description="View bait bot configuration and status." + name="baitbot", description="View bait bot configuration and status." ) async def baitbot(self, context): return await baitbot_command()(self, context) @@ -60,8 +61,8 @@ class Events(commands.GroupCog, name="events"): async def setup(bot) -> None: cog = Events(bot) await bot.add_cog(cog) - + listener = BaitBotListener(bot) await bot.add_cog(listener) - + bot.logger.info("Loaded extension 'events.baitbot'") diff --git a/cogs/events/baitbot.py b/cogs/events/baitbot.py index c8fec79..3a128dd 100644 --- a/cogs/events/baitbot.py +++ b/cogs/events/baitbot.py @@ -42,60 +42,80 @@ BAIT_CONFIGS = { }, } -BAN_REASON = 'Detected bot/scammer in bait channel' +BAN_REASON = "Detected bot/scammer in bait channel" + def has_protected_role(): async def predicate(context: Context): if not context.guild: - context.bot.logger.warning(f'[BAITBOT] Unauthorized baitbot command attempt by {context.author} ({context.author.id}) in DMs') + context.bot.logger.warning( + f"[BAITBOT] Unauthorized baitbot command attempt by {context.author} ({context.author.id}) in DMs" + ) embed = discord.Embed( title="Permission Denied", description="You don't have permission to use this command.", - color=0xE02B2B + color=0xE02B2B, + ) + embed.set_author( + name="Events", icon_url="https://yes.nighty.works/raw/C8Hh6o.png" ) - embed.set_author(name="Events", icon_url="https://yes.nighty.works/raw/C8Hh6o.png") await context.send(embed=embed, ephemeral=True) return False - - if not hasattr(context.author, 'roles'): - context.bot.logger.warning(f'[BAITBOT] Unauthorized baitbot command attempt by {context.author} ({context.author.id}) in {context.guild.name} - no roles') + + if not hasattr(context.author, "roles"): + context.bot.logger.warning( + f"[BAITBOT] Unauthorized baitbot command attempt by {context.author} ({context.author.id}) in {context.guild.name} - no roles" + ) embed = discord.Embed( title="Permission Denied", description="You don't have permission to use this command.", - color=0xE02B2B + color=0xE02B2B, + ) + embed.set_author( + name="Events", icon_url="https://yes.nighty.works/raw/C8Hh6o.png" ) - embed.set_author(name="Events", icon_url="https://yes.nighty.works/raw/C8Hh6o.png") await context.send(embed=embed, ephemeral=True) return False - + for config in BAIT_CONFIGS.values(): protected_role_id = config.get("protected_role_id") if protected_role_id: protected_role = context.guild.get_role(protected_role_id) if protected_role: for role in context.author.roles: - if role.position >= protected_role.position and role.id != context.guild.default_role.id: + if ( + role.position >= protected_role.position + and role.id != context.guild.default_role.id + ): return True - - context.bot.logger.warning(f'[BAITBOT] Unauthorized baitbot command attempt by {context.author} ({context.author.id}) in {context.guild.name} - insufficient role permissions') + + context.bot.logger.warning( + f"[BAITBOT] Unauthorized baitbot command attempt by {context.author} ({context.author.id}) in {context.guild.name} - insufficient role permissions" + ) embed = discord.Embed( title="Permission Denied", description="You don't have permission to use this command.", - color=0xE02B2B + color=0xE02B2B, + ) + embed.set_author( + name="Events", icon_url="https://yes.nighty.works/raw/C8Hh6o.png" ) - embed.set_author(name="Events", icon_url="https://yes.nighty.works/raw/C8Hh6o.png") await context.send(embed=embed, ephemeral=True) return False + return commands.check(predicate) + def baitbot_command(): async def wrapper(self, context: Context): embed = discord.Embed( title="Bait Bot", description="Bans people who post in a specific channel.", - color=0x7289DA + color=0x7289DA, + ) + embed.set_author( + name="Events", icon_url="https://yes.nighty.works/raw/C8Hh6o.png" ) - embed.set_author(name="Events", icon_url="https://yes.nighty.works/raw/C8Hh6o.png") found_config = False if BAIT_CONFIGS: @@ -112,24 +132,36 @@ def baitbot_command(): channel_displays = [] for channel_id in channel_ids: channel = context.guild.get_channel(channel_id) - channel_display = f"<#{channel_id}> (`{channel_id}`)" if channel else f"`{channel_id}`" + channel_display = ( + f"<#{channel_id}> (`{channel_id}`)" + if channel + else f"`{channel_id}`" + ) channel_displays.append(channel_display) - - channels_text = "\n".join(channel_displays) if channel_displays else "Not set" + + channels_text = ( + "\n".join(channel_displays) if channel_displays else "Not set" + ) role = context.guild.get_role(role_id) - role_display = f"<@&{role_id}> (`{role_id}`)" if role else f"`{role_id}`" - + role_display = ( + f"<@&{role_id}> (`{role_id}`)" if role else f"`{role_id}`" + ) + log_channel_id = config.get("log_channel_id") log_channel = None if log_channel_id: log_channel = context.guild.get_channel(log_channel_id) - log_display = f"<#{log_channel_id}> (`{log_channel_id}`)" if log_channel else (f"`{log_channel_id}`" if log_channel_id else "Not set") + log_display = ( + f"<#{log_channel_id}> (`{log_channel_id}`)" + if log_channel + else (f"`{log_channel_id}`" if log_channel_id else "Not set") + ) embed.add_field( name="\u200b", value=f"Channels:\n{channels_text}\n\nProtected Role:\n{role_display}\n\nLog Channel:\n{log_display}", - inline=False + inline=False, ) found_config = True @@ -137,15 +169,17 @@ def baitbot_command(): embed.add_field( name="No Configurations", value="No bait channels configured for this server", - inline=False + inline=False, ) if context.guild and context.guild.icon: embed.set_thumbnail(url=context.guild.icon.url) await context.send(embed=embed) + return wrapper + class BaitBotListener(commands.Cog): def __init__(self, bot): self.bot = bot @@ -154,137 +188,200 @@ class BaitBotListener(commands.Cog): async def on_message(self, message: discord.Message): if message.guild is None: return - + if message.author.bot: return - + bait_config = None config_name = None for name, config in BAIT_CONFIGS.items(): if message.guild.id != config.get("guild_id"): continue - + channel_ids = config.get("channel_ids", []) if not channel_ids: channel_id = config.get("channel_id") if channel_id: channel_ids = [channel_id] - + if message.channel.id in channel_ids: bait_config = config config_name = name break - + if not bait_config: return - + protected_role_id = bait_config.get("protected_role_id") is_protected = False - if protected_role_id and hasattr(message.author, 'roles'): + if protected_role_id and hasattr(message.author, "roles"): protected_role = message.guild.get_role(protected_role_id) if protected_role: for role in message.author.roles: - if role.position >= protected_role.position and role.id != message.guild.default_role.id: - self.bot.logger.info(f'[BAITBOT] Skipped banning {message.author} ({message.author.id}) in #{message.channel.name}: protected role ({role.name})') + if ( + role.position >= protected_role.position + and role.id != message.guild.default_role.id + ): + self.bot.logger.info( + f"[BAITBOT] Skipped banning {message.author} ({message.author.id}) in #{message.channel.name}: protected role ({role.name})" + ) is_protected = True break - + message_content = message.content if message.content else "*No text content*" message_attachments = message.attachments message_embeds = message.embeds - + try: await message.delete() - self.bot.logger.info(f'[BAITBOT] Deleted message from {message.author} in #{message.channel.name}') + self.bot.logger.info( + f"[BAITBOT] Deleted message from {message.author} in #{message.channel.name}" + ) except Exception as e: - self.bot.logger.warning(f'[BAITBOT] Could not delete message from {message.author}: {e}') + self.bot.logger.warning( + f"[BAITBOT] Could not delete message from {message.author}: {e}" + ) banned = False if not is_protected: try: - self.bot.logger.warning(f'[BAITBOT] Detected user in bait channel [{config_name}]: {message.author.name} ({message.author.id}) in #{message.channel.name}') - + self.bot.logger.warning( + f"[BAITBOT] Detected user in bait channel [{config_name}]: {message.author.name} ({message.author.id}) in #{message.channel.name}" + ) + if not message.guild.me.guild_permissions.ban_members: - self.bot.logger.error(f'[BAITBOT] No permission to ban members in {message.guild.name}') + self.bot.logger.error( + f"[BAITBOT] No permission to ban members in {message.guild.name}" + ) else: try: - await message.author.ban(reason=BAN_REASON, delete_message_days=7) - self.bot.logger.info(f'[BAITBOT] Banned {message.author.name} - deleted messages from last 7 days') + await message.author.ban( + reason=BAN_REASON, delete_message_days=7 + ) + self.bot.logger.info( + f"[BAITBOT] Banned {message.author.name} - deleted messages from last 7 days" + ) banned = True except discord.Forbidden: - self.bot.logger.error(f'[BAITBOT] Could not ban {message.author.name}: missing permissions') + self.bot.logger.error( + f"[BAITBOT] Could not ban {message.author.name}: missing permissions" + ) except Exception as e: - self.bot.logger.error(f'[BAITBOT] Error banning {message.author.name}: {e}') - + self.bot.logger.error( + f"[BAITBOT] Error banning {message.author.name}: {e}" + ) + if banned: await asyncio.sleep(2) try: - await message.guild.unban(message.author, reason="Auto-unban after cleanup") - self.bot.logger.info(f'[BAITBOT] Unbanned {message.author.name} - cleanup complete') + await message.guild.unban( + message.author, reason="Auto-unban after cleanup" + ) + self.bot.logger.info( + f"[BAITBOT] Unbanned {message.author.name} - cleanup complete" + ) except Exception as e: - self.bot.logger.error(f'[BAITBOT] Error unbanning {message.author.name}: {e}') + self.bot.logger.error( + f"[BAITBOT] Error unbanning {message.author.name}: {e}" + ) except Exception as e: - self.bot.logger.error(f'[BAITBOT] Error handling bait message: {e}') - + self.bot.logger.error(f"[BAITBOT] Error handling bait message: {e}") + log_channel_id = bait_config.get("log_channel_id") if log_channel_id: try: log_channel = self.bot.get_channel(log_channel_id) if log_channel: - action_text = "Message deleted (user banned and unbanned)" if banned else "Message deleted (protected user)" if is_protected else "Message deleted" + action_text = ( + "Message deleted (user banned and unbanned)" + if banned + else "Message deleted (protected user)" + if is_protected + else "Message deleted" + ) log_embed = discord.Embed( title="Bait Bot", description=action_text, color=0xE02B2B, - timestamp=message.created_at + timestamp=message.created_at, ) - log_embed.set_author(name=str(message.author), icon_url=message.author.display_avatar.url) - log_embed.add_field(name="User", value=message.author.mention, inline=True) - log_embed.add_field(name="Channel", value=message.channel.mention, inline=True) - + log_embed.set_author( + name=str(message.author), + icon_url=message.author.display_avatar.url, + ) + log_embed.add_field( + name="User", value=message.author.mention, inline=True + ) + log_embed.add_field( + name="Channel", value=message.channel.mention, inline=True + ) + combined_content = [] if message_content and message_content != "*No text content*": combined_content.append(message_content) - + image_url = None if message_attachments: for attachment in message_attachments: - if attachment.content_type and attachment.content_type.startswith("image/"): + if ( + attachment.content_type + and attachment.content_type.startswith("image/") + ): if not image_url: image_url = attachment.url combined_content.append(attachment.filename) - - content_text = "\n".join(combined_content) if combined_content else "*No content*" + + content_text = ( + "\n".join(combined_content) + if combined_content + else "*No content*" + ) if len(content_text) > 1000: content_text = content_text[:997] + "..." - - log_embed.add_field(name="Content", value=f"```\n{content_text}\n```", inline=False) - + + log_embed.add_field( + name="Content", value=f"```\n{content_text}\n```", inline=False + ) + if image_url: log_embed.set_image(url=image_url) - + if message_embeds: embed_info = [] for embed in message_embeds[:3]: embed_desc = f"**Embed:** {embed.title or 'Untitled'}\n" if embed.description: - desc_text = embed.description[:200] + "..." if len(embed.description) > 200 else embed.description + desc_text = ( + embed.description[:200] + "..." + if len(embed.description) > 200 + else embed.description + ) embed_desc += f"{desc_text}\n" if embed.url: embed_desc += f"[Link]({embed.url})" embed_info.append(embed_desc) if embed_info: - log_embed.add_field(name="Embeds", value="\n\n".join(embed_info), inline=False) - + log_embed.add_field( + name="Embeds", + value="\n\n".join(embed_info), + inline=False, + ) + log_embed.set_footer(text=f"Message ID: {message.id}") - + try: await log_channel.send(embed=log_embed) - self.bot.logger.info(f'[BAITBOT] Sent log to #{log_channel.name}') + self.bot.logger.info( + f"[BAITBOT] Sent log to #{log_channel.name}" + ) except discord.Forbidden: - self.bot.logger.error(f'[BAITBOT] No permission to send log to #{log_channel.name}') + self.bot.logger.error( + f"[BAITBOT] No permission to send log to #{log_channel.name}" + ) except Exception as e: - self.bot.logger.error(f'[BAITBOT] Error sending log: {e}') + self.bot.logger.error(f"[BAITBOT] Error sending log: {e}") else: - self.bot.logger.warning(f'[BAITBOT] Log channel {log_channel_id} not found') + self.bot.logger.warning( + f"[BAITBOT] Log channel {log_channel_id} not found" + ) except Exception as e: - self.bot.logger.error(f'[BAITBOT] Error handling log channel: {e}') + self.bot.logger.error(f"[BAITBOT] Error handling log channel: {e}") diff --git a/cogs/fun/__init__.py b/cogs/fun/__init__.py index 5248942..e61adb3 100644 --- a/cogs/fun/__init__.py +++ b/cogs/fun/__init__.py @@ -22,10 +22,16 @@ class Fun(commands.GroupCog, name="fun"): embed = discord.Embed( title="Fun Commands", description="Use `.fun ` or slash `/fun `.", - color=0x7289DA + 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, ) - 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): @@ -67,8 +73,7 @@ class Fun(commands.GroupCog, name="fun"): @commands.check(_require_group_prefix) @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): return await coinflip_command()(self, context) @@ -83,8 +88,7 @@ class Fun(commands.GroupCog, name="fun"): @commands.check(_require_group_prefix) @commands.hybrid_command( - name="minesweeper", - description="Play a buttoned minesweeper mini-game." + name="minesweeper", description="Play a buttoned minesweeper mini-game." ) async def minesweeper(self, context): return await minesweeper_command()(self, context) @@ -101,10 +105,11 @@ class Fun(commands.GroupCog, name="fun"): 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'") diff --git a/cogs/fun/coinflip.py b/cogs/fun/coinflip.py index 2f68de6..e633456 100644 --- a/cogs/fun/coinflip.py +++ b/cogs/fun/coinflip.py @@ -2,6 +2,7 @@ import random import discord from discord.ext import commands + class Choice(discord.ui.View): def __init__(self) -> None: super().__init__() @@ -21,19 +22,19 @@ class Choice(discord.ui.View): self.value = "tails" self.stop() + 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): buttons = Choice() embed = discord.Embed( - title="Coinflip", - description="What is your bet?", - color=0x7289DA + title="Coinflip", description="What is your bet?", color=0x7289DA + ) + embed.set_author( + name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp" ) - embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp") message = await context.send(embed=embed, view=buttons) await buttons.wait() result = random.choice(["heads", "tails"]) @@ -43,14 +44,18 @@ def coinflip_command(): description=f"Correct! You guessed `{buttons.value}` and I flipped the coin to `{result}`.", color=0x00FF00, ) - embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp") + embed.set_author( + name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp" + ) else: embed = discord.Embed( title="Coinflip", description=f"Woops! You guessed `{buttons.value}` and I flipped the coin to `{result}`, better luck next time!", color=0xE02B2B, ) - embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp") + embed.set_author( + name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp" + ) await message.edit(embed=embed, view=None, content=None) - + return coinflip diff --git a/cogs/fun/eightball.py b/cogs/fun/eightball.py index 3625e8c..3f28068 100644 --- a/cogs/fun/eightball.py +++ b/cogs/fun/eightball.py @@ -2,6 +2,7 @@ import random import discord from discord.ext import commands + def eightball_command(): @commands.hybrid_command( name="8ball", @@ -35,8 +36,10 @@ def eightball_command(): description=f"{random.choice(answers)}", color=0x7289DA, ) - embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp") + 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) - + return eight_ball diff --git a/cogs/fun/minesweeper.py b/cogs/fun/minesweeper.py index c4a7d1a..3c9c53a 100644 --- a/cogs/fun/minesweeper.py +++ b/cogs/fun/minesweeper.py @@ -5,9 +5,12 @@ from discord.ext import commands import asyncio import time + class RowButton(discord.ui.Button): def __init__(self, ctx, label, custom_id, bombs, board): - super().__init__(label=label, style=discord.ButtonStyle.grey, custom_id=custom_id) + super().__init__( + label=label, style=discord.ButtonStyle.grey, custom_id=custom_id + ) self.ctx = ctx self.bombs = bombs self.board = board @@ -15,16 +18,18 @@ class RowButton(discord.ui.Button): async def callback(self, interaction): assert self.view is not None view: MsView = self.view - + current_time = time.time() if current_time - view.last_interaction < 0.5: try: - return await interaction.response.send_message("Please wait before clicking again.", ephemeral=True) + return await interaction.response.send_message( + "Please wait before clicking again.", ephemeral=True + ) except: return - + view.last_interaction = current_time - + try: await interaction.response.defer() except discord.errors.HTTPException as e: @@ -32,7 +37,7 @@ class RowButton(discord.ui.Button): await asyncio.sleep(1) return raise - + if interaction.user.id != self.ctx.author.id: return await interaction.followup.send( "You cannot interact with these buttons.", ephemeral=True @@ -40,12 +45,14 @@ class RowButton(discord.ui.Button): b_id = self.custom_id if int(b_id[5:]) in view.moves: - return await interaction.followup.send("That part is already taken.", ephemeral=True) - + return await interaction.followup.send( + "That part is already taken.", ephemeral=True + ) + if not view.bombs_generated: view.generate_bombs(int(b_id[5:])) self.bombs = view.bombs - + if int(b_id[5:]) in self.bombs: await view.RevealBombs(b_id, view.board) else: @@ -74,13 +81,13 @@ class RowButton(discord.ui.Button): return count count = checkpos(count, rawpos, pos) - number = 8-len(count) + number = 8 - len(count) self.label = str(number) if number > 0 else "0" self.style = discord.ButtonStyle.green pos = int(b_id[5:]) - view.board[view.GetBoardRow(pos)][ - view.GetBoardPos(pos) - ] = str(number) if number > 0 else "0" + view.board[view.GetBoardRow(pos)][view.GetBoardPos(pos)] = ( + str(number) if number > 0 else "0" + ) view.moves.append(pos) if len(view.moves) + len(self.bombs) == 25: await view.EndGame() @@ -93,6 +100,7 @@ class RowButton(discord.ui.Button): else: raise + class MsView(discord.ui.View): def __init__(self, ctx, options, bomb_count, board): super().__init__(timeout=300) @@ -106,34 +114,36 @@ class MsView(discord.ui.View): self.ctx = ctx self.message = None self.last_interaction = 0 - + def generate_bombs(self, first_move_pos): """Generate bombs excluding the first clicked position""" bombpositions = [] - excluded_positions = [0, 4, 20, 24, first_move_pos] - + excluded_positions = [0, 4, 20, 24, first_move_pos] + while len(bombpositions) < self.bomb_count: random_index = random.randint(0, 24) - if random_index not in bombpositions and random_index not in excluded_positions: + if ( + random_index not in bombpositions + and random_index not in excluded_positions + ): bombpositions.append(random_index) - + self.bombs = bombpositions self.bombs_generated = True - for button in self.children: if isinstance(button, RowButton): button.bombs = self.bombs - + async def on_timeout(self): for button in self.children: button.disabled = True embed = discord.Embed( - title="Minesweeper", - description="Game timed out!", - color=0xFF0000 + title="Minesweeper", description="Game timed out!", color=0xFF0000 + ) + embed.set_author( + name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp" ) - embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp") try: await self.message.edit(embed=embed, view=self) except: @@ -147,14 +157,14 @@ class MsView(discord.ui.View): button.label = "๐Ÿ’ฃ" button.style = discord.ButtonStyle.red self.board[self.GetBoardRow(pos)][self.GetBoardPos(pos)] = "๐Ÿ’ฃ" - + embed = discord.Embed( - title="Minesweeper", - description="Game Ended. You won!", - color=0x00FF00 + title="Minesweeper", description="Game Ended. You won!", color=0x00FF00 ) - embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp") - + embed.set_author( + name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp" + ) + try: await self.message.edit(embed=embed, view=self) except discord.errors.HTTPException as e: @@ -200,7 +210,7 @@ class MsView(discord.ui.View): async def RevealBombs(self, b_id, board): bombemo = "๐Ÿ’ฃ" - + for button in self.children: button.disabled = True if button.custom_id == b_id: @@ -214,17 +224,17 @@ class MsView(discord.ui.View): button.label = bombemo button.style = discord.ButtonStyle.red pos = int(button.custom_id[5:]) - self.board[self.GetBoardRow(pos)][ - self.GetBoardPos(pos) - ] = bombemo - + self.board[self.GetBoardRow(pos)][self.GetBoardPos(pos)] = bombemo + embed = discord.Embed( title="Minesweeper", description=f"๐Ÿ’ฅ BOOM! You hit a bomb. Game Over!\n-# gg {self.ctx.author.mention}", - color=0xE02B2B + color=0xE02B2B, ) - embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp") - + embed.set_author( + name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp" + ) + try: await self.message.edit(embed=embed, view=self) except discord.errors.HTTPException as e: @@ -234,10 +244,10 @@ class MsView(discord.ui.View): raise self.stop() + def minesweeper_command(): @commands.hybrid_command( - name="minesweeper", - description="Play a buttoned minesweeper mini-game." + name="minesweeper", description="Play a buttoned minesweeper mini-game." ) async def minesweeper(self, context): board = [["แžตแžต "] * 5 for _ in range(5)] @@ -253,12 +263,14 @@ def minesweeper_command(): embed = discord.Embed( title="Minesweeper", description=f"๐Ÿ’ฃ Total Bombs: `{bomb_count}`\n\nClick the buttons to reveal the grid. Avoid the bombs!", - color=0x7289DA + color=0x7289DA, ) - embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp") - + embed.set_author( + name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp" + ) + view = MsView(context, ExtractBlocks(), bomb_count, board) message = await context.send(embed=embed, view=view) view.message = message - + return minesweeper diff --git a/cogs/fun/randomfact.py b/cogs/fun/randomfact.py index 7a9f500..2662479 100644 --- a/cogs/fun/randomfact.py +++ b/cogs/fun/randomfact.py @@ -2,6 +2,7 @@ import aiohttp import discord from discord.ext import commands + def randomfact_command(): @commands.hybrid_command(name="randomfact", description="Get a random fact.") async def randomfact(self, context): @@ -10,7 +11,9 @@ def randomfact_command(): description="This command is currently disabled.", color=0xE02B2B, ) - embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp") + embed.set_author( + name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp" + ) await context.send(embed=embed) - + return randomfact diff --git a/cogs/fun/rockpaperscissors.py b/cogs/fun/rockpaperscissors.py index b9bb943..a9c6f4a 100644 --- a/cogs/fun/rockpaperscissors.py +++ b/cogs/fun/rockpaperscissors.py @@ -2,6 +2,7 @@ import random import discord from discord.ext import commands + class RockPaperScissors(discord.ui.Select): def __init__(self) -> None: options = [ @@ -34,12 +35,14 @@ class RockPaperScissors(discord.ui.Select): bot_choice_index = choices[bot_choice] result_embed = discord.Embed(title="Rock Paper Scissors", color=0x7289DA) - result_embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp") + result_embed.set_author( + name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp" + ) winner = (3 + user_choice_index - bot_choice_index) % 3 - + user_mention = interaction.user.mention - + if winner == 0: result_embed.description = f"**That's a draw!** You've chosen {user_choice} and I've chosen {bot_choice}.\n-# gg {user_mention}" result_embed.colour = 0xF59E42 @@ -54,11 +57,13 @@ class RockPaperScissors(discord.ui.Select): embed=result_embed, content=None, view=None ) + class RockPaperScissorsView(discord.ui.View): def __init__(self) -> None: super().__init__() self.add_item(RockPaperScissors()) + def rps_command(): @commands.hybrid_command( name="rps", description="Play the rock paper scissors game against the bot." @@ -68,9 +73,11 @@ def rps_command(): embed = discord.Embed( title="Rock Paper Scissors", description="Please make your choice", - color=0x7289DA + color=0x7289DA, + ) + embed.set_author( + name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp" ) - embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp") await context.send(embed=embed, view=view) - + return rock_paper_scissors diff --git a/cogs/general/__init__.py b/cogs/general/__init__.py index e83d69a..742ebdf 100644 --- a/cogs/general/__init__.py +++ b/cogs/general/__init__.py @@ -33,10 +33,16 @@ class General(commands.GroupCog, name="general"): embed = discord.Embed( title="General Commands", description="Use `.general ` or `/general `.", - color=0x7289DA + color=0x7289DA, + ) + embed.set_author( + name="General", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) + embed.add_field( + name="Available", + value="ping, uptime, serverinfo, userinfo, feedback", + inline=False, ) - embed.set_author(name="General", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - embed.add_field(name="Available", value="ping, uptime, serverinfo, userinfo, feedback", inline=False) await context.send(embed=embed) async def _invoke_hybrid(self, context: Context, name: str, **kwargs): @@ -59,7 +65,9 @@ class General(commands.GroupCog, name="general"): await self._invoke_hybrid(context, "serverinfo") @general_group.command(name="userinfo") - async def general_group_userinfo(self, context: Context, user: discord.User = None, user_id: str = None): + async def general_group_userinfo( + self, context: Context, user: discord.User = None, user_id: str = None + ): await self._invoke_hybrid(context, "userinfo", user=user, user_id=user_id) @general_group.command(name="feedback") @@ -96,24 +104,23 @@ class General(commands.GroupCog, name="general"): description="Get information on a user.", ) @app_commands.describe( - user="User to get info for", - user_id="User ID to get info for" + user="User to get info for", user_id="User ID to get info for" ) async def userinfo(self, context, user: discord.User = None, user_id: str = None): return await userinfo_command()(self, context, user=user, user_id=user_id) @commands.check(_require_group_prefix) @commands.hybrid_command( - name="feedback", - description="Submit a feedback for the owners of the bot" + 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.serverinfo'") diff --git a/cogs/general/feedback.py b/cogs/general/feedback.py index f3f0f84..af0c1b9 100644 --- a/cogs/general/feedback.py +++ b/cogs/general/feedback.py @@ -2,6 +2,7 @@ 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?", @@ -16,10 +17,10 @@ class FeedbackForm(discord.ui.Modal, title="Feeedback"): self.answer = str(self.feedback) self.stop() + def feedback_command(): @commands.hybrid_command( - name="feedback", - description="Submit a feedback for the owners of the bot" + name="feedback", description="Submit a feedback for the owners of the bot" ) async def feedback(self, context): if getattr(context, "interaction", None): @@ -34,7 +35,10 @@ def feedback_command(): title="Thank You!", description="Your feedback has been submitted, the owners have been notified about it.", color=0x7289DA, - ).set_author(name="Feedback System", icon_url="https://yes.nighty.works/raw/gSxqzV.png"), + ).set_author( + name="Feedback System", + icon_url="https://yes.nighty.works/raw/gSxqzV.png", + ), ephemeral=True, ) @@ -44,7 +48,10 @@ def feedback_command(): title="New Feedback", description=f"{interaction.user} (<@{interaction.user.id}>) has submitted a new feedback:\n```\n{feedback_form.answer}\n```", color=0x7289DA, - ).set_author(name="Feedback System", icon_url="https://yes.nighty.works/raw/gSxqzV.png") + ).set_author( + name="Feedback System", + icon_url="https://yes.nighty.works/raw/gSxqzV.png", + ) ) else: embed = discord.Embed( @@ -52,7 +59,10 @@ def feedback_command(): description="This command can only be used as a slash command. Please use `/general feedback` instead.", color=0xE02B2B, ) - embed.set_author(name="Feedback System", icon_url="https://yes.nighty.works/raw/gSxqzV.png") + embed.set_author( + name="Feedback System", + icon_url="https://yes.nighty.works/raw/gSxqzV.png", + ) await context.send(embed=embed) - - return feedback \ No newline at end of file + + return feedback diff --git a/cogs/general/ping.py b/cogs/general/ping.py index bf7de55..87b0a31 100644 --- a/cogs/general/ping.py +++ b/cogs/general/ping.py @@ -1,6 +1,7 @@ import discord from discord.ext import commands + def ping_command(): @commands.hybrid_command( name="ping", @@ -12,7 +13,9 @@ def ping_command(): description=f"The bot latency is {round(self.bot.latency * 1000)}ms.", color=0x7289DA, ) - embed.set_author(name="Ping", icon_url="https://yes.nighty.works/raw/gSxqzV.png") + embed.set_author( + name="Ping", icon_url="https://yes.nighty.works/raw/gSxqzV.png" + ) if getattr(context, "interaction", None): inter = context.interaction if not inter.response.is_done(): @@ -21,5 +24,5 @@ def ping_command(): await inter.followup.send(embed=embed, ephemeral=False) else: await context.send(embed=embed) - + return ping diff --git a/cogs/general/serverinfo.py b/cogs/general/serverinfo.py index f8cb4cf..3e7aed6 100644 --- a/cogs/general/serverinfo.py +++ b/cogs/general/serverinfo.py @@ -1,6 +1,7 @@ import discord from discord.ext import commands + def serverinfo_command(): @commands.hybrid_command( name="serverinfo", @@ -11,123 +12,153 @@ def serverinfo_command(): if context.guild is None: await context.send("This command can only be used in a server, not in DMs!") return - + guild = context.guild - - text_channels = len([c for c in guild.channels if isinstance(c, discord.TextChannel)]) - voice_channels = len([c for c in guild.channels if isinstance(c, discord.VoiceChannel)]) - category_channels = len([c for c in guild.channels if isinstance(c, discord.CategoryChannel)]) - forum_channels = len([c for c in guild.channels if isinstance(c, discord.ForumChannel)]) - stage_channels = len([c for c in guild.channels if isinstance(c, discord.StageChannel)]) - - age_restricted = len([c for c in guild.channels if hasattr(c, 'nsfw') and c.nsfw]) - hidden_channels = len([c for c in guild.channels if c.permissions_for(guild.default_role).view_channel == False]) - + + text_channels = len( + [c for c in guild.channels if isinstance(c, discord.TextChannel)] + ) + voice_channels = len( + [c for c in guild.channels if isinstance(c, discord.VoiceChannel)] + ) + category_channels = len( + [c for c in guild.channels if isinstance(c, discord.CategoryChannel)] + ) + forum_channels = len( + [c for c in guild.channels if isinstance(c, discord.ForumChannel)] + ) + stage_channels = len( + [c for c in guild.channels if isinstance(c, discord.StageChannel)] + ) + + age_restricted = len( + [c for c in guild.channels if hasattr(c, "nsfw") and c.nsfw] + ) + hidden_channels = len( + [ + c + for c in guild.channels + if c.permissions_for(guild.default_role).view_channel == False + ] + ) + managed_roles = len([r for r in guild.roles if r.managed]) - + animated_emojis = len([e for e in guild.emojis if e.animated]) managed_emojis = len([e for e in guild.emojis if e.managed]) unavailable_emojis = len([e for e in guild.emojis if not e.available]) - - png_stickers = len([s for s in guild.stickers if s.format == discord.StickerFormatType.png]) - apng_stickers = len([s for s in guild.stickers if s.format == discord.StickerFormatType.apng]) - gif_stickers = len([s for s in guild.stickers if s.format == discord.StickerFormatType.lottie]) - lottie_stickers = len([s for s in guild.stickers if s.format == discord.StickerFormatType.lottie]) - - online_members = len([m for m in guild.members if m.status == discord.Status.online]) - idle_members = len([m for m in guild.members if m.status == discord.Status.idle]) + + png_stickers = len( + [s for s in guild.stickers if s.format == discord.StickerFormatType.png] + ) + apng_stickers = len( + [s for s in guild.stickers if s.format == discord.StickerFormatType.apng] + ) + gif_stickers = len( + [s for s in guild.stickers if s.format == discord.StickerFormatType.lottie] + ) + lottie_stickers = len( + [s for s in guild.stickers if s.format == discord.StickerFormatType.lottie] + ) + + online_members = len( + [m for m in guild.members if m.status == discord.Status.online] + ) + idle_members = len( + [m for m in guild.members if m.status == discord.Status.idle] + ) dnd_members = len([m for m in guild.members if m.status == discord.Status.dnd]) - offline_members = len([m for m in guild.members if m.status == discord.Status.offline]) - + offline_members = len( + [m for m in guild.members if m.status == discord.Status.offline] + ) + bot_count = len([m for m in guild.members if m.bot]) human_count = guild.member_count - bot_count - + created_delta = discord.utils.utcnow() - guild.created_at years_ago = created_delta.days // 365 - + embed = discord.Embed( - title=f"**Server Name:** {guild.name}", - color=0x7289DA - ).set_author(name="Server Information", icon_url="https://yes.nighty.works/raw/gSxqzV.png") - + title=f"**Server Name:** {guild.name}", color=0x7289DA + ).set_author( + name="Server Information", + icon_url="https://yes.nighty.works/raw/gSxqzV.png", + ) + if guild.icon is not None: embed.set_thumbnail(url=guild.icon.url) - - owner_value = guild.owner.mention if guild.owner else (f"<@{guild.owner_id}>" if guild.owner_id else "Unknown") - embed.add_field( - name="Owner", - value=owner_value, - inline=True + + owner_value = ( + guild.owner.mention + if guild.owner + else (f"<@{guild.owner_id}>" if guild.owner_id else "Unknown") ) - + embed.add_field(name="Owner", value=owner_value, inline=True) + embed.add_field( - name="Created", - value=f"{years_ago} year{'s' if years_ago != 1 else ''} ago", - inline=True + name="Created", + value=f"{years_ago} year{'s' if years_ago != 1 else ''} ago", + inline=True, ) - + embed.add_field( - name="Max Members", - value=f"{guild.max_members:,}" if guild.max_members else "Unknown", - inline=True + name="Max Members", + value=f"{guild.max_members:,}" if guild.max_members else "Unknown", + inline=True, ) - + boost_level = guild.premium_tier boost_count = guild.premium_subscription_count or 0 embed.add_field( - name="Boost Status", - value=f"Level {boost_level}, {boost_count} Boost{'s' if boost_count != 1 else ''}", - inline=False + name="Boost Status", + value=f"Level {boost_level}, {boost_count} Boost{'s' if boost_count != 1 else ''}", + inline=False, ) - + channels_info = f"{text_channels} text, {voice_channels} voice, {category_channels} category" if forum_channels > 0: channels_info += f", {forum_channels} forum" if stage_channels > 0: channels_info += f", {stage_channels} stage" channels_info += f"\n{age_restricted} age restricted, {hidden_channels} hidden" - + embed.add_field( - name=f"Channels ({len(guild.channels)})", - value=channels_info, - inline=True + name=f"Channels ({len(guild.channels)})", value=channels_info, inline=True ) - + roles_info = f"{len(guild.roles)} total\n{managed_roles} managed" embed.add_field( - name=f"Roles ({len(guild.roles)})", - value=roles_info, - inline=True + name=f"Roles ({len(guild.roles)})", value=roles_info, inline=True ) - + emotes_info = f"{len(guild.emojis)} total\n{animated_emojis} animated, {managed_emojis} managed" if unavailable_emojis > 0: emotes_info += f"\n{unavailable_emojis} unavailable" embed.add_field( - name=f"Emotes ({len(guild.emojis)})", - value=emotes_info, - inline=True + name=f"Emotes ({len(guild.emojis)})", value=emotes_info, inline=True ) - + if len(guild.stickers) > 0: stickers_info = f"{len(guild.stickers)} total\n{png_stickers} PNG, {apng_stickers} APNG, {gif_stickers} GIF, {lottie_stickers} Lottie" embed.add_field( - name=f"Stickers ({len(guild.stickers)})", - value=stickers_info, - inline=True + name=f"Stickers ({len(guild.stickers)})", + value=stickers_info, + inline=True, ) - + embed.add_field( - name="Member Count", - value=f"{guild.member_count}", - inline=False + name="Member Count", value=f"{guild.member_count}", inline=False ) - - embed.set_footer(text=f"Server ID: {guild.id} โ€ข Created: {guild.created_at.strftime('%m/%d/%Y')}") - + + embed.set_footer( + text=f"Server ID: {guild.id} โ€ข Created: {guild.created_at.strftime('%m/%d/%Y')}" + ) + if getattr(context, "interaction", None): - await context.interaction.response.send_message(embed=embed, ephemeral=False) + await context.interaction.response.send_message( + embed=embed, ephemeral=False + ) else: await context.send(embed=embed) - - return serverinfo \ No newline at end of file + + return serverinfo diff --git a/cogs/general/uptime.py b/cogs/general/uptime.py index fe8e044..47584b9 100644 --- a/cogs/general/uptime.py +++ b/cogs/general/uptime.py @@ -1,21 +1,29 @@ import discord from discord.ext import commands + class UptimeView(discord.ui.View): def __init__(self, bot): super().__init__(timeout=300) self.bot = bot - @discord.ui.button(emoji="<:RefreshEmoji:1418934990770802891>", style=discord.ButtonStyle.primary) - async def refresh_button(self, interaction: discord.Interaction, button: discord.ui.Button): + @discord.ui.button( + emoji="<:RefreshEmoji:1418934990770802891>", style=discord.ButtonStyle.primary + ) + async def refresh_button( + self, interaction: discord.Interaction, button: discord.ui.Button + ): embed = discord.Embed( title="Bot Uptime", description=f"The bot has been running for {self.bot.get_uptime()}", color=0x7289DA, ) - embed.set_author(name="Uptime", icon_url="https://yes.nighty.works/raw/gSxqzV.png") + embed.set_author( + name="Uptime", icon_url="https://yes.nighty.works/raw/gSxqzV.png" + ) await interaction.response.edit_message(embed=embed, view=self) + def uptime_command(): @commands.hybrid_command( name="uptime", @@ -27,15 +35,19 @@ def uptime_command(): description=f"The bot has been running for **{self.bot.get_uptime()}**", color=0x7289DA, ) - embed.set_author(name="Uptime", icon_url="https://yes.nighty.works/raw/gSxqzV.png") + embed.set_author( + name="Uptime", icon_url="https://yes.nighty.works/raw/gSxqzV.png" + ) view = UptimeView(self.bot) if getattr(context, "interaction", None): inter = context.interaction if not inter.response.is_done(): - await inter.response.send_message(embed=embed, view=view, ephemeral=True) + await inter.response.send_message( + embed=embed, view=view, ephemeral=True + ) else: await inter.followup.send(embed=embed, view=view, ephemeral=True) else: await context.send(embed=embed, view=view) - + return uptime diff --git a/cogs/general/userinfo.py b/cogs/general/userinfo.py index b4bfe73..431bd82 100644 --- a/cogs/general/userinfo.py +++ b/cogs/general/userinfo.py @@ -20,503 +20,582 @@ ACTIVITY_TYPE_NAMES = [ "Watching", "Custom Status", "Competing in", - "Hang Status" + "Hang Status", ] USER_FLAGS = { - 'STAFF': 1 << 0, - 'PARTNER': 1 << 1, - 'HYPESQUAD': 1 << 2, - 'BUG_HUNTER_LEVEL_1': 1 << 3, - 'HYPESQUAD_ONLINE_HOUSE_1': 1 << 6, - 'HYPESQUAD_ONLINE_HOUSE_2': 1 << 7, - 'HYPESQUAD_ONLINE_HOUSE_3': 1 << 8, - 'PREMIUM_EARLY_SUPPORTER': 1 << 9, - 'BUG_HUNTER_LEVEL_2': 1 << 14, - 'VERIFIED_DEVELOPER': 1 << 17, - 'CERTIFIED_MODERATOR': 1 << 18, - 'ACTIVE_DEVELOPER': 1 << 22 + "STAFF": 1 << 0, + "PARTNER": 1 << 1, + "HYPESQUAD": 1 << 2, + "BUG_HUNTER_LEVEL_1": 1 << 3, + "HYPESQUAD_ONLINE_HOUSE_1": 1 << 6, + "HYPESQUAD_ONLINE_HOUSE_2": 1 << 7, + "HYPESQUAD_ONLINE_HOUSE_3": 1 << 8, + "PREMIUM_EARLY_SUPPORTER": 1 << 9, + "BUG_HUNTER_LEVEL_2": 1 << 14, + "VERIFIED_DEVELOPER": 1 << 17, + "CERTIFIED_MODERATOR": 1 << 18, + "ACTIVE_DEVELOPER": 1 << 22, } APPLICATION_FLAGS = { - 'APPLICATION_COMMAND_BADGE': 1 << 23, - 'AUTO_MODERATION_RULE_CREATE_BADGE': 1 << 6 + "APPLICATION_COMMAND_BADGE": 1 << 23, + "AUTO_MODERATION_RULE_CREATE_BADGE": 1 << 6, } BADGE_URLS = { - 'staff': 'https://discord.com/company', - 'partner': 'https://discord.com/partners', - 'certified_moderator': 'https://discord.com/safety', - 'hypesquad': 'https://discord.com/hypesquad', - 'hypesquad_house_1': 'https://discord.com/settings/hypesquad-online', - 'hypesquad_house_2': 'https://discord.com/settings/hypesquad-online', - 'hypesquad_house_3': 'https://discord.com/settings/hypesquad-online', - 'bug_hunter_level_1': 'https://support.discord.com/hc/en-us/articles/360046057772-Discord-Bugs', - 'bug_hunter_level_2': 'https://support.discord.com/hc/en-us/articles/360046057772-Discord-Bugs', - 'active_developer': 'https://support-dev.discord.com/hc/en-us/articles/10113997751447?ref=badge', - 'early_supporter': 'https://discord.com/settings/premium', - 'premium': 'https://discord.com/settings/premium', - 'bot_commands': 'https://discord.com/blog/welcome-to-the-new-era-of-discord-apps?ref=badge', - 'quest_completed': 'https://discord.com/settings/inventory' + "staff": "https://discord.com/company", + "partner": "https://discord.com/partners", + "certified_moderator": "https://discord.com/safety", + "hypesquad": "https://discord.com/hypesquad", + "hypesquad_house_1": "https://discord.com/settings/hypesquad-online", + "hypesquad_house_2": "https://discord.com/settings/hypesquad-online", + "hypesquad_house_3": "https://discord.com/settings/hypesquad-online", + "bug_hunter_level_1": "https://support.discord.com/hc/en-us/articles/360046057772-Discord-Bugs", + "bug_hunter_level_2": "https://support.discord.com/hc/en-us/articles/360046057772-Discord-Bugs", + "active_developer": "https://support-dev.discord.com/hc/en-us/articles/10113997751447?ref=badge", + "early_supporter": "https://discord.com/settings/premium", + "premium": "https://discord.com/settings/premium", + "bot_commands": "https://discord.com/blog/welcome-to-the-new-era-of-discord-apps?ref=badge", + "quest_completed": "https://discord.com/settings/inventory", } BADGE_ICONS = { - 'staff': '<:discordstaff:1426051878155845702>', - 'partner': '<:discordpartner:1426051933608873986>', - 'certified_moderator': '<:discordmod:1426051921826943050>', - 'hypesquad': '<:hypesquadevents:1426051833536970852>', - 'hypesquad_house_1': '<:hypesquadbravery:1426051916739383297>', - 'hypesquad_house_2': '<:hypesquadbrilliance:1426051849433387068>', - 'hypesquad_house_3': '<:hypesquadbalance:1426051905179750495>', - 'bug_hunter_level_1': '<:discordbughunter1:1426052002193997895>', - 'bug_hunter_level_2': '<:discordbughunter2:1426052028257406987>', - 'active_developer': '<:activedeveloper:1426051981658685552>', - 'verified_developer': '<:discordbotdev:1426051827077480570>', - 'early_supporter': '<:discordearlysupporter:1426052023165517924>', - 'premium': '<:discordnitro:1426051911123206296>', - 'guild_booster_lvl1': '<:discordboost1:1426052007294144605>', - 'guild_booster_lvl2': '<:discordboost2:1426051986985582692>', - 'guild_booster_lvl3': '<:discordboost3:1426051991812964434>', - 'guild_booster_lvl4': '<:discordboost4:1426051955473645671>', - 'guild_booster_lvl5': '<:discordboost5:1426051960456609824>', - 'guild_booster_lvl6': '<:discordboost6:1426051976583712918>', - 'guild_booster_lvl7': '<:discordboost7:1426051965808410634>', - 'guild_booster_lvl8': '<:discordboost8:1426051844014342225>', - 'guild_booster_lvl9': '<:discordboost9:1426051855015743558>', - 'bot_commands': '<:supportscommands:1426051872476889171>', - 'automod': '<:automod:1426051939103146115>', - 'quest_completed': '<:quest:1426051817946611784>', - 'username': '<:username:1426051894371160115>', - 'premium_bot': '<:premiumbot:1426051888272638025>', - 'orb': '<:orb:1426051861605126289>', - 'bronze': '<:bronze:1426051866969772034>', - 'silver': '<:silver:1426051928575709286>', - 'gold': '<:gold:1426052012352737333>', - 'platinum': '<:platinum:1426052018040082545>', - 'diamond': '<:diamond:1426051944685895771>', - 'emerald': '<:emerald:1426051812313792537>', - 'ruby': '<:ruby:1426051838645637150>', - 'opal': '<:opal:1426051883247603762>' + "staff": "<:discordstaff:1426051878155845702>", + "partner": "<:discordpartner:1426051933608873986>", + "certified_moderator": "<:discordmod:1426051921826943050>", + "hypesquad": "<:hypesquadevents:1426051833536970852>", + "hypesquad_house_1": "<:hypesquadbravery:1426051916739383297>", + "hypesquad_house_2": "<:hypesquadbrilliance:1426051849433387068>", + "hypesquad_house_3": "<:hypesquadbalance:1426051905179750495>", + "bug_hunter_level_1": "<:discordbughunter1:1426052002193997895>", + "bug_hunter_level_2": "<:discordbughunter2:1426052028257406987>", + "active_developer": "<:activedeveloper:1426051981658685552>", + "verified_developer": "<:discordbotdev:1426051827077480570>", + "early_supporter": "<:discordearlysupporter:1426052023165517924>", + "premium": "<:discordnitro:1426051911123206296>", + "guild_booster_lvl1": "<:discordboost1:1426052007294144605>", + "guild_booster_lvl2": "<:discordboost2:1426051986985582692>", + "guild_booster_lvl3": "<:discordboost3:1426051991812964434>", + "guild_booster_lvl4": "<:discordboost4:1426051955473645671>", + "guild_booster_lvl5": "<:discordboost5:1426051960456609824>", + "guild_booster_lvl6": "<:discordboost6:1426051976583712918>", + "guild_booster_lvl7": "<:discordboost7:1426051965808410634>", + "guild_booster_lvl8": "<:discordboost8:1426051844014342225>", + "guild_booster_lvl9": "<:discordboost9:1426051855015743558>", + "bot_commands": "<:supportscommands:1426051872476889171>", + "automod": "<:automod:1426051939103146115>", + "quest_completed": "<:quest:1426051817946611784>", + "username": "<:username:1426051894371160115>", + "premium_bot": "<:premiumbot:1426051888272638025>", + "orb": "<:orb:1426051861605126289>", + "bronze": "<:bronze:1426051866969772034>", + "silver": "<:silver:1426051928575709286>", + "gold": "<:gold:1426052012352737333>", + "platinum": "<:platinum:1426052018040082545>", + "diamond": "<:diamond:1426051944685895771>", + "emerald": "<:emerald:1426051812313792537>", + "ruby": "<:ruby:1426051838645637150>", + "opal": "<:opal:1426051883247603762>", } ACTIVITY_TYPE_ICONS = { - 0: '<:gaming:1426409065701048451>', - 2: '<:music:1426409047132737586>', - 3: '<:watching:1426409475450863778>' + 0: "<:gaming:1426409065701048451>", + 2: "<:music:1426409047132737586>", + 3: "<:watching:1426409475450863778>", } + def format_username(user): - if user.discriminator and user.discriminator != '0': - return f'{user.name}#{user.discriminator}' - return f'@{user.name}' + if user.discriminator and user.discriminator != "0": + return f"{user.name}#{user.discriminator}" + return f"@{user.name}" + def get_default_avatar(user_id, discriminator=None): if discriminator and int(discriminator) > 0: index = int(discriminator) % 5 else: index = (int(user_id) >> 22) % 6 - return f'https://cdn.discordapp.com/embed/avatars/{index}.png' + return f"https://cdn.discordapp.com/embed/avatars/{index}.png" + def snowflake_to_timestamp(snowflake): return ((int(snowflake) >> 22) + 1420070400000) / 1000 + async def fetch_quest_data(): global quests_fetch, quest_data - + async with aiohttp.ClientSession() as session: async with session.get( - 'https://raw.githubusercontent.com/aamiaa/discord-api-diff/refs/heads/main/quests.json' + "https://raw.githubusercontent.com/aamiaa/discord-api-diff/refs/heads/main/quests.json" ) as resp: quest_data = await resp.json() - + quests_fetch = int(datetime.now().timestamp() * 1000) + 3600000 + async def get_user_data(bot, user_id): - headers = {'Authorization': f'Bot {bot.http.token}'} - + headers = {"Authorization": f"Bot {bot.http.token}"} + async with aiohttp.ClientSession() as session: async with session.get( - f'https://discord.com/api/v10/users/{user_id}', - headers=headers + f"https://discord.com/api/v10/users/{user_id}", headers=headers ) as resp: if resp.status == 404: return None return await resp.json() + async def get_application_data(bot, app_id): - headers = {'Authorization': f'Bot {bot.http.token}'} - + headers = {"Authorization": f"Bot {bot.http.token}"} + async with aiohttp.ClientSession() as session: async with session.get( - f'https://discord.com/api/v10/applications/{app_id}/rpc', - headers=headers + f"https://discord.com/api/v10/applications/{app_id}/rpc", headers=headers ) as resp: if resp.status in [404, 403, 10002]: return None return await resp.json() + async def get_published_listing(bot, sku_id): - headers = {'Authorization': f'Bot {bot.http.token}'} - + headers = {"Authorization": f"Bot {bot.http.token}"} + async with aiohttp.ClientSession() as session: async with session.get( - f'https://discord.com/api/v10/store/published-listings/skus/{sku_id}', - headers=headers + f"https://discord.com/api/v10/store/published-listings/skus/{sku_id}", + headers=headers, ) as resp: if resp.status != 200: return None return await resp.json() + def userinfo_command(): @commands.hybrid_command( name="userinfo", description="Get information on a user.", ) @app_commands.describe( - user="User to get info for", - user_id="User ID to get info for" + user="User to get info for", user_id="User ID to get info for" ) async def userinfo(self, context, user: discord.User = None, user_id: str = None): await context.defer() - + bot = self.bot target_user = user if user else context.author - + if user_id: try: target_user = await bot.fetch_user(int(user_id)) except: - await context.send('User not found.') + await context.send("User not found.") return - + user_data = await get_user_data(bot, target_user.id) if not user_data: - await context.send('Failed to fetch user data.') + await context.send("Failed to fetch user data.") return - + guild = context.guild member = guild.get_member(target_user.id) if guild else None - + if int(datetime.now().timestamp() * 1000) > quests_fetch: try: await fetch_quest_data() except: pass - + badges = [] - flags = user_data.get('public_flags', 0) - - if str(target_user.id) == '1015372540937502851': + flags = user_data.get("public_flags", 0) + + if str(target_user.id) == "1015372540937502851": badges.append(f"[{BADGE_ICONS['staff']}]({BADGE_URLS['staff']})") badges.append(f"[{BADGE_ICONS['partner']}]({BADGE_URLS['partner']})") - badges.append(f"[{BADGE_ICONS['certified_moderator']}]({BADGE_URLS['certified_moderator']})") - badges.append(f"[{BADGE_ICONS['hypesquad_house_1']}]({BADGE_URLS['hypesquad_house_1']})") - badges.append(f"[{BADGE_ICONS['bug_hunter_level_2']}]({BADGE_URLS['bug_hunter_level_2']})") - badges.append(BADGE_ICONS['verified_developer']) - badges.append(f"[{BADGE_ICONS['early_supporter']}]({BADGE_URLS['early_supporter']})") - badges.append(f"[{BADGE_ICONS['guild_booster_lvl9']}]({BADGE_URLS['premium']})") - badges.append(f"[{BADGE_ICONS['quest_completed']}]({BADGE_URLS['quest_completed']})") - badges.append(BADGE_ICONS['username']) - elif str(target_user.id) == '1376728824108286034': - badges.append(BADGE_ICONS['automod']) - badges.append(BADGE_ICONS['verified_developer']) - badges.append(BADGE_ICONS['premium_bot']) - elif flags & USER_FLAGS['STAFF']: + badges.append( + f"[{BADGE_ICONS['certified_moderator']}]({BADGE_URLS['certified_moderator']})" + ) + badges.append( + f"[{BADGE_ICONS['hypesquad_house_1']}]({BADGE_URLS['hypesquad_house_1']})" + ) + badges.append( + f"[{BADGE_ICONS['bug_hunter_level_2']}]({BADGE_URLS['bug_hunter_level_2']})" + ) + badges.append(BADGE_ICONS["verified_developer"]) + badges.append( + f"[{BADGE_ICONS['early_supporter']}]({BADGE_URLS['early_supporter']})" + ) + badges.append( + f"[{BADGE_ICONS['guild_booster_lvl9']}]({BADGE_URLS['premium']})" + ) + badges.append( + f"[{BADGE_ICONS['quest_completed']}]({BADGE_URLS['quest_completed']})" + ) + badges.append(BADGE_ICONS["username"]) + elif str(target_user.id) == "1376728824108286034": + badges.append(BADGE_ICONS["automod"]) + badges.append(BADGE_ICONS["verified_developer"]) + badges.append(BADGE_ICONS["premium_bot"]) + elif flags & USER_FLAGS["STAFF"]: badges.append(f"[{BADGE_ICONS['staff']}]({BADGE_URLS['staff']})") - if flags & USER_FLAGS['PARTNER']: + if flags & USER_FLAGS["PARTNER"]: badges.append(f"[{BADGE_ICONS['partner']}]({BADGE_URLS['partner']})") - if flags & USER_FLAGS['CERTIFIED_MODERATOR']: - badges.append(f"[{BADGE_ICONS['certified_moderator']}]({BADGE_URLS['certified_moderator']})") - if flags & USER_FLAGS['HYPESQUAD']: + if flags & USER_FLAGS["CERTIFIED_MODERATOR"]: + badges.append( + f"[{BADGE_ICONS['certified_moderator']}]({BADGE_URLS['certified_moderator']})" + ) + if flags & USER_FLAGS["HYPESQUAD"]: badges.append(f"[{BADGE_ICONS['hypesquad']}]({BADGE_URLS['hypesquad']})") - if flags & USER_FLAGS['HYPESQUAD_ONLINE_HOUSE_1']: - badges.append(f"[{BADGE_ICONS['hypesquad_house_1']}]({BADGE_URLS['hypesquad_house_1']})") - if flags & USER_FLAGS['HYPESQUAD_ONLINE_HOUSE_2']: - badges.append(f"[{BADGE_ICONS['hypesquad_house_2']}]({BADGE_URLS['hypesquad_house_2']})") - if flags & USER_FLAGS['HYPESQUAD_ONLINE_HOUSE_3']: - badges.append(f"[{BADGE_ICONS['hypesquad_house_3']}]({BADGE_URLS['hypesquad_house_3']})") - if flags & USER_FLAGS['BUG_HUNTER_LEVEL_1']: - badges.append(f"[{BADGE_ICONS['bug_hunter_level_1']}]({BADGE_URLS['bug_hunter_level_1']})") - if flags & USER_FLAGS['BUG_HUNTER_LEVEL_2']: - badges.append(f"[{BADGE_ICONS['bug_hunter_level_2']}]({BADGE_URLS['bug_hunter_level_2']})") - if flags & USER_FLAGS['ACTIVE_DEVELOPER']: - badges.append(f"[{BADGE_ICONS['active_developer']}]({BADGE_URLS['active_developer']})") - if flags & USER_FLAGS['VERIFIED_DEVELOPER']: - badges.append(BADGE_ICONS['verified_developer']) - if flags & USER_FLAGS['PREMIUM_EARLY_SUPPORTER']: - badges.append(f"[{BADGE_ICONS['early_supporter']}]({BADGE_URLS['early_supporter']})") - - avatar_hash = user_data.get('avatar', '') - banner_hash = user_data.get('banner') - if (banner_hash or (avatar_hash and avatar_hash.startswith('a_'))) and not user_data.get('bot'): + if flags & USER_FLAGS["HYPESQUAD_ONLINE_HOUSE_1"]: + badges.append( + f"[{BADGE_ICONS['hypesquad_house_1']}]({BADGE_URLS['hypesquad_house_1']})" + ) + if flags & USER_FLAGS["HYPESQUAD_ONLINE_HOUSE_2"]: + badges.append( + f"[{BADGE_ICONS['hypesquad_house_2']}]({BADGE_URLS['hypesquad_house_2']})" + ) + if flags & USER_FLAGS["HYPESQUAD_ONLINE_HOUSE_3"]: + badges.append( + f"[{BADGE_ICONS['hypesquad_house_3']}]({BADGE_URLS['hypesquad_house_3']})" + ) + if flags & USER_FLAGS["BUG_HUNTER_LEVEL_1"]: + badges.append( + f"[{BADGE_ICONS['bug_hunter_level_1']}]({BADGE_URLS['bug_hunter_level_1']})" + ) + if flags & USER_FLAGS["BUG_HUNTER_LEVEL_2"]: + badges.append( + f"[{BADGE_ICONS['bug_hunter_level_2']}]({BADGE_URLS['bug_hunter_level_2']})" + ) + if flags & USER_FLAGS["ACTIVE_DEVELOPER"]: + badges.append( + f"[{BADGE_ICONS['active_developer']}]({BADGE_URLS['active_developer']})" + ) + if flags & USER_FLAGS["VERIFIED_DEVELOPER"]: + badges.append(BADGE_ICONS["verified_developer"]) + if flags & USER_FLAGS["PREMIUM_EARLY_SUPPORTER"]: + badges.append( + f"[{BADGE_ICONS['early_supporter']}]({BADGE_URLS['early_supporter']})" + ) + + avatar_hash = user_data.get("avatar", "") + banner_hash = user_data.get("banner") + if ( + banner_hash or (avatar_hash and avatar_hash.startswith("a_")) + ) and not user_data.get("bot"): badges.append(f"[{BADGE_ICONS['premium']}]({BADGE_URLS['premium']})") - + if member and member.premium_since: boosting_since = member.premium_since delta = (datetime.now(timezone.utc) - boosting_since).total_seconds() - icon = BADGE_ICONS['guild_booster_lvl1'] - + icon = BADGE_ICONS["guild_booster_lvl1"] + if delta >= ONE_MONTH * 24: - icon = BADGE_ICONS['guild_booster_lvl9'] + icon = BADGE_ICONS["guild_booster_lvl9"] elif delta >= ONE_MONTH * 18: - icon = BADGE_ICONS['guild_booster_lvl8'] + icon = BADGE_ICONS["guild_booster_lvl8"] elif delta >= ONE_MONTH * 15: - icon = BADGE_ICONS['guild_booster_lvl7'] + icon = BADGE_ICONS["guild_booster_lvl7"] elif delta >= ONE_MONTH * 12: - icon = BADGE_ICONS['guild_booster_lvl6'] + icon = BADGE_ICONS["guild_booster_lvl6"] elif delta >= ONE_MONTH * 9: - icon = BADGE_ICONS['guild_booster_lvl5'] + icon = BADGE_ICONS["guild_booster_lvl5"] elif delta >= ONE_MONTH * 6: - icon = BADGE_ICONS['guild_booster_lvl4'] + icon = BADGE_ICONS["guild_booster_lvl4"] elif delta >= ONE_MONTH * 3: - icon = BADGE_ICONS['guild_booster_lvl3'] + icon = BADGE_ICONS["guild_booster_lvl3"] elif delta >= ONE_MONTH * 2: - icon = BADGE_ICONS['guild_booster_lvl2'] - + icon = BADGE_ICONS["guild_booster_lvl2"] + badges.append(f"[{icon}]({BADGE_URLS['premium']})") - + bot_deleted = False - if user_data.get('bot'): + if user_data.get("bot"): app_data = await get_application_data(bot, target_user.id) if app_data: - app_flags = app_data.get('flags', 0) - if app_flags & APPLICATION_FLAGS['APPLICATION_COMMAND_BADGE']: - badges.append(f"[{BADGE_ICONS['bot_commands']}]({BADGE_URLS['bot_commands']})") - if app_flags & APPLICATION_FLAGS['AUTO_MODERATION_RULE_CREATE_BADGE']: - badges.append(BADGE_ICONS['automod']) + app_flags = app_data.get("flags", 0) + if app_flags & APPLICATION_FLAGS["APPLICATION_COMMAND_BADGE"]: + badges.append( + f"[{BADGE_ICONS['bot_commands']}]({BADGE_URLS['bot_commands']})" + ) + if app_flags & APPLICATION_FLAGS["AUTO_MODERATION_RULE_CREATE_BADGE"]: + badges.append(BADGE_ICONS["automod"]) else: bot_deleted = True - - if user_data.get('system'): + + if user_data.get("system"): bot_deleted = False - + quest_decoration_name = None - avatar_decoration = user_data.get('avatar_decoration_data') - if avatar_decoration and avatar_decoration.get('sku_id'): + avatar_decoration = user_data.get("avatar_decoration_data") + if avatar_decoration and avatar_decoration.get("sku_id"): for quest in quest_data: - config = quest.get('config', {}) - rewards = config.get('rewards_config', {}).get('rewards', []) or config.get('rewards', []) - + config = quest.get("config", {}) + rewards = config.get("rewards_config", {}).get( + "rewards", [] + ) or config.get("rewards", []) + for reward in rewards: - if reward.get('type') == 3 and reward.get('sku_id') == avatar_decoration['sku_id']: - quest_decoration_name = (reward.get('name') or reward.get('messages', {}).get('name') or '*Unknown*').replace('Avatar Decoration', 'Avatar Deco') - badges.append(f"[{BADGE_ICONS['quest_completed']}]({BADGE_URLS['quest_completed']})") + if ( + reward.get("type") == 3 + and reward.get("sku_id") == avatar_decoration["sku_id"] + ): + quest_decoration_name = ( + reward.get("name") + or reward.get("messages", {}).get("name") + or "*Unknown*" + ).replace("Avatar Decoration", "Avatar Deco") + badges.append( + f"[{BADGE_ICONS['quest_completed']}]({BADGE_URLS['quest_completed']})" + ) break if quest_decoration_name: break - elif avatar_decoration and (avatar_decoration.get('expires_at') or avatar_decoration.get('sku_id') == '1226939756617793606'): - badges.append(f"[{BADGE_ICONS['quest_completed']}]({BADGE_URLS['quest_completed']})") - - if user_data.get('legacy_username'): - badges.append(BADGE_ICONS['username']) - - if user_data.get('bot') and user_data.get('approximated_guild_count'): - badges.append(BADGE_ICONS['premium_bot']) - - profile_effect = user_data.get('profile_effect') + elif avatar_decoration and ( + avatar_decoration.get("expires_at") + or avatar_decoration.get("sku_id") == "1226939756617793606" + ): + badges.append( + f"[{BADGE_ICONS['quest_completed']}]({BADGE_URLS['quest_completed']})" + ) + + if user_data.get("legacy_username"): + badges.append(BADGE_ICONS["username"]) + + if user_data.get("bot") and user_data.get("approximated_guild_count"): + badges.append(BADGE_ICONS["premium_bot"]) + + profile_effect = user_data.get("profile_effect") if profile_effect: - effect_id = profile_effect.get('id') + effect_id = profile_effect.get("id") if effect_id: orb_tier = None - - if '1139323098643333240' in effect_id: - orb_tier = 'opal' - elif '1139323095841308733' in effect_id: - orb_tier = 'ruby' - elif '1139323090842013756' in effect_id: - orb_tier = 'emerald' - elif '1139323087608832090' in effect_id: - orb_tier = 'diamond' - elif '1144286544523669516' in effect_id: - orb_tier = 'platinum' - elif '1139323084127289374' in effect_id: - orb_tier = 'gold' - elif '1139323078435717220' in effect_id: - orb_tier = 'silver' - elif '1139323075214307448' in effect_id: - orb_tier = 'bronze' + + if "1139323098643333240" in effect_id: + orb_tier = "opal" + elif "1139323095841308733" in effect_id: + orb_tier = "ruby" + elif "1139323090842013756" in effect_id: + orb_tier = "emerald" + elif "1139323087608832090" in effect_id: + orb_tier = "diamond" + elif "1144286544523669516" in effect_id: + orb_tier = "platinum" + elif "1139323084127289374" in effect_id: + orb_tier = "gold" + elif "1139323078435717220" in effect_id: + orb_tier = "silver" + elif "1139323075214307448" in effect_id: + orb_tier = "bronze" else: - orb_tier = 'orb' - + orb_tier = "orb" + if orb_tier: badges.append(BADGE_ICONS[orb_tier]) - - default_avatar = get_default_avatar(target_user.id, user_data.get('discriminator', '0')) + + default_avatar = get_default_avatar( + target_user.id, user_data.get("discriminator", "0") + ) avatar_url = target_user.avatar.url if target_user.avatar else default_avatar - + banner_url = None banner_file = None original_banner_link = None if banner_hash: - is_animated = banner_hash.startswith('a_') - ext = 'gif' if is_animated else 'png' - original_banner_url = f'https://cdn.discordapp.com/banners/{target_user.id}/{banner_hash}.{ext}?size=4096' + is_animated = banner_hash.startswith("a_") + ext = "gif" if is_animated else "png" + original_banner_url = f"https://cdn.discordapp.com/banners/{target_user.id}/{banner_hash}.{ext}?size=4096" original_banner_link = original_banner_url - + try: async with aiohttp.ClientSession() as session: async with session.get(original_banner_url) as resp: if resp.status == 200: banner_data = await resp.read() img = Image.open(BytesIO(banner_data)) - + if img.width < 1100: target_width = 1100 target_height = 440 - + img_aspect = img.width / img.height target_aspect = target_width / target_height - + if img_aspect > target_aspect: scale_height = target_height scale_width = int(scale_height * img_aspect) else: scale_width = target_width scale_height = int(scale_width / img_aspect) - + left = (scale_width - target_width) // 2 top = (scale_height - target_height) // 2 right = left + target_width bottom = top + target_height - + if is_animated: frames = [] durations = [] - + try: while True: - frame = img.copy().convert('RGBA') - frame = frame.resize((scale_width, scale_height), Image.Resampling.LANCZOS) - frame = frame.crop((left, top, right, bottom)) + frame = img.copy().convert("RGBA") + frame = frame.resize( + (scale_width, scale_height), + Image.Resampling.LANCZOS, + ) + frame = frame.crop( + (left, top, right, bottom) + ) frames.append(frame) - durations.append(img.info.get('duration', 100)) + durations.append( + img.info.get("duration", 100) + ) img.seek(img.tell() + 1) except EOFError: pass - + output = BytesIO() frames[0].save( output, - format='GIF', + format="GIF", save_all=True, append_images=frames[1:], duration=durations, loop=0, - optimize=False + optimize=False, ) output.seek(0) - banner_file = discord.File(output, filename='banner.gif') - banner_url = 'attachment://banner.gif' + banner_file = discord.File( + output, filename="banner.gif" + ) + banner_url = "attachment://banner.gif" else: - img = img.resize((scale_width, scale_height), Image.Resampling.LANCZOS) + img = img.resize( + (scale_width, scale_height), + Image.Resampling.LANCZOS, + ) img = img.crop((left, top, right, bottom)) - + output = BytesIO() - img.save(output, format='PNG') + img.save(output, format="PNG") output.seek(0) - banner_file = discord.File(output, filename='banner.png') - banner_url = 'attachment://banner.png' + banner_file = discord.File( + output, filename="banner.png" + ) + banner_url = "attachment://banner.png" else: banner_url = original_banner_url except: banner_url = original_banner_url - - images = [f'[Avatar]({avatar_url})'] - + + images = [f"[Avatar]({avatar_url})"] + if banner_url: - images.append(f'[Banner]({original_banner_link})') - + images.append(f"[Banner]({original_banner_link})") + if avatar_decoration: - await get_published_listing(bot, avatar_decoration['sku_id']) + await get_published_listing(bot, avatar_decoration["sku_id"]) decoration_url = f"https://cdn.discordapp.com/avatar-decoration-presets/{avatar_decoration['asset']}.png?size=4096&passthrough=true" - images.append(f'[Avatar Deco]({decoration_url})') - - collectibles = user_data.get('collectibles') - if collectibles and collectibles.get('nameplate'): - nameplate = collectibles['nameplate'] - nameplate_asset = nameplate['asset'] - images.append(f'[Nameplate](https://cdn.discordapp.com/assets/collectibles/{nameplate_asset}static.png)') - + images.append(f"[Avatar Deco]({decoration_url})") + + collectibles = user_data.get("collectibles") + if collectibles and collectibles.get("nameplate"): + nameplate = collectibles["nameplate"] + nameplate_asset = nameplate["asset"] + images.append( + f"[Nameplate](https://cdn.discordapp.com/assets/collectibles/{nameplate_asset}static.png)" + ) + mutual_guilds = [g for g in bot.guilds if g.get_member(target_user.id)] - display_name = user_data.get('global_name') or user_data.get('username') - + display_name = user_data.get("global_name") or user_data.get("username") + desc_lines = [ f"# {member.nick if member and member.nick else display_name}", - f"{format_username(target_user).replace('@', '')} โ€ข <@{target_user.id}>" + f"{format_username(target_user).replace('@', '')} โ€ข <@{target_user.id}>", ] - + subline = "" if badges: - subline += ''.join(badges) - + subline += "".join(badges) + activity_lines = [] if member and member.activities: for activity in member.activities: if activity.type == discord.ActivityType.custom: activity_lines.append(ACTIVITY_TYPE_NAMES[4]) - elif activity.type in [discord.ActivityType.playing, discord.ActivityType.listening, discord.ActivityType.watching]: + elif activity.type in [ + discord.ActivityType.playing, + discord.ActivityType.listening, + discord.ActivityType.watching, + ]: name = activity.name activity_lines.append( f"{ACTIVITY_TYPE_ICONS.get(activity.type.value, '')} {ACTIVITY_TYPE_NAMES[activity.type.value]} **{name}**".strip() ) - + if subline: desc_lines.append(subline) - + if mutual_guilds: - desc_lines.append(f"-# {len(mutual_guilds)} Bot Mutual Server{'s' if len(mutual_guilds) > 1 else ''}") + desc_lines.append( + f"-# {len(mutual_guilds)} Bot Mutual Server{'s' if len(mutual_guilds) > 1 else ''}" + ) else: - desc_lines.append('') - - is_system = user_data.get('system') or user_data.get('discriminator') == '0000' - + desc_lines.append("") + + is_system = user_data.get("system") or user_data.get("discriminator") == "0000" + if bot_deleted and not is_system: - desc_lines.append("*This bot's application has been deleted*\n-# (or app ID and user ID desync)") + desc_lines.append( + "*This bot's application has been deleted*\n-# (or app ID and user ID desync)" + ) if is_system: - desc_lines.append('**System account**') - desc_lines.append('') - + desc_lines.append("**System account**") + desc_lines.append("") + if activity_lines: desc_lines.extend(activity_lines) - - embed = discord.Embed( - color=0x7289DA, - description='\n'.join(desc_lines) - ) - - primary_guild = user_data.get('primary_guild') - if primary_guild and primary_guild.get('identity_guild_id'): + + embed = discord.Embed(color=0x7289DA, description="\n".join(desc_lines)) + + primary_guild = user_data.get("primary_guild") + if primary_guild and primary_guild.get("identity_guild_id"): clan_badge_url = f"https://cdn.discordapp.com/clan-badges/{primary_guild['identity_guild_id']}/{primary_guild['badge']}.png?size=4096" - embed.set_author(name=primary_guild.get('tag', ''), icon_url=clan_badge_url) + embed.set_author(name=primary_guild.get("tag", ""), icon_url=clan_badge_url) else: - embed.set_author(name="User Information", icon_url="https://yes.nighty.works/raw/gSxqzV.png") - + embed.set_author( + name="User Information", + icon_url="https://yes.nighty.works/raw/gSxqzV.png", + ) + if member and member.nick and member.nick != display_name: embed.title = display_name - + embed.set_thumbnail(url=avatar_url) - + if banner_url: embed.set_image(url=banner_url) - + created_timestamp = int(snowflake_to_timestamp(target_user.id)) created_date = f"" - - embed.add_field(name='Created Date', value=created_date, inline=False) - - if member and hasattr(member, 'joined_at') and member.joined_at: + + embed.add_field(name="Created Date", value=created_date, inline=False) + + if member and hasattr(member, "joined_at") and member.joined_at: joined_timestamp = int(member.joined_at.timestamp()) join_date = f"" - embed.add_field(name='Join Date', value=join_date, inline=False) - - is_bot = user_data.get('bot', False) - embed.add_field(name='Is Bot', value='True' if is_bot else 'False', inline=True) - + embed.add_field(name="Join Date", value=join_date, inline=False) + + is_bot = user_data.get("bot", False) + embed.add_field(name="Is Bot", value="True" if is_bot else "False", inline=True) + if member and member.roles[1:]: roles = sorted(member.roles[1:], key=lambda r: r.position, reverse=True) - role_mentions = [f'<@&{r.id}>' for r in roles] - role_list = ' '.join(role_mentions) - + role_mentions = [f"<@&{r.id}>" for r in roles] + role_list = " ".join(role_mentions) + if len(role_list) > 1024: truncated_roles = [] current_length = 0 @@ -525,19 +604,20 @@ def userinfo_command(): break truncated_roles.append(mention) current_length += len(mention) + 1 - role_list = ' '.join(truncated_roles) + ' ...' - - embed.add_field(name=f'Roles ({len(member.roles) - 1})', value=role_list, inline=False) - + role_list = " ".join(truncated_roles) + " ..." + + embed.add_field( + name=f"Roles ({len(member.roles) - 1})", value=role_list, inline=False + ) + if images: - embed.add_field(name='\u200b', value='\u3000'.join(images), inline=False) - - embed.set_footer(text=f'ID: {target_user.id}') - + embed.add_field(name="\u200b", value="\u3000".join(images), inline=False) + + embed.set_footer(text=f"ID: {target_user.id}") + if banner_file: await context.send(embed=embed, file=banner_file) else: await context.send(embed=embed) - - return userinfo + return userinfo diff --git a/cogs/help.py b/cogs/help.py index dec0279..e6fdfdf 100644 --- a/cogs/help.py +++ b/cogs/help.py @@ -13,18 +13,29 @@ class Help(commands.Cog, name="help"): interaction: discord.Interaction, current: str, ) -> list[app_commands.Choice[str]]: - categories = ["general", "fun", "moderation", "owner", "sidestore", "idevice", "melonx", "media", "miscellaneous", "utilities", "events"] - + categories = [ + "general", + "fun", + "moderation", + "owner", + "sidestore", + "idevice", + "melonx", + "media", + "miscellaneous", + "utilities", + "events", + ] + suggestions = [] for category in categories: if current.lower() in category.lower(): suggestions.append( app_commands.Choice( - name=f"{category.capitalize()} Commands", - value=category + name=f"{category.capitalize()} Commands", value=category ) ) - + return suggestions[:25] @commands.hybrid_command( @@ -35,7 +46,6 @@ class Help(commands.Cog, name="help"): @app_commands.describe(category="Choose a specific category to view its commands") @app_commands.autocomplete(category=category_autocomplete) async def help(self, context: Context, category: str = None) -> None: - category_mapping = { "general": "general", "fun": "fun", @@ -49,7 +59,6 @@ class Help(commands.Cog, name="help"): "utils": "utilities", "utilities": "utilities", "events": "events", - "sync": "owner", "logs": "owner", "invite": "owner", @@ -59,10 +68,10 @@ class Help(commands.Cog, name="help"): "shutdown": "owner", "say": "owner", } - + category_descriptions = { "general": "General commands", - "fun": "Fun commands", + "fun": "Fun commands", "moderation": "Administration commands", "owner": "Owner commands", "sidestore": "SideStore troubleshooting commands", @@ -71,48 +80,47 @@ class Help(commands.Cog, name="help"): "media": "Media commands", "utilities": "Utility commands", "miscellaneous": "Miscellaneous commands", - "events": "Events commands" + "events": "Events commands", } - + if category is None: - embed = discord.Embed( - title="Help", - color=0x7289DA + embed = discord.Embed(title="Help", color=0x7289DA) + embed.set_author( + name="Help", icon_url="https://yes.nighty.works/raw/T9mnBO.png" ) - embed.set_author(name="Help", icon_url="https://yes.nighty.works/raw/T9mnBO.png") - + standalone_commands = [] botinfo_cmd = self.bot.tree.get_command("botinfo") if botinfo_cmd: - standalone_commands.append("**/botinfo** ยป Get information about this bot") - + standalone_commands.append( + "**/botinfo** ยป Get information about this bot" + ) + if standalone_commands: embed.add_field( - name="", - value="".join(standalone_commands) + "\n", - inline=False + name="", value="".join(standalone_commands) + "\n", inline=False ) - + available_categories = set() for cog_name in self.bot.cogs: mapped_category = category_mapping.get(cog_name.lower()) if mapped_category: available_categories.add(mapped_category) - + category_list = [] for cat in sorted(available_categories): - description = category_descriptions.get(cat, f"{cat.capitalize()} commands") - category_list.append(f"**/help {cat}** ยป {description}") - - if category_list: - embed.add_field( - name="", - value="\n".join(category_list), - inline=False + description = category_descriptions.get( + cat, f"{cat.capitalize()} commands" ) - + category_list.append(f"**/help {cat}** ยป {description}") + + if category_list: + embed.add_field(name="", value="\n".join(category_list), inline=False) + if context.interaction: - await context.interaction.response.send_message(embed=embed, ephemeral=True) + await context.interaction.response.send_message( + embed=embed, ephemeral=True + ) else: try: await context.send(embed=embed) @@ -122,16 +130,18 @@ class Help(commands.Cog, name="help"): except discord.Forbidden: pass # User has DMs disabled return - + category = category.lower() if category not in category_descriptions: embed = discord.Embed( title="Error", description=f"Category '{category}' not found. Use `/help` to see available categories.", - color=0x7289DA + color=0x7289DA, ) if context.interaction: - await context.interaction.response.send_message(embed=embed, ephemeral=True) + await context.interaction.response.send_message( + embed=embed, ephemeral=True + ) else: try: await context.send(embed=embed) @@ -139,10 +149,9 @@ class Help(commands.Cog, name="help"): try: await context.author.send(embed=embed) except discord.Forbidden: - pass + pass return - - + commands_in_category = [] seen_names = set() for cog_name in self.bot.cogs: @@ -151,13 +160,20 @@ class Help(commands.Cog, name="help"): if cog: commands_list = cog.get_commands() for command in commands_list: - has_prefix_subcommands = hasattr(command, 'commands') and len(getattr(command, 'commands', [])) > 0 + has_prefix_subcommands = ( + hasattr(command, "commands") + and len(getattr(command, "commands", [])) > 0 + ) if has_prefix_subcommands: continue name = command.name if name in seen_names: continue - description = command.description.partition("\n")[0] if command.description else "No description available" + description = ( + command.description.partition("\n")[0] + if command.description + else "No description available" + ) commands_in_category.append((name, description)) seen_names.add(name) @@ -168,30 +184,45 @@ class Help(commands.Cog, name="help"): bound_cog_name = getattr(bound_cog, "qualified_name", "").lower() if category_mapping.get(bound_cog_name) != category: continue - has_subcommands = hasattr(app_command, 'commands') and len(getattr(app_command, 'commands', [])) > 0 + has_subcommands = ( + hasattr(app_command, "commands") + and len(getattr(app_command, "commands", [])) > 0 + ) if has_subcommands 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)) + 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}") else: if app_command.name in seen_names: continue - description = app_command.description.partition("\n")[0] if getattr(app_command, "description", None) else "No description available" + 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 not commands_in_category: embed = discord.Embed( title="Error", description=f"No commands found in category '{category}'.", - color=0x7289DA + color=0x7289DA, ) if context.interaction: - await context.interaction.response.send_message(embed=embed, ephemeral=True) + await context.interaction.response.send_message( + embed=embed, ephemeral=True + ) else: try: await context.send(embed=embed) @@ -199,23 +230,18 @@ class Help(commands.Cog, name="help"): try: await context.author.send(embed=embed) except discord.Forbidden: - pass + pass return - - embed = discord.Embed( - title=f"/help ยป {category.lower()}", - color=0x7289DA + + embed = discord.Embed(title=f"/help ยป {category.lower()}", color=0x7289DA) + embed.set_author( + name="Help", icon_url="https://yes.nighty.works/raw/T9mnBO.png" ) - embed.set_author(name="Help", icon_url="https://yes.nighty.works/raw/T9mnBO.png") data = [] for command_name, description in sorted(commands_in_category): data.append(f"**/{command_name}** ยป {description}") help_text = "\n".join(data) - embed.add_field( - name="", - value=help_text, - inline=False - ) + embed.add_field(name="", value=help_text, inline=False) if context.interaction: await context.interaction.response.send_message(embed=embed, ephemeral=True) else: @@ -225,7 +251,7 @@ class Help(commands.Cog, name="help"): try: await context.author.send(embed=embed) except discord.Forbidden: - pass + pass async def setup(bot) -> None: diff --git a/cogs/idevice/__init__.py b/cogs/idevice/__init__.py index 57d74c6..ecd4d90 100644 --- a/cogs/idevice/__init__.py +++ b/cogs/idevice/__init__.py @@ -22,9 +22,11 @@ class Idevice(commands.GroupCog, name="idevice"): embed = discord.Embed( title="idevice Commands", description="Choose a command from the dropdown below to get help with specific issues:", - color=0xfa8c4a + color=0xFA8C4A, + ) + embed.set_author( + name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png" ) - 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) @@ -33,9 +35,11 @@ class Idevice(commands.GroupCog, name="idevice"): embed = discord.Embed( title="idevice Commands", description="Choose a command from the dropdown below to get help with specific issues:", - color=0xfa8c4a + color=0xFA8C4A, + ) + embed.set_author( + name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png" ) - 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) @@ -72,32 +76,29 @@ class Idevice(commands.GroupCog, name="idevice"): async def idevice_group_mountddi(self, context: Context): await self._invoke_hybrid(context, "mountddi") - @app_commands.command( - name="help", - description="idevice troubleshooting help" - ) + @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 + color=0xFA8C4A, + ) + embed.set_author( + name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png" ) - 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." + 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" + name="developermode", description="How to turn on developer mode" ) async def developermode(self, context): return await developermode_command()(self, context) @@ -105,23 +106,21 @@ class Idevice(commands.GroupCog, name="idevice"): @commands.check(_require_group_prefix) @commands.hybrid_command( name="noapps", - description="Help when apps aren't showing in installed apps view" + 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" - ) + @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'") diff --git a/cogs/idevice/developermode.py b/cogs/idevice/developermode.py index aad0595..794e0a9 100644 --- a/cogs/idevice/developermode.py +++ b/cogs/idevice/developermode.py @@ -11,31 +11,35 @@ def developermode_command(): ) async def developermode(self, context): embed = discord.Embed( - color=0xfa8c4a, + color=0xFA8C4A, description=( - '# How to Enable Developer Mode\n\n---\n\n' + - '1. Open the "Settings" app\n' + - '2. Navigate to "Privacy & Security"\n' + - '3. Scroll all the way down to find "Developer Mode"\n\n' + - 'If you don\'t see the Developer Mode option, you need to install a developer app first.\n\n' + - 'You can use [SideStore](https://sidestore.io/) for this purpose - follow their installation guide to get started.' - ) + "# How to Enable Developer Mode\n\n---\n\n" + + '1. Open the "Settings" app\n' + + '2. Navigate to "Privacy & Security"\n' + + '3. Scroll all the way down to find "Developer Mode"\n\n' + + "If you don't see the Developer Mode option, you need to install a developer app first.\n\n" + + "You can use [SideStore](https://sidestore.io/) for this purpose - follow their installation guide to get started." + ), ) - embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png") - embed.set_footer(text=f'Last Edited by neoarz') + embed.set_author( + name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png" + ) + embed.set_footer(text=f"Last Edited by neoarz") embed.timestamp = discord.utils.utcnow() view = discord.ui.View() - view.add_item(discord.ui.Button( - label="Edit Command", - style=discord.ButtonStyle.secondary, - url="https://github.com/neoarz/Syntrel/blob/main/cogs/idevice/developermode.py", - emoji="<:githubicon:1417717356846776340>" - )) + view.add_item( + discord.ui.Button( + label="Edit Command", + style=discord.ButtonStyle.secondary, + url="https://github.com/neoarz/Syntrel/blob/main/cogs/idevice/developermode.py", + emoji="<:githubicon:1417717356846776340>", + ) + ) if context.interaction: await context.interaction.response.send_message(embed=embed, view=view) else: await context.send(embed=embed, view=view) - + return developermode diff --git a/cogs/idevice/error_codes.py b/cogs/idevice/error_codes.py index 0a1560f..e22bbd5 100644 --- a/cogs/idevice/error_codes.py +++ b/cogs/idevice/error_codes.py @@ -7,13 +7,15 @@ from discord.ext.commands import Context def errorcodes_command(): - @commands.hybrid_command(name="errorcodes", description="Look up an idevice error code by name or number") + @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") async def errorcodes(self, context, name: str): def load_errors(): - json_path = os.path.join(os.path.dirname(__file__), 'files/errorcodes.json') + json_path = os.path.join(os.path.dirname(__file__), "files/errorcodes.json") try: - with open(json_path, 'r', encoding='utf-8') as f: + 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}") @@ -23,9 +25,11 @@ def errorcodes_command(): 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_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 key_to_data: try: @@ -35,32 +39,36 @@ def errorcodes_command(): key = None 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) + await context.interaction.response.send_message( + "Error not found.", ephemeral=True + ) else: await context.send("Error not found.") return - + title, code = key_to_data[key] - + embed = discord.Embed( description=f"## Error Code: {code}\n\n**Name**: `{key}`\n**Description**: {title}", - color=0xfa8c4a, + color=0xFA8C4A, ) - embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png") - + embed.set_author( + name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png" + ) + view = discord.ui.View() - view.add_item(discord.ui.Button( - label="Edit Command", - style=discord.ButtonStyle.secondary, - url="https://github.com/neoarz/Syntrel/blob/main/cogs/idevice/error_codes.py", - emoji="<:githubicon:1417717356846776340>" - )) - + view.add_item( + discord.ui.Button( + label="Edit Command", + style=discord.ButtonStyle.secondary, + url="https://github.com/neoarz/Syntrel/blob/main/cogs/idevice/error_codes.py", + emoji="<:githubicon:1417717356846776340>", + ) + ) + if context.interaction: await context.interaction.response.send_message(embed=embed, view=view) else: await context.send(embed=embed, view=view) - + return errorcodes - - diff --git a/cogs/idevice/idevice.py b/cogs/idevice/idevice.py index 560a1f4..f1a152f 100644 --- a/cogs/idevice/idevice.py +++ b/cogs/idevice/idevice.py @@ -9,8 +9,8 @@ import math def load_error_codes(): try: - json_path = os.path.join(os.path.dirname(__file__), 'files/errorcodes.json') - with open(json_path, 'r', encoding='utf-8') as f: + json_path = os.path.join(os.path.dirname(__file__), "files/errorcodes.json") + with open(json_path, "r", encoding="utf-8") as f: return json.load(f) except FileNotFoundError: return [] @@ -24,115 +24,110 @@ class ErrorCodesBrowserView(discord.ui.View): self.error_codes = load_error_codes() self.items_per_page = items_per_page self.current_page = 0 - self.max_pages = math.ceil(len(self.error_codes) / items_per_page) if self.error_codes else 1 + self.max_pages = ( + math.ceil(len(self.error_codes) / items_per_page) if self.error_codes else 1 + ) self.update_buttons() - + def update_buttons(self): self.clear_items() - + first_button = discord.ui.Button( emoji="<:leftmax:1420240325770870905>", style=discord.ButtonStyle.primary, - disabled=(self.current_page == 0) + disabled=(self.current_page == 0), ) first_button.callback = self.first_page self.add_item(first_button) - + prev_button = discord.ui.Button( emoji="<:left:1420240344926126090>", style=discord.ButtonStyle.primary, - disabled=(self.current_page == 0) + disabled=(self.current_page == 0), ) prev_button.callback = self.prev_page self.add_item(prev_button) - + stop_button = discord.ui.Button( - emoji="<:middle:1420240356087173160>", - style=discord.ButtonStyle.secondary + emoji="<:middle:1420240356087173160>", style=discord.ButtonStyle.secondary ) stop_button.callback = self.stop_interaction self.add_item(stop_button) - + next_button = discord.ui.Button( emoji="<:right:1420240334100627456>", style=discord.ButtonStyle.primary, - disabled=(self.current_page >= self.max_pages - 1) + disabled=(self.current_page >= self.max_pages - 1), ) next_button.callback = self.next_page self.add_item(next_button) - + last_button = discord.ui.Button( emoji="<:rightmax:1420240368846372886>", style=discord.ButtonStyle.primary, - disabled=(self.current_page >= self.max_pages - 1) + disabled=(self.current_page >= self.max_pages - 1), ) last_button.callback = self.last_page self.add_item(last_button) - + def create_embed(self): if not self.error_codes: embed = discord.Embed( - title="Error Codes", - description="No error codes found.", - color=0xfa8c4a + title="Error Codes", description="No error codes found.", color=0xFA8C4A + ) + embed.set_author( + name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png" ) - embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png") embed.set_footer(text="Page 0 of 0") return embed - - embed = discord.Embed( - title="Error Codes", - color=0xfa8c4a + + embed = discord.Embed(title="Error Codes", color=0xFA8C4A) + embed.set_author( + name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png" ) - embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png") - + start_idx = self.current_page * self.items_per_page end_idx = min(start_idx + self.items_per_page, len(self.error_codes)) current_codes = self.error_codes[start_idx:end_idx] - + for i, error in enumerate(current_codes): field_value = f"**Code:** `{error.get('code', 'N/A')}`\n**Name:** `{error.get('name', 'Unknown')}`\n**Description:** {error.get('description', 'No description')}" - embed.add_field( - name="\u200b", - value=field_value, - inline=True - ) - + embed.add_field(name="\u200b", value=field_value, inline=True) while len(current_codes) % 3 != 0: embed.add_field(name="\u200b", value="\u200b", inline=True) - + embed.set_footer(text=f"Page {self.current_page + 1} of {self.max_pages}") - + return embed - + async def first_page(self, interaction: discord.Interaction): self.current_page = 0 self.update_buttons() embed = self.create_embed() await interaction.response.edit_message(embed=embed, view=self) - + async def prev_page(self, interaction: discord.Interaction): if self.current_page > 0: self.current_page -= 1 self.update_buttons() embed = self.create_embed() await interaction.response.edit_message(embed=embed, view=self) - + async def stop_interaction(self, interaction: discord.Interaction): self.clear_items() embed = self.create_embed() embed.set_footer(text="Interaction stopped") await interaction.response.edit_message(embed=embed, view=self) self.stop() - + async def next_page(self, interaction: discord.Interaction): if self.current_page < self.max_pages - 1: self.current_page += 1 self.update_buttons() embed = self.create_embed() await interaction.response.edit_message(embed=embed, view=self) - + async def last_page(self, interaction: discord.Interaction): self.current_page = self.max_pages - 1 self.update_buttons() @@ -174,48 +169,60 @@ class ideviceSelect(discord.ui.Select): try: view = ErrorCodesBrowserView() embed = view.create_embed() - + success_embed = discord.Embed( title="Command Executed", description="Successfully executed `/errorcodes`. Please run /[errorcode_name] to get more information about an error code, and send it in chat", - color=0x00FF00 + color=0x00FF00, + ) + success_embed.set_author( + name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png" ) - success_embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png") await interaction.response.edit_message(embed=success_embed, view=None) - + await interaction.followup.send(embed=embed, view=view, ephemeral=True) - + except discord.Forbidden: - guild_info = f"server {interaction.guild.name} (ID: {interaction.guild.id})" if interaction.guild else "DM or private channel" - self.bot.logger.warning(f"Bot missing permissions in {guild_info} - cannot execute errorcodes command") - + guild_info = ( + f"server {interaction.guild.name} (ID: {interaction.guild.id})" + if interaction.guild + else "DM or private channel" + ) + self.bot.logger.warning( + f"Bot missing permissions in {guild_info} - cannot execute errorcodes command" + ) + if interaction.guild is None: embed = discord.Embed( title="Error", description="This command cannot be executed in DMs.", - color=0xFF0000 + color=0xFF0000, ) else: embed = discord.Embed( title="Permission Error", description="The bot needs the `send messages` permission to execute this command.", - color=0xFF0000 + color=0xFF0000, ) - embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png") + embed.set_author( + name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png" + ) await interaction.response.edit_message(embed=embed, view=None) except Exception as e: self.bot.logger.error(f"Error executing errorcodes command: {e}") embed = discord.Embed( title="Error", description="An error occurred while executing the command.", - color=0xFF0000 + color=0xFF0000, + ) + embed.set_author( + name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png" ) - embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png") await interaction.response.edit_message(embed=embed, view=None) return - + command = self.bot.get_command(command_name) - + if command: try: ctx = await self.bot.get_context(interaction.message) @@ -224,44 +231,57 @@ class ideviceSelect(discord.ui.Select): embed = discord.Embed( title="Command Executed", description=f"Successfully executed `/{command_name}`", - color=0x00FF00 + color=0x00FF00, + ) + embed.set_author( + name="idevice", + icon_url="https://yes.nighty.works/raw/snLMuO.png", ) - embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png") await interaction.response.edit_message(embed=embed, view=None) except discord.Forbidden: - guild_info = f"server {interaction.guild.name} (ID: {interaction.guild.id})" if interaction.guild else "DM or private channel" - self.bot.logger.warning(f"Bot missing permissions in {guild_info} - cannot execute {command_name} command") - + guild_info = ( + f"server {interaction.guild.name} (ID: {interaction.guild.id})" + if interaction.guild + else "DM or private channel" + ) + self.bot.logger.warning( + f"Bot missing permissions in {guild_info} - cannot execute {command_name} command" + ) + if interaction.guild is None: embed = discord.Embed( title="Error", description="This command cannot be executed in DMs.", - color=0xFF0000 + color=0xFF0000, ) else: embed = discord.Embed( title="Permission Error", description="The bot needs the `send messages` permission to execute this command.", - color=0xFF0000 + color=0xFF0000, ) - embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png") + embed.set_author( + name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png" + ) await interaction.response.edit_message(embed=embed, view=None) except Exception as e: self.bot.logger.error(f"Error executing {command_name} command: {e}") embed = discord.Embed( title="Error", description="An error occurred while executing the command.", - color=0xFF0000 + color=0xFF0000, + ) + embed.set_author( + name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png" ) - embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png") await interaction.response.edit_message(embed=embed, view=None) else: embed = discord.Embed( - title="Error", - description="Command not found!", - color=0xFF0000 + title="Error", description="Command not found!", color=0xFF0000 + ) + embed.set_author( + name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png" ) - embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png") await interaction.response.edit_message(embed=embed, view=None) @@ -282,10 +302,14 @@ def idevice_command(): description="This command can only be used in servers.", color=0xE02B2B, ) - embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png") - + embed.set_author( + name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png" + ) + if context.interaction: - await context.interaction.response.send_message(embed=embed, ephemeral=True) + await context.interaction.response.send_message( + embed=embed, ephemeral=True + ) else: await context.send(embed=embed, ephemeral=True) return @@ -296,10 +320,14 @@ def idevice_command(): description="The bot needs send messages permissions in this channel.", color=0xE02B2B, ) - embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png") - + embed.set_author( + name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png" + ) + if context.interaction: - await context.interaction.response.send_message(embed=embed, ephemeral=True) + await context.interaction.response.send_message( + embed=embed, ephemeral=True + ) else: await context.send(embed=embed, ephemeral=True) return @@ -307,15 +335,19 @@ def idevice_command(): embed = discord.Embed( title="idevice Commands", description="Choose a command from the dropdown below to get help with specific issues:", - color=0xfa8c4a + color=0xFA8C4A, ) - embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png") - + embed.set_author( + name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png" + ) + view = ideviceView(self.bot) - + if context.interaction: - await context.interaction.response.send_message(embed=embed, view=view, ephemeral=True) + await context.interaction.response.send_message( + embed=embed, view=view, ephemeral=True + ) else: await context.send(embed=embed, view=view) - - return idevice \ No newline at end of file + + return idevice diff --git a/cogs/idevice/mountddi.py b/cogs/idevice/mountddi.py index 505b808..95cf142 100644 --- a/cogs/idevice/mountddi.py +++ b/cogs/idevice/mountddi.py @@ -5,50 +5,52 @@ import os def mountddi_command(): - @commands.hybrid_command( - name="mountddi", - description="How to manually mount DDI" - ) + @commands.hybrid_command(name="mountddi", description="How to manually mount DDI") async def mountddi(self, context): await context.defer() embed = discord.Embed( - color=0xfa8c4a, + color=0xFA8C4A, description=( - '# How to Manually Mount DDI\n\n---\n\n' - '1. **Download the DDI.zip file attached above:**\n' - ' - Save it to your device and extract the contents\n\n' - '2. **Replace the DDI folder in StikDebug:**\n' - ' - Navigate to the StikDebug default directory on your iPhone/iPad\n' - ' - Delete the existing DDI folder completely\n' - ' - Replace it with the DDI folder from the downloaded zip\n' - ' - Make sure it\'s in the StikDebug default directory\n\n' - '3. **Restart and retry:**\n' - ' - Completely restart StikDebug\n' - ' - See if you get the same error again\n\n' - ) + "# How to Manually Mount DDI\n\n---\n\n" + "1. **Download the DDI.zip file attached above:**\n" + " - Save it to your device and extract the contents\n\n" + "2. **Replace the DDI folder in StikDebug:**\n" + " - Navigate to the StikDebug default directory on your iPhone/iPad\n" + " - Delete the existing DDI folder completely\n" + " - Replace it with the DDI folder from the downloaded zip\n" + " - Make sure it's in the StikDebug default directory\n\n" + "3. **Restart and retry:**\n" + " - Completely restart StikDebug\n" + " - See if you get the same error again\n\n" + ), ) embed.set_author( - name="idevice", - icon_url="https://yes.nighty.works/raw/snLMuO.png" + name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png" ) embed.set_footer(text="Last Edited by neoarz") embed.timestamp = discord.utils.utcnow() view = discord.ui.View() - view.add_item(discord.ui.Button( - label="Edit Command", - style=discord.ButtonStyle.secondary, - url="https://github.com/neoarz/Syntrel/blob/main/cogs/idevice/mountddi.py", - emoji="<:githubicon:1417717356846776340>" - )) + view.add_item( + discord.ui.Button( + label="Edit Command", + style=discord.ButtonStyle.secondary, + url="https://github.com/neoarz/Syntrel/blob/main/cogs/idevice/mountddi.py", + 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 + 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 context.send(embed=embed, view=view, file=file) else: await context.send(embed=embed, view=view) - + return mountddi diff --git a/cogs/idevice/noapps.py b/cogs/idevice/noapps.py index 386c27b..e54dcfe 100644 --- a/cogs/idevice/noapps.py +++ b/cogs/idevice/noapps.py @@ -7,36 +7,41 @@ import time def noapps_command(): @commands.hybrid_command( - name="noapps", description="Help when apps aren't showing in installed apps view" + name="noapps", + description="Help when apps aren't showing in installed apps view", ) async def noapps(self, context): embed = discord.Embed( - color=0xfa8c4a, + color=0xFA8C4A, description=( - '# Apps Not Showing in Installed Apps View\n\n---\n\n' + - 'If apps aren\'t appearing in the StikDebug installed apps view, this is likely because they were signed with a distribution certificate instead of a development certificate.\n\n' + - 'Distribution certificates lack the `get-task-allow` entitlement needed for JIT.\n\n' + - 'To fix this issue:\n' + - '- Use a development certificate when signing apps, or\n' + - '- Try SideStore, the best free sideloading method available\n\n' + - 'More details can be found at [SideStore\'s official website](https://sidestore.io/)' - ) + "# Apps Not Showing in Installed Apps View\n\n---\n\n" + + "If apps aren't appearing in the StikDebug installed apps view, this is likely because they were signed with a distribution certificate instead of a development certificate.\n\n" + + "Distribution certificates lack the `get-task-allow` entitlement needed for JIT.\n\n" + + "To fix this issue:\n" + + "- Use a development certificate when signing apps, or\n" + + "- Try SideStore, the best free sideloading method available\n\n" + + "More details can be found at [SideStore's official website](https://sidestore.io/)" + ), ) - embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png") - embed.set_footer(text=f'Last Edited by neoarz') + embed.set_author( + name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png" + ) + embed.set_footer(text=f"Last Edited by neoarz") embed.timestamp = discord.utils.utcnow() view = discord.ui.View() - view.add_item(discord.ui.Button( - label="Edit Command", - style=discord.ButtonStyle.secondary, - url="https://github.com/neoarz/Syntrel/blob/main/cogs/idevice/noapps.py", - emoji="<:githubicon:1417717356846776340>" - )) + view.add_item( + discord.ui.Button( + label="Edit Command", + style=discord.ButtonStyle.secondary, + url="https://github.com/neoarz/Syntrel/blob/main/cogs/idevice/noapps.py", + emoji="<:githubicon:1417717356846776340>", + ) + ) if context.interaction: await context.interaction.response.send_message(embed=embed, view=view) else: await context.send(embed=embed, view=view) - + return noapps diff --git a/cogs/media/__init__.py b/cogs/media/__init__.py index fae9fce..3acbddd 100644 --- a/cogs/media/__init__.py +++ b/cogs/media/__init__.py @@ -34,12 +34,20 @@ class Media(commands.GroupCog, name="media"): """Listen for bot mentions with 'tweety' command while replying to a message""" if message.author.bot: return - - if self.bot.user in message.mentions and message.reference and message.reference.message_id: + + if ( + self.bot.user in message.mentions + and message.reference + and message.reference.message_id + ): content = message.content.lower() - content_without_mention = content.replace(f'<@{self.bot.user.id}>', '').replace(f'<@!{self.bot.user.id}>', '').strip() - - if content_without_mention.strip() == 'tweety': + content_without_mention = ( + content.replace(f"<@{self.bot.user.id}>", "") + .replace(f"<@!{self.bot.user.id}>", "") + .strip() + ) + + if content_without_mention.strip() == "tweety": ctx = await self.bot.get_context(message) await self.tweety(ctx) @@ -48,27 +56,33 @@ class Media(commands.GroupCog, name="media"): embed = discord.Embed( title="Media Commands", description="Use `.media ` or `/media `.", - color=0x7289DA + color=0x7289DA, + ) + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) + embed.add_field( + name="Available", + value="download, mcquote, img2gif, tweety, tts", + inline=False, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - embed.add_field(name="Available", value="download, mcquote, img2gif, tweety, tts", inline=False) await context.send(embed=embed) async def _invoke_hybrid(self, context: Context, name: str, *args, **kwargs): if name == "download": - await self.download(context, url=kwargs.get('url', '')) + await self.download(context, url=kwargs.get("url", "")) return if name == "mcquote": - await self.mcquote(context, text=kwargs.get('text', '')) + await self.mcquote(context, text=kwargs.get("text", "")) return if name == "img2gif": - await self.img2gif(context, attachment=kwargs.get('attachment')) + await self.img2gif(context, attachment=kwargs.get("attachment")) return if name == "tweety": await self.tweety(context) return if name == "tts": - await self.tts(context, text=kwargs.get('text')) + await self.tts(context, text=kwargs.get("text")) return await context.send(f"Unknown media command: {name}") @@ -81,7 +95,9 @@ class Media(commands.GroupCog, name="media"): await self._invoke_hybrid(context, "mcquote", text=text) @media_group.command(name="img2gif") - async def media_group_img2gif(self, context: Context, attachment: Optional[discord.Attachment] = None): + async def media_group_img2gif( + self, context: Context, attachment: Optional[discord.Attachment] = None + ): await self._invoke_hybrid(context, "img2gif", attachment=attachment) @media_group.command(name="tweety") @@ -132,6 +148,7 @@ class Media(commands.GroupCog, name="media"): async def tts(self, context, text: str = None): return await tts_command()(context, text=text) + async def setup(bot) -> None: cog = Media(bot) await bot.add_cog(cog) diff --git a/cogs/media/download.py b/cogs/media/download.py index 7102d9e..a974acd 100644 --- a/cogs/media/download.py +++ b/cogs/media/download.py @@ -10,6 +10,7 @@ import logging logger = logging.getLogger("discord_bot") + def download_command(): @commands.hybrid_command( name="download", @@ -23,8 +24,10 @@ def download_command(): description="This command can only be used in servers.", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) + interaction = getattr(context, "interaction", None) if interaction is not None: if not interaction.response.is_done(): @@ -41,8 +44,10 @@ def download_command(): description="The bot needs the `send messages` permission in this channel.", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) + interaction = getattr(context, "interaction", None) if interaction is not None: if not interaction.response.is_done(): @@ -52,15 +57,17 @@ def download_command(): else: await context.send(embed=embed, ephemeral=True) return - + if not url: embed = discord.Embed( title="Error", description="Please provide a valid URL to download.", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) + interaction = getattr(context, "interaction", None) if interaction is not None: if not interaction.response.is_done(): @@ -74,7 +81,9 @@ def download_command(): # Check if bot has send messages permission before starting download try: test_embed = discord.Embed(title="Testing permissions...", color=0x7289DA) - test_embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + test_embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) await context.channel.send(embed=test_embed, delete_after=0.1) except discord.Forbidden: embed = discord.Embed( @@ -82,8 +91,10 @@ def download_command(): description="The bot needs the `send messages` permission to execute this command.", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) + interaction = getattr(context, "interaction", None) if interaction is not None: if not interaction.response.is_done(): @@ -102,12 +113,16 @@ def download_command(): description="Please provide a valid URL.", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) + interaction = getattr(context, "interaction", None) if interaction is not None: if not interaction.response.is_done(): - await interaction.response.send_message(embed=embed, ephemeral=True) + await interaction.response.send_message( + embed=embed, ephemeral=True + ) else: await interaction.followup.send(embed=embed, ephemeral=True) else: @@ -119,8 +134,10 @@ def download_command(): description="Please provide a valid URL.", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) + interaction = getattr(context, "interaction", None) if interaction is not None: if not interaction.response.is_done(): @@ -136,35 +153,41 @@ def download_command(): description=" Downloading video... This may take a moment.", color=0x7289DA, ) - processing_embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - + processing_embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) + interaction = getattr(context, "interaction", None) if interaction is not None: if not interaction.response.is_done(): - await interaction.response.send_message(embed=processing_embed, ephemeral=True) + await interaction.response.send_message( + embed=processing_embed, ephemeral=True + ) else: await interaction.followup.send(embed=processing_embed, ephemeral=True) else: processing_msg = await context.send(embed=processing_embed) temp_dir = tempfile.mkdtemp() - + # Try Docker path first, fallback to local path for development - cookie_path = '/bot/cogs/media/files/cookies.txt' + cookie_path = "/bot/cogs/media/files/cookies.txt" if not os.path.exists(cookie_path): - cookie_path = os.path.join(os.path.dirname(__file__), 'files', 'cookies.txt') - + cookie_path = os.path.join( + os.path.dirname(__file__), "files", "cookies.txt" + ) + ydl_opts = { - 'format': 'bestvideo[filesize<200M]+bestaudio[filesize<200M]/best[filesize<200M]/bestvideo+bestaudio/best', - 'outtmpl': os.path.join(temp_dir, '%(title)s.%(ext)s'), - 'noplaylist': True, - 'extract_flat': False, - 'writesubtitles': False, - 'writeautomaticsub': False, - 'writethumbnail': False, - 'ignoreerrors': False, - 'merge_output_format': 'mp4', - 'cookiefile': cookie_path, + "format": "bestvideo[filesize<200M]+bestaudio[filesize<200M]/best[filesize<200M]/bestvideo+bestaudio/best", + "outtmpl": os.path.join(temp_dir, "%(title)s.%(ext)s"), + "noplaylist": True, + "extract_flat": False, + "writesubtitles": False, + "writeautomaticsub": False, + "writethumbnail": False, + "ignoreerrors": False, + "merge_output_format": "mp4", + "cookiefile": cookie_path, } try: @@ -172,63 +195,103 @@ def download_command(): info = await asyncio.get_event_loop().run_in_executor( None, lambda: ydl.extract_info(url, download=True) ) - + if not info: raise Exception("Could not extract video information") - - video_title = info.get('title', 'Unknown Title') - video_duration_seconds = int(info.get('duration') or 0) - video_uploader = info.get('uploader', 'Unknown') - video_url = info.get('webpage_url') or info.get('original_url') or url - platform = info.get('extractor') or info.get('extractor_key') or 'Unknown' - view_count = info.get('view_count') - files = [f for f in os.listdir(temp_dir) if os.path.isfile(os.path.join(temp_dir, f))] - + video_title = info.get("title", "Unknown Title") + video_duration_seconds = int(info.get("duration") or 0) + video_uploader = info.get("uploader", "Unknown") + video_url = info.get("webpage_url") or info.get("original_url") or url + platform = ( + info.get("extractor") or info.get("extractor_key") or "Unknown" + ) + view_count = info.get("view_count") + + files = [ + f + for f in os.listdir(temp_dir) + if os.path.isfile(os.path.join(temp_dir, f)) + ] + if not files: raise Exception("No video file was downloaded") - + video_file = os.path.join(temp_dir, files[0]) file_size = os.path.getsize(video_file) - logger.info(f"File size: {file_size} bytes ({file_size / (1024*1024):.2f} MB)") - + logger.info( + f"File size: {file_size} bytes ({file_size / (1024 * 1024):.2f} MB)" + ) + if file_size > 24 * 1024 * 1024: # 24MB limit logger.info("File is over 24MB, uploading to Catbox") + async def upload_to_catbox(path: str) -> str: try: file_size_bytes = os.path.getsize(path) except Exception: file_size_bytes = -1 - logger.info(f"Catbox upload start: name={os.path.basename(path)} size={file_size_bytes}") + logger.info( + f"Catbox upload start: name={os.path.basename(path)} size={file_size_bytes}" + ) form = aiohttp.FormData() - form.add_field('reqtype', 'fileupload') - form.add_field('fileToUpload', open(path, 'rb'), filename=os.path.basename(path)) + form.add_field("reqtype", "fileupload") + form.add_field( + "fileToUpload", + open(path, "rb"), + filename=os.path.basename(path), + ) timeout = aiohttp.ClientTimeout(total=600) async with aiohttp.ClientSession(timeout=timeout) as session: - async with session.post('https://catbox.moe/user/api.php', data=form) as resp: + async with session.post( + "https://catbox.moe/user/api.php", data=form + ) as resp: text = await resp.text() - logger.info(f"Catbox response: status={resp.status} body_len={len(text)}") - if resp.status == 200 and text.startswith('https://'): + logger.info( + f"Catbox response: status={resp.status} body_len={len(text)}" + ) + if resp.status == 200 and text.startswith("https://"): url_text = text.strip() - logger.info(f"Catbox upload success: url={url_text}") + logger.info( + f"Catbox upload success: url={url_text}" + ) return url_text - logger.error(f"Catbox upload failed: status={resp.status} body={text.strip()[:500]}") + logger.error( + f"Catbox upload failed: status={resp.status} body={text.strip()[:500]}" + ) raise RuntimeError(f"Upload failed: {text.strip()}") + try: link = await upload_to_catbox(video_file) minutes, seconds = divmod(video_duration_seconds, 60) duration_str = f"{minutes}:{seconds:02d}" - description_text = f"### **[{video_title}]({video_url})**" if video_url else f"### **{video_title}**" + description_text = ( + f"### **[{video_title}]({video_url})**" + if video_url + else f"### **{video_title}**" + ) embed = discord.Embed( title="Download", description=description_text, color=0x7289DA, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - embed.add_field(name="Uploader", value=video_uploader or "Unknown", inline=True) - embed.add_field(name="Duration", value=duration_str, inline=True) + embed.set_author( + name="Media", + icon_url="https://yes.nighty.works/raw/y5SEZ9.webp", + ) + embed.add_field( + name="Uploader", + value=video_uploader or "Unknown", + inline=True, + ) + embed.add_field( + name="Duration", value=duration_str, inline=True + ) embed.add_field(name="Platform", value=platform, inline=True) - embed.set_footer(text=f"Requested by {context.author.name}", icon_url=context.author.display_avatar.url) + embed.set_footer( + text=f"Requested by {context.author.name}", + icon_url=context.author.display_avatar.url, + ) if interaction is not None: await context.channel.send(embed=embed) @@ -249,13 +312,16 @@ def download_command(): description = "The video is too large to upload. The file exceeds 200MB (Catbox limit) and cannot be sent via Discord (25MB limit)." else: description = f"The video is over 25MB and upload to hosting failed: {upload_error}" - + embed = discord.Embed( title="Error", description=description, color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + embed.set_author( + name="Media", + icon_url="https://yes.nighty.works/raw/y5SEZ9.webp", + ) if interaction is not None: try: @@ -271,22 +337,34 @@ def download_command(): logger.info("File is under 24MB, sending directly to Discord") minutes, seconds = divmod(video_duration_seconds, 60) duration_str = f"{minutes}:{seconds:02d}" - description_text = f"### **[{video_title}]({video_url})**" if video_url else f"### **{video_title}**" + description_text = ( + f"### **[{video_title}]({video_url})**" + if video_url + else f"### **{video_title}**" + ) embed = discord.Embed( title="Download", description=description_text, color=0x7289DA, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - embed.add_field(name="Uploader", value=video_uploader or "Unknown", inline=True) + embed.set_author( + name="Media", + icon_url="https://yes.nighty.works/raw/y5SEZ9.webp", + ) + embed.add_field( + name="Uploader", value=video_uploader or "Unknown", inline=True + ) embed.add_field(name="Duration", value=duration_str, inline=True) embed.add_field(name="Platform", value=platform, inline=True) - embed.set_footer(text=f"Requested by {context.author.name}", icon_url=context.author.display_avatar.url) - + embed.set_footer( + text=f"Requested by {context.author.name}", + icon_url=context.author.display_avatar.url, + ) + try: - with open(video_file, 'rb') as f: + with open(video_file, "rb") as f: file = discord.File(f, filename=files[0]) - + if interaction is not None: await context.channel.send(embed=embed) await context.channel.send(file=file) @@ -300,40 +378,82 @@ def download_command(): await context.channel.send(file=file) except discord.HTTPException as e: if e.status == 413: - logger.info("Discord rejected file (413), falling back to Catbox upload") + logger.info( + "Discord rejected file (413), falling back to Catbox upload" + ) + async def upload_to_catbox(path: str) -> str: try: file_size_bytes = os.path.getsize(path) except Exception: file_size_bytes = -1 - logger.info(f"Catbox upload start: name={os.path.basename(path)} size={file_size_bytes}") + logger.info( + f"Catbox upload start: name={os.path.basename(path)} size={file_size_bytes}" + ) form = aiohttp.FormData() - form.add_field('reqtype', 'fileupload') - form.add_field('fileToUpload', open(path, 'rb'), filename=os.path.basename(path)) + form.add_field("reqtype", "fileupload") + form.add_field( + "fileToUpload", + open(path, "rb"), + filename=os.path.basename(path), + ) timeout = aiohttp.ClientTimeout(total=600) - async with aiohttp.ClientSession(timeout=timeout) as session: - async with session.post('https://catbox.moe/user/api.php', data=form) as resp: + async with aiohttp.ClientSession( + timeout=timeout + ) as session: + async with session.post( + "https://catbox.moe/user/api.php", data=form + ) as resp: text = await resp.text() - logger.info(f"Catbox response: status={resp.status} body_len={len(text)}") - if resp.status == 200 and text.startswith('https://'): + logger.info( + f"Catbox response: status={resp.status} body_len={len(text)}" + ) + if resp.status == 200 and text.startswith( + "https://" + ): url_text = text.strip() - logger.info(f"Catbox upload success: url={url_text}") + logger.info( + f"Catbox upload success: url={url_text}" + ) return url_text - logger.error(f"Catbox upload failed: status={resp.status} body={text.strip()[:500]}") - raise RuntimeError(f"Upload failed: {text.strip()}") + logger.error( + f"Catbox upload failed: status={resp.status} body={text.strip()[:500]}" + ) + raise RuntimeError( + f"Upload failed: {text.strip()}" + ) + try: link = await upload_to_catbox(video_file) - description_text_with_link = f"### **[{video_title}]({video_url})**\n\n{link}" if video_url else f"### **{video_title}**\n\n{link}" + description_text_with_link = ( + f"### **[{video_title}]({video_url})**\n\n{link}" + if video_url + else f"### **{video_title}**\n\n{link}" + ) embed = discord.Embed( title="Download", description=description_text, color=0x7289DA, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - embed.add_field(name="Uploader", value=video_uploader or "Unknown", inline=True) - embed.add_field(name="Duration", value=duration_str, inline=True) - embed.add_field(name="Platform", value=platform, inline=True) - embed.set_footer(text=f"Requested by {context.author.name}", icon_url=context.author.display_avatar.url) + embed.set_author( + name="Media", + icon_url="https://yes.nighty.works/raw/y5SEZ9.webp", + ) + embed.add_field( + name="Uploader", + value=video_uploader or "Unknown", + inline=True, + ) + embed.add_field( + name="Duration", value=duration_str, inline=True + ) + embed.add_field( + name="Platform", value=platform, inline=True + ) + embed.set_footer( + text=f"Requested by {context.author.name}", + icon_url=context.author.display_avatar.url, + ) if interaction is not None: await context.channel.send(embed=embed) @@ -347,33 +467,42 @@ def download_command(): await context.channel.send(embed=embed) await context.channel.send(link) except Exception as upload_error: - logger.exception(f"Catbox upload exception: {upload_error}") + logger.exception( + f"Catbox upload exception: {upload_error}" + ) embed = discord.Embed( title="Error", description=f"Discord rejected the file and Catbox upload failed: {upload_error}", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + embed.set_author( + name="Media", + icon_url="https://yes.nighty.works/raw/y5SEZ9.webp", + ) if interaction is not None: try: await interaction.delete_original_response() except: pass - await interaction.followup.send(embed=embed, ephemeral=True) + await interaction.followup.send( + embed=embed, ephemeral=True + ) else: await processing_msg.delete() await context.send(embed=embed, ephemeral=True) else: raise e - + except Exception as e: embed = discord.Embed( title="Error", description=f"Failed to download video: {str(e)}", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) if interaction is not None: try: @@ -387,7 +516,7 @@ def download_command(): except: pass await context.send(embed=embed, ephemeral=True) - + finally: for file in os.listdir(temp_dir): try: @@ -398,5 +527,5 @@ def download_command(): os.rmdir(temp_dir) except: pass - - return download \ No newline at end of file + + return download diff --git a/cogs/media/img2gif.py b/cogs/media/img2gif.py index 0f65d6b..e2a9def 100644 --- a/cogs/media/img2gif.py +++ b/cogs/media/img2gif.py @@ -6,8 +6,10 @@ from PIL import Image import subprocess import shutil from typing import Optional + try: import pillow_heif + pillow_heif.register_heif_opener() except Exception: pass @@ -20,7 +22,7 @@ async def send_error_message(context, description: str): color=0xE02B2B, ) embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - + interaction = getattr(context, "interaction", None) if interaction is not None: if not interaction.response.is_done(): @@ -30,6 +32,7 @@ async def send_error_message(context, description: str): else: await context.send(embed=embed, ephemeral=True) + def img2gif_command(): @commands.hybrid_command( name="img2gif", @@ -39,14 +42,30 @@ def img2gif_command(): async def img2gif(self, context, attachment: Optional[discord.Attachment] = None): resolved_attachment = attachment if resolved_attachment is None: - if context.message and context.message.reference and context.message.reference.resolved: + if ( + context.message + and context.message.reference + and context.message.reference.resolved + ): ref_msg = context.message.reference.resolved if isinstance(ref_msg, discord.Message) and ref_msg.attachments: resolved_attachment = ref_msg.attachments[0] - if resolved_attachment is None and context.message and context.message.attachments: + if ( + resolved_attachment is None + and context.message + and context.message.attachments + ): resolved_attachment = context.message.attachments[0] - if resolved_attachment is None or not resolved_attachment.filename.lower().endswith((".png", ".jpg", ".jpeg", ".webp", ".bmp", ".tiff", ".heic", ".heif")): - await send_error_message(context, "Provide or reply to an image (png/jpg/jpeg/webp/bmp/tiff/heic/heif).") + if ( + resolved_attachment is None + or not resolved_attachment.filename.lower().endswith( + (".png", ".jpg", ".jpeg", ".webp", ".bmp", ".tiff", ".heic", ".heif") + ) + ): + await send_error_message( + context, + "Provide or reply to an image (png/jpg/jpeg/webp/bmp/tiff/heic/heif).", + ) return interaction = getattr(context, "interaction", None) @@ -59,12 +78,16 @@ def img2gif_command(): description=" Converting image...", color=0x7289DA, ) - processing_embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + processing_embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) processing_msg = await context.send(embed=processing_embed) tmp_dir = tempfile.mkdtemp() src_path = os.path.join(tmp_dir, resolved_attachment.filename) - out_path = os.path.join(tmp_dir, os.path.splitext(resolved_attachment.filename)[0] + ".gif") + out_path = os.path.join( + tmp_dir, os.path.splitext(resolved_attachment.filename)[0] + ".gif" + ) try: await resolved_attachment.save(src_path) @@ -75,20 +98,42 @@ def img2gif_command(): img = img.convert("RGBA") duration_ms = 100 loop = 0 - img.save(out_path, format="GIF", save_all=True, optimize=True, duration=duration_ms, loop=loop) + img.save( + out_path, + format="GIF", + save_all=True, + optimize=True, + duration=duration_ms, + loop=loop, + ) except Exception: - if resolved_attachment.filename.lower().endswith((".heic", ".heif")) and shutil.which("ffmpeg"): - png_path = os.path.join(tmp_dir, os.path.splitext(resolved_attachment.filename)[0] + ".png") + if resolved_attachment.filename.lower().endswith( + (".heic", ".heif") + ) and shutil.which("ffmpeg"): + png_path = os.path.join( + tmp_dir, + os.path.splitext(resolved_attachment.filename)[0] + ".png", + ) try: - subprocess.run([ - "ffmpeg", "-y", "-i", src_path, png_path - ], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + subprocess.run( + ["ffmpeg", "-y", "-i", src_path, png_path], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) with Image.open(png_path) as img: if img.mode not in ("RGB", "RGBA"): img = img.convert("RGBA") duration_ms = 100 loop = 0 - img.save(out_path, format="GIF", save_all=True, optimize=True, duration=duration_ms, loop=loop) + img.save( + out_path, + format="GIF", + save_all=True, + optimize=True, + duration=duration_ms, + loop=loop, + ) except Exception as conv_err: raise conv_err else: @@ -103,18 +148,30 @@ def img2gif_command(): await context.send(file=file) except Exception as e: if interaction is not None: - await interaction.followup.send(embed=discord.Embed( - title="Error", - description=f"Failed to convert image: {e}", - color=0xE02B2B, - ).set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp"), ephemeral=True) + await interaction.followup.send( + embed=discord.Embed( + title="Error", + description=f"Failed to convert image: {e}", + color=0xE02B2B, + ).set_author( + name="Media", + icon_url="https://yes.nighty.works/raw/y5SEZ9.webp", + ), + ephemeral=True, + ) else: await processing_msg.delete() - await context.send(embed=discord.Embed( - title="Error", - description=f"Failed to convert image: {e}", - color=0xE02B2B, - ).set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp"), ephemeral=True) + await context.send( + embed=discord.Embed( + title="Error", + description=f"Failed to convert image: {e}", + color=0xE02B2B, + ).set_author( + name="Media", + icon_url="https://yes.nighty.works/raw/y5SEZ9.webp", + ), + ephemeral=True, + ) finally: try: for f in os.listdir(tmp_dir): @@ -127,5 +184,3 @@ def img2gif_command(): pass return img2gif - - diff --git a/cogs/media/mcquote.py b/cogs/media/mcquote.py index 60bd960..3be0fca 100644 --- a/cogs/media/mcquote.py +++ b/cogs/media/mcquote.py @@ -6,6 +6,7 @@ from discord.ext import commands import aiohttp import random + def mcquote_command(): @commands.hybrid_command( name="mcquote", @@ -19,8 +20,10 @@ def mcquote_command(): description="This command can only be used in servers.", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) + interaction = getattr(context, "interaction", None) if interaction is not None: if not interaction.response.is_done(): @@ -37,8 +40,10 @@ def mcquote_command(): description="The bot needs the `send messages` permission in this channel.", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) + interaction = getattr(context, "interaction", None) if interaction is not None: if not interaction.response.is_done(): @@ -55,8 +60,10 @@ def mcquote_command(): description="Please provide text for the Minecraft quote.", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) + interaction = getattr(context, "interaction", None) if interaction is not None: if not interaction.response.is_done(): @@ -73,8 +80,10 @@ def mcquote_command(): description="Text must be 25 characters or less.", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) + interaction = getattr(context, "interaction", None) if interaction is not None: if not interaction.response.is_done(): @@ -88,7 +97,9 @@ def mcquote_command(): # Check if bot has send messages permission before starting quote generation try: test_embed = discord.Embed(title="Testing permissions...", color=0x7289DA) - test_embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + test_embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) await context.channel.send(embed=test_embed, delete_after=0.1) except discord.Forbidden: embed = discord.Embed( @@ -96,8 +107,10 @@ def mcquote_command(): description="The bot needs the `send messages` permission to execute this command.", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) + interaction = getattr(context, "interaction", None) if interaction is not None: if not interaction.response.is_done(): @@ -113,12 +126,16 @@ def mcquote_command(): description=" Generating quote... This may take a moment.", color=0x7289DA, ) - processing_embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - + processing_embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) + interaction = getattr(context, "interaction", None) if interaction is not None: if not interaction.response.is_done(): - await interaction.response.send_message(embed=processing_embed, ephemeral=True) + await interaction.response.send_message( + embed=processing_embed, ephemeral=True + ) else: await interaction.followup.send(embed=processing_embed, ephemeral=True) else: @@ -126,7 +143,7 @@ def mcquote_command(): quote_text = text.replace(" ", "+") random_number = random.randint(1, 39) - mc_quote_url = f'https://skinmc.net/achievement/{random_number}/Achievement+Unlocked!/{quote_text}' + mc_quote_url = f"https://skinmc.net/achievement/{random_number}/Achievement+Unlocked!/{quote_text}" try: async with aiohttp.ClientSession() as session: @@ -142,12 +159,17 @@ def mcquote_command(): title="Minecraft Quote", color=0x7289DA, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - embed.set_footer(text=f"Requested by {context.author.name}", icon_url=context.author.display_avatar.url) + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) + embed.set_footer( + text=f"Requested by {context.author.name}", + icon_url=context.author.display_avatar.url, + ) - with open(temp_file_path, 'rb') as f: + with open(temp_file_path, "rb") as f: file = discord.File(f, filename="mcquote.png") - + interaction = getattr(context, "interaction", None) if interaction is not None: await context.channel.send(embed=embed) @@ -168,8 +190,10 @@ def mcquote_command(): description="Failed to generate Minecraft quote. Please try again later.", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) + interaction = getattr(context, "interaction", None) if interaction is not None: try: @@ -186,8 +210,10 @@ def mcquote_command(): description=f"An unexpected error occurred: {str(e)}", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) + interaction = getattr(context, "interaction", None) if interaction is not None: try: @@ -198,5 +224,5 @@ def mcquote_command(): else: await processing_msg.delete() await context.send(embed=embed, ephemeral=True) - - return mcquote \ No newline at end of file + + return mcquote diff --git a/cogs/media/tts.py b/cogs/media/tts.py index f1a097e..0fd6496 100644 --- a/cogs/media/tts.py +++ b/cogs/media/tts.py @@ -11,8 +11,8 @@ from gtts import gTTS DEFAULT_LANG = "en" -def tts_command(): +def tts_command(): async def send_embed( context: commands.Context, embed: discord.Embed, @@ -24,14 +24,20 @@ def tts_command(): if interaction is not None: if interaction.response.is_done(): if file: - await interaction.followup.send(embed=embed, file=file, ephemeral=ephemeral) + await interaction.followup.send( + embed=embed, file=file, ephemeral=ephemeral + ) else: await interaction.followup.send(embed=embed, ephemeral=ephemeral) else: if file: - await interaction.response.send_message(embed=embed, file=file, ephemeral=ephemeral) + await interaction.response.send_message( + embed=embed, file=file, ephemeral=ephemeral + ) else: - await interaction.response.send_message(embed=embed, ephemeral=ephemeral) + await interaction.response.send_message( + embed=embed, ephemeral=ephemeral + ) else: if file: await context.send(embed=embed, file=file) @@ -42,8 +48,7 @@ def tts_command(): try: loop = asyncio.get_event_loop() audio_bytes = await loop.run_in_executor( - None, - lambda: _generate_tts_sync(text) + None, lambda: _generate_tts_sync(text) ) return audio_bytes, None except Exception as e: @@ -70,8 +75,10 @@ def tts_command(): description="This command can only be used in servers.", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) + interaction = getattr(context, "interaction", None) if interaction is not None: if not interaction.response.is_done(): @@ -88,8 +95,10 @@ def tts_command(): description="The bot needs the `send messages` permission in this channel.", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) + interaction = getattr(context, "interaction", None) if interaction is not None: if not interaction.response.is_done(): @@ -101,29 +110,33 @@ def tts_command(): return if not text or not text.strip(): - if context.message and context.message.reference and context.message.reference.resolved: + if ( + context.message + and context.message.reference + and context.message.reference.resolved + ): referenced = context.message.reference.resolved if isinstance(referenced, discord.Message) and referenced.content: text = referenced.content if not text or not text.strip(): - embed = ( - discord.Embed( - title="Error", - description="Please provide text to convert or reply to a message containing text.", - color=0xE02B2B, - ).set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + embed = discord.Embed( + title="Error", + description="Please provide text to convert or reply to a message containing text.", + color=0xE02B2B, + ).set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" ) await send_embed(context, embed, ephemeral=True) return text = text.strip() if len(text) > 500: - embed = ( - discord.Embed( - title="Error", - description="Text is too long. Please limit to 500 characters.", - color=0xE02B2B, - ).set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + embed = discord.Embed( + title="Error", + description="Text is too long. Please limit to 500 characters.", + color=0xE02B2B, + ).set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" ) await send_embed(context, embed, ephemeral=True) return @@ -131,7 +144,9 @@ def tts_command(): # Check if bot has send messages permission before starting TTS generation try: test_embed = discord.Embed(title="Testing permissions...", color=0x7289DA) - test_embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + test_embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) await context.channel.send(embed=test_embed, delete_after=0.1) except discord.Forbidden: embed = discord.Embed( @@ -139,8 +154,10 @@ def tts_command(): description="The bot needs the `send messages` permission to execute this command.", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) + interaction = getattr(context, "interaction", None) if interaction is not None: if not interaction.response.is_done(): @@ -151,43 +168,45 @@ def tts_command(): await context.send(embed=embed, ephemeral=True) return - processing_embed = ( - discord.Embed( - title="TTS (Processing)", - description=" Generating speech...", - color=0x7289DA, - ).set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - ) + processing_embed = discord.Embed( + title="TTS (Processing)", + description=" Generating speech...", + color=0x7289DA, + ).set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") interaction = getattr(context, "interaction", None) processing_message = None sent_initial_interaction_response = False if interaction is not None: if interaction.response.is_done(): - processing_message = await interaction.followup.send(embed=processing_embed, ephemeral=True) + processing_message = await interaction.followup.send( + embed=processing_embed, ephemeral=True + ) else: - await interaction.response.send_message(embed=processing_embed, ephemeral=True) + await interaction.response.send_message( + embed=processing_embed, ephemeral=True + ) sent_initial_interaction_response = True if not interaction.response.is_done(): await interaction.response.defer(ephemeral=False) else: - processing_embed = ( - discord.Embed( - title="TTS (Processing)", - description=" Generating speech...", - color=0x7289DA, - ).set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + processing_embed = discord.Embed( + title="TTS (Processing)", + description=" Generating speech...", + color=0x7289DA, + ).set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" ) processing_message = await context.send(embed=processing_embed) audio_bytes, error = await generate_tts_audio(text) if error or not audio_bytes: - embed = ( - discord.Embed( - title="Error", - description=f"Failed to generate speech. {error or 'Unknown error.'}", - color=0xE02B2B, - ).set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + embed = discord.Embed( + title="Error", + description=f"Failed to generate speech. {error or 'Unknown error.'}", + color=0xE02B2B, + ).set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" ) await send_embed(context, embed, ephemeral=True) if interaction is not None and sent_initial_interaction_response: @@ -213,7 +232,9 @@ def tts_command(): description=f"**Input:** {text}", color=0x7289DA, ) - .set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + .set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) .set_footer( text=f"Requested by {context.author.display_name}", icon_url=getattr(context.author.display_avatar, "url", None), @@ -232,4 +253,4 @@ def tts_command(): await context.channel.send(embed=embed) await context.channel.send(file=audio_file) - return tts \ No newline at end of file + return tts diff --git a/cogs/media/tweety.py b/cogs/media/tweety.py index e506ef5..85179af 100644 --- a/cogs/media/tweety.py +++ b/cogs/media/tweety.py @@ -16,22 +16,25 @@ import asyncio def break_long_words(text: str, max_word_length: int = 50) -> str: - words = text.split(' ') + words = text.split(" ") result = [] - + for word in words: if len(word) > max_word_length: - chunks = [word[i:i+max_word_length] for i in range(0, len(word), max_word_length)] - result.append(' '.join(chunks)) + chunks = [ + word[i : i + max_word_length] + for i in range(0, len(word), max_word_length) + ] + result.append(" ".join(chunks)) else: result.append(word) - - return ' '.join(result) + + return " ".join(result) class TweetyHelpView(discord.ui.View): """This is the help view for the slash command cuz only the pinging and prefix versions can use this command""" - + def __init__(self, user_id: int, bot): super().__init__(timeout=180) self.user_id = user_id @@ -41,22 +44,30 @@ class TweetyHelpView(discord.ui.View): { "title": "Tweety (Method 1)", "description": "Use the prefix command `.media tweety` while replying to a message.", - "gif_url": "https://yes.nighty.works/raw/VrKX1L.gif", + "gif_url": "https://yes.nighty.works/raw/VrKX1L.gif", "fields": [ - {"name": "How to use", "value": "1. Reply to any message\n2. Type `.media tweety`\n3. Use the buttons to customize!", "inline": False}, - ] + { + "name": "How to use", + "value": "1. Reply to any message\n2. Type `.media tweety`\n3. Use the buttons to customize!", + "inline": False, + }, + ], }, { "title": "Tweety (Method 2)", "description": f"Mention <@{bot.user.id}> with `tweety` while replying to a message.", - "gif_url": "https://yes.nighty.works/raw/9XEe9j.gif", + "gif_url": "https://yes.nighty.works/raw/9XEe9j.gif", "fields": [ - {"name": "How to use", "value": f"1. Reply to any message\n2. Type `<@{bot.user.id}> tweety`\n3. Use the buttons to customize!", "inline": False}, - ] - } + { + "name": "How to use", + "value": f"1. Reply to any message\n2. Type `<@{bot.user.id}> tweety`\n3. Use the buttons to customize!", + "inline": False, + }, + ], + }, ] self.update_buttons() - + def create_embed(self): page = self.pages[self.current_page] embed = discord.Embed( @@ -64,68 +75,87 @@ class TweetyHelpView(discord.ui.View): description=page["description"], color=0x7289DA, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") - + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) + for field in page["fields"]: - embed.add_field(name=field["name"], value=field["value"], inline=field["inline"]) - + embed.add_field( + name=field["name"], value=field["value"], inline=field["inline"] + ) + embed.set_image(url=page["gif_url"]) embed.set_footer(text=f"Page {self.current_page + 1}/{len(self.pages)}") - + return embed - + def update_buttons(self): self.clear_items() - + prev_button = discord.ui.Button( label="Prev", style=discord.ButtonStyle.secondary, emoji=discord.PartialEmoji(name="left", id=1420240344926126090), - disabled=self.current_page == 0 + disabled=self.current_page == 0, ) prev_button.callback = self.previous_page self.add_item(prev_button) - + next_button = discord.ui.Button( label="Next", style=discord.ButtonStyle.secondary, emoji=discord.PartialEmoji(name="right", id=1420240334100627456), - disabled=self.current_page == len(self.pages) - 1 + disabled=self.current_page == len(self.pages) - 1, ) next_button.callback = self.next_page self.add_item(next_button) - + async def previous_page(self, interaction: discord.Interaction): if interaction.user.id != self.user_id: - await interaction.response.send_message("You can't control someone else's help menu!", ephemeral=True) + await interaction.response.send_message( + "You can't control someone else's help menu!", ephemeral=True + ) return - + if self.current_page > 0: self.current_page -= 1 self.update_buttons() - await interaction.response.edit_message(embed=self.create_embed(), view=self) + await interaction.response.edit_message( + embed=self.create_embed(), view=self + ) else: await interaction.response.defer() - + async def next_page(self, interaction: discord.Interaction): if interaction.user.id != self.user_id: - await interaction.response.send_message("You can't control someone else's help menu!", ephemeral=True) + await interaction.response.send_message( + "You can't control someone else's help menu!", ephemeral=True + ) return - + if self.current_page < len(self.pages) - 1: self.current_page += 1 self.update_buttons() - await interaction.response.edit_message(embed=self.create_embed(), view=self) + await interaction.response.edit_message( + embed=self.create_embed(), view=self + ) else: await interaction.response.defer() - + async def on_timeout(self): for item in self.children: item.disabled = True class TweetyView(discord.ui.View): - def __init__(self, author_id: int, original_message, tweet_data: dict, api_url: str, image_message: Optional[discord.Message] = None): + def __init__( + self, + author_id: int, + original_message, + tweet_data: dict, + api_url: str, + image_message: Optional[discord.Message] = None, + ): super().__init__(timeout=300) self.author_id = author_id self.original_message = original_message @@ -138,28 +168,34 @@ class TweetyView(discord.ui.View): self.click_count = 0 self.update_button_styles() - + def update_button_styles(self): self.clear_items() - + dark_button = discord.ui.Button( label="Dark Mode" if self.is_dark else "Light Mode", - style=discord.ButtonStyle.primary if self.is_dark else discord.ButtonStyle.secondary, + style=discord.ButtonStyle.primary + if self.is_dark + else discord.ButtonStyle.secondary, emoji=discord.PartialEmoji(name="darkmode", id=1425165393751965884), - custom_id="toggle_dark" + custom_id="toggle_dark", ) dark_button.callback = self.toggle_dark_callback self.add_item(dark_button) - + verified_button = discord.ui.Button( label="Verified", - style=discord.ButtonStyle.primary if self.is_verified else discord.ButtonStyle.secondary, - emoji=discord.PartialEmoji(name="TwitterVerifiedBadge", id=1425165432142172392), - custom_id="toggle_verified" + style=discord.ButtonStyle.primary + if self.is_verified + else discord.ButtonStyle.secondary, + emoji=discord.PartialEmoji( + name="TwitterVerifiedBadge", id=1425165432142172392 + ), + custom_id="toggle_verified", ) verified_button.callback = self.toggle_verified_callback self.add_item(verified_button) - + async def regenerate_tweet(self, interaction: discord.Interaction): """Regenerate only the image message with current settings""" if self.click_count >= 10: @@ -168,7 +204,9 @@ class TweetyView(discord.ui.View): description="Stop spamming! You've reached the maximum number of changes for this tweet.", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) await interaction.response.send_message(embed=embed, ephemeral=True) return @@ -176,7 +214,7 @@ class TweetyView(discord.ui.View): current_time = time.time() time_since_last = current_time - self.last_regenerate_time - + if time_since_last < 3 and self.last_regenerate_time > 0: wait_time = 3 - time_since_last await asyncio.sleep(wait_time) @@ -189,9 +227,8 @@ class TweetyView(discord.ui.View): async with session.post( f"{self.api_url}/api/render", json=self.tweet_data, - headers={"Content-Type": "application/json"} + headers={"Content-Type": "application/json"}, ) as response: - if response.status != 200: error_text = await response.text() embed = discord.Embed( @@ -199,23 +236,27 @@ class TweetyView(discord.ui.View): description=f"API Error ({response.status}): {error_text}", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + embed.set_author( + name="Media", + icon_url="https://yes.nighty.works/raw/y5SEZ9.webp", + ) await interaction.followup.send(embed=embed, ephemeral=True) return - + image_data = await response.read() - - with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp_file: + + with tempfile.NamedTemporaryFile( + delete=False, suffix=".png" + ) as temp_file: temp_file.write(image_data) temp_file_path = temp_file.name - - with open(temp_file_path, 'rb') as f: + + with open(temp_file_path, "rb") as f: author_name = self.original_message.author.name - filename = f"tweet_{author_name}_{int(datetime.now().timestamp())}.png" - file = discord.File( - f, - filename=filename + filename = ( + f"tweet_{author_name}_{int(datetime.now().timestamp())}.png" ) + file = discord.File(f, filename=filename) self.update_button_styles() @@ -223,18 +264,20 @@ class TweetyView(discord.ui.View): await self.image_message.edit(attachments=[file], view=self) else: await interaction.followup.send(file=file, view=self) - + os.remove(temp_file_path) - + except Exception as e: embed = discord.Embed( title="Error", description="Error regenerating tweet image", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) await interaction.followup.send(embed=embed, ephemeral=True) - + async def _check_author(self, interaction: discord.Interaction) -> bool: """Check if user is authorized to modify the tweet""" if interaction.user.id != self.author_id: @@ -243,11 +286,13 @@ class TweetyView(discord.ui.View): description="You can't modify someone else's tweet!", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) await interaction.response.send_message(embed=embed, ephemeral=True) return False return True - + async def toggle_dark_callback(self, interaction: discord.Interaction): """Handle dark mode toggle button click""" if not await self._check_author(interaction): @@ -255,7 +300,7 @@ class TweetyView(discord.ui.View): self.is_dark = not self.is_dark self.tweet_data["dark"] = self.is_dark await self.regenerate_tweet(interaction) - + async def toggle_verified_callback(self, interaction: discord.Interaction): """Handle verified toggle button click""" if not await self._check_author(interaction): @@ -263,16 +308,16 @@ class TweetyView(discord.ui.View): self.is_verified = not self.is_verified self.tweet_data["verified"] = self.is_verified await self.regenerate_tweet(interaction) - + async def on_timeout(self): """Disable buttons when view times out""" for item in self.children: item.disabled = True + def tweety_command(): @commands.hybrid_command( - name="tweety", - description="Convert a replied message to a tweet image." + name="tweety", description="Convert a replied message to a tweet image." ) @commands.cooldown(1, 10, commands.BucketType.user) async def tweety(self, context): @@ -281,44 +326,60 @@ def tweety_command(): embed = view.create_embed() await context.send(embed=embed, view=view, ephemeral=True) return - + if not context.message.reference or not context.message.reference.message_id: embed = discord.Embed( title="Error", description="You must reply to a message to use this command!", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) await context.send(embed=embed) return - + try: - original_message = await context.channel.fetch_message(context.message.reference.message_id) + original_message = await context.channel.fetch_message( + context.message.reference.message_id + ) processing_embed = discord.Embed( title="Tweet Generator (Processing)", description=" Generating tweet... This may take a moment.", color=0x7289DA, ) - processing_embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + processing_embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) processing_msg = await context.send(embed=processing_embed) author = original_message.author display_name = author.display_name or author.name username = f"@{author.name}" - avatar_url = str(author.avatar.url) if author.avatar else str(author.default_avatar.url) + avatar_url = ( + str(author.avatar.url) + if author.avatar + else str(author.default_avatar.url) + ) message_text = original_message.content - + for mention in original_message.mentions: - message_text = message_text.replace(f'<@{mention.id}>', f'@{mention.name}') - message_text = message_text.replace(f'<@!{mention.id}>', f'@{mention.name}') - + message_text = message_text.replace( + f"<@{mention.id}>", f"@{mention.name}" + ) + message_text = message_text.replace( + f"<@!{mention.id}>", f"@{mention.name}" + ) + for role in original_message.role_mentions: - message_text = message_text.replace(f'<@&{role.id}>', f'@{role.name}') - + message_text = message_text.replace(f"<@&{role.id}>", f"@{role.name}") + for channel in original_message.channel_mentions: - message_text = message_text.replace(f'<#{channel.id}>', f'#{channel.name}') - + message_text = message_text.replace( + f"<#{channel.id}>", f"#{channel.name}" + ) + if not message_text.strip(): await processing_msg.delete() embed = discord.Embed( @@ -326,10 +387,12 @@ def tweety_command(): description="No text found! This command only works with text messages.", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) await context.send(embed=embed) return - + if len(message_text) > 300: await processing_msg.delete() embed = discord.Embed( @@ -337,13 +400,15 @@ def tweety_command(): description=f"Message is too long! Maximum 300 characters allowed.\nYour message: {len(message_text)} characters", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) await context.send(embed=embed) return - + message_text = break_long_words(message_text, max_word_length=50) - - ny_tz = pytz.timezone('America/New_York') + + ny_tz = pytz.timezone("America/New_York") msg_time_ny = original_message.created_at.astimezone(ny_tz) timestamp = msg_time_ny.strftime("%I:%M %p ยท %b %d, %Y").replace(" 0", " ") tweet_data = { @@ -353,19 +418,18 @@ def tweety_command(): "avatar": avatar_url, "timestamp": timestamp, "verified": False, - "dark": False + "dark": False, } - + API_BASE_URL = "http://tweet.6969.pro" - + async with aiohttp.ClientSession() as session: try: async with session.post( f"{API_BASE_URL}/api/render", json=tweet_data, - headers={"Content-Type": "application/json"} + headers={"Content-Type": "application/json"}, ) as response: - if response.status != 200: await processing_msg.delete() error_text = await response.text() @@ -374,26 +438,37 @@ def tweety_command(): description=f"API Error ({response.status}): {error_text}", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + embed.set_author( + name="Media", + icon_url="https://yes.nighty.works/raw/y5SEZ9.webp", + ) await context.send(embed=embed) return - + image_data = await response.read() - - with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp_file: + + with tempfile.NamedTemporaryFile( + delete=False, suffix=".png" + ) as temp_file: temp_file.write(image_data) temp_file_path = temp_file.name await processing_msg.delete() - - with open(temp_file_path, 'rb') as f: - file = discord.File(f, filename=f"tweet_{author.name}_{int(datetime.now().timestamp())}.png") + + with open(temp_file_path, "rb") as f: + file = discord.File( + f, + filename=f"tweet_{author.name}_{int(datetime.now().timestamp())}.png", + ) embed = discord.Embed( title="Tweet Generated", description=f"<:error:1424007141768822824> Tweet sometimes may look a bit broken, im gonna rewrite the API another time... (it wasnt made for Syntrel in the first place)", color=0x7289DA, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + embed.set_author( + name="Media", + icon_url="https://yes.nighty.works/raw/y5SEZ9.webp", + ) embed.set_footer( text=f"Requested by {context.author.name}", icon_url=context.author.display_avatar.url, @@ -403,7 +478,7 @@ def tweety_command(): author_id=context.author.id, original_message=original_message, tweet_data=tweet_data, - api_url=API_BASE_URL + api_url=API_BASE_URL, ) await context.send(embed=embed) @@ -411,7 +486,7 @@ def tweety_command(): view.image_message = image_message os.remove(temp_file_path) - + except aiohttp.ClientError: await processing_msg.delete() embed = discord.Embed( @@ -419,7 +494,10 @@ def tweety_command(): description=f"Connection error: Could not reach tweet API", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + embed.set_author( + name="Media", + icon_url="https://yes.nighty.works/raw/y5SEZ9.webp", + ) await context.send(embed=embed) except Exception: await processing_msg.delete() @@ -428,7 +506,10 @@ def tweety_command(): description="Error generating tweet image", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + embed.set_author( + name="Media", + icon_url="https://yes.nighty.works/raw/y5SEZ9.webp", + ) await context.send(embed=embed) except Exception: @@ -437,7 +518,9 @@ def tweety_command(): description="Error processing the message!", color=0xE02B2B, ) - embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp") + embed.set_author( + name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp" + ) await context.send(embed=embed) - + return tweety diff --git a/cogs/melonx/__init__.py b/cogs/melonx/__init__.py index f2c4b01..44fe054 100644 --- a/cogs/melonx/__init__.py +++ b/cogs/melonx/__init__.py @@ -25,9 +25,11 @@ class Melonx(commands.GroupCog, name="melonx"): embed = discord.Embed( title="MeloNX Commands", description="Choose a command from the dropdown below to get help with specific issues:", - color=0x963155 + color=0x963155, + ) + embed.set_author( + name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png" ) - embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png") view = MelonxView(self.bot) await context.send(embed=embed, view=view) @@ -36,9 +38,11 @@ class Melonx(commands.GroupCog, name="melonx"): embed = discord.Embed( title="MeloNX Commands", description="Choose a command from the dropdown below to get help with specific issues:", - color=0x963155 + color=0x963155, + ) + embed.set_author( + name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png" ) - embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png") view = MelonxView(self.bot) await context.send(embed=embed, view=view) @@ -87,80 +91,68 @@ class Melonx(commands.GroupCog, name="melonx"): content = context.message.content.strip().lower() return content.startswith(f"{prefix}{group} ") - @app_commands.command( - name="help", - description="MeloNX troubleshooting help" - ) + @app_commands.command(name="help", description="MeloNX troubleshooting help") async def help(self, interaction: discord.Interaction): embed = discord.Embed( title="MeloNX Commands", description="Choose a command from the dropdown below to get help with specific issues:", - color=0x963155 + color=0x963155, + ) + embed.set_author( + name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png" ) - embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png") view = MelonxView(self.bot) await interaction.response.send_message(embed=embed, view=view, ephemeral=True) @commands.check(_require_group_prefix) @commands.hybrid_command( name="transfer", - description="How to transfer save files from other emulators or platforms" + description="How to transfer save files from other emulators or platforms", ) async def transfer(self, context): return await transfer_command()(self, context) @commands.check(_require_group_prefix) @commands.hybrid_command( - name="mods", - description="How to install mods within MeloNX (Limited Support)" + name="mods", description="How to install mods within MeloNX (Limited Support)" ) async def mods(self, context): return await mods_command()(self, context) @commands.check(_require_group_prefix) - @commands.hybrid_command( - name="legal", - description="Legality of emulators" - ) + @commands.hybrid_command(name="legal", description="Legality of emulators") async def legal(self, context): return await legal_command()(self, context) @commands.check(_require_group_prefix) - @commands.hybrid_command( - name="gamecrash", - description="Why does my game crash?" - ) + @commands.hybrid_command(name="gamecrash", description="Why does my game crash?") async def gamecrash(self, context): return await crash_command()(self, context) @commands.check(_require_group_prefix) @commands.hybrid_command( - name="requirements", - description="What does MeloNX require?" + name="requirements", description="What does MeloNX require?" ) async def requirements(self, context): return await requirements_command()(self, context) @commands.check(_require_group_prefix) @commands.hybrid_command( - name="error", - description="What does this error message mean?" + name="error", description="What does this error message mean?" ) async def error(self, context): return await error_command()(self, context) @commands.check(_require_group_prefix) - @commands.hybrid_command( - name="26", - description="How can I run MeloNX on iOS 26?" - ) + @commands.hybrid_command(name="26", description="How can I run MeloNX on iOS 26?") async def ios26(self, context): return await ios26_command()(self, context) + async def setup(bot) -> None: cog = Melonx(bot) await bot.add_cog(cog) - + bot.logger.info("Loaded extension 'melonx.help'") bot.logger.info("Loaded extension 'melonx.transfer'") bot.logger.info("Loaded extension 'melonx.mods'") diff --git a/cogs/melonx/error.py b/cogs/melonx/error.py index 19fef03..c02c45e 100644 --- a/cogs/melonx/error.py +++ b/cogs/melonx/error.py @@ -13,35 +13,40 @@ def error_command(): embed = discord.Embed( color=0x963155, description=( - '# What does this error message mean?\n\n---\n\n' + - '**1. "MeloNX Crashed! System.SystemException: Cannot allocate memory"**' + - 'You likely don\'t have the increased memory limit entitlement enabled, are using an a12 chipset, and have 4GB or less of memory. You can see the status of the entitlement for MeloNX under the Settings tab.\n\n' + - '**2. "MeloNX Crashed! LibHac.Common.HorizonResultException: ResultLoaderInvalidNso (2009-0005)"**' + - 'This is likely a bad game / update / or DLC dump. redump your files and try again.' - ) + "# What does this error message mean?\n\n---\n\n" + + '**1. "MeloNX Crashed! System.SystemException: Cannot allocate memory"**' + + "You likely don't have the increased memory limit entitlement enabled, are using an a12 chipset, and have 4GB or less of memory. You can see the status of the entitlement for MeloNX under the Settings tab.\n\n" + + '**2. "MeloNX Crashed! LibHac.Common.HorizonResultException: ResultLoaderInvalidNso (2009-0005)"**' + + "This is likely a bad game / update / or DLC dump. redump your files and try again." + ), ) - embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png") - embed.set_footer(text=f'Last Edited by Meshal :D') + embed.set_author( + name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png" + ) + embed.set_footer(text=f"Last Edited by Meshal :D") embed.timestamp = discord.utils.utcnow() view = discord.ui.View() - view.add_item(discord.ui.Button( - label="Edit Command", - style=discord.ButtonStyle.secondary, - url="https://github.com/neoarz/Syntrel/blob/main/cogs/melonx/error.py", - emoji="<:githubicon:1417717356846776340>" - )) - view.add_item(discord.ui.Button( - label="MeloNX Discord", - style=discord.ButtonStyle.primary, - url="https://discord.gg/EMXB2XYQgA", - emoji="<:Discord:1428762057758474280>" - )) + view.add_item( + discord.ui.Button( + label="Edit Command", + style=discord.ButtonStyle.secondary, + url="https://github.com/neoarz/Syntrel/blob/main/cogs/melonx/error.py", + emoji="<:githubicon:1417717356846776340>", + ) + ) + view.add_item( + discord.ui.Button( + label="MeloNX Discord", + style=discord.ButtonStyle.primary, + url="https://discord.gg/EMXB2XYQgA", + emoji="<:Discord:1428762057758474280>", + ) + ) if context.interaction: await context.interaction.response.send_message(embed=embed, view=view) else: await context.send(embed=embed, view=view) - return error diff --git a/cogs/melonx/gamecrash.py b/cogs/melonx/gamecrash.py index efdd39f..2768eff 100644 --- a/cogs/melonx/gamecrash.py +++ b/cogs/melonx/gamecrash.py @@ -6,47 +6,50 @@ import time def crash_command(): - @commands.hybrid_command( - name="gamecrash", description="Why does my game crash?" - ) + @commands.hybrid_command(name="gamecrash", description="Why does my game crash?") async def crash(self, context): embed = discord.Embed( color=0x963155, description=( - '# "Why does my game crash?"\n\n---\n\n' + - '**This can be caused by multiple reasons:**\n' + - '- Not enough available ram\n' + - '- You may have tried to update your firmware without updating your keys\n' + - '- Game file is corrupted/broken\n' + - '- Game requires a higher firmware+keys combination than you currently have\n' + - '- In rare cases some games also crash when not having the resolution set to 1x\n' + - '- The a12 chipset have a lot of issues at the moment\n' + - '- Shader cache isn\'t cleared after updating MeloNX\n' + - '- iOS 15 and 16 are known to have some compatibility issues' - ) + '# "Why does my game crash?"\n\n---\n\n' + + "**This can be caused by multiple reasons:**\n" + + "- Not enough available ram\n" + + "- You may have tried to update your firmware without updating your keys\n" + + "- Game file is corrupted/broken\n" + + "- Game requires a higher firmware+keys combination than you currently have\n" + + "- In rare cases some games also crash when not having the resolution set to 1x\n" + + "- The a12 chipset have a lot of issues at the moment\n" + + "- Shader cache isn't cleared after updating MeloNX\n" + + "- iOS 15 and 16 are known to have some compatibility issues" + ), ) - embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png") - embed.set_footer(text=f'Last Edited by Meshal :D') + embed.set_author( + name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png" + ) + embed.set_footer(text=f"Last Edited by Meshal :D") embed.timestamp = discord.utils.utcnow() view = discord.ui.View() - view.add_item(discord.ui.Button( - label="Edit Command", - style=discord.ButtonStyle.secondary, - url="https://github.com/neoarz/Syntrel/blob/main/cogs/melonx/gamecrash.py", - emoji="<:githubicon:1417717356846776340>" - )) - view.add_item(discord.ui.Button( - label="MeloNX Discord", - style=discord.ButtonStyle.primary, - url="https://discord.gg/EMXB2XYQgA", - emoji="<:Discord:1428762057758474280>" - )) + view.add_item( + discord.ui.Button( + label="Edit Command", + style=discord.ButtonStyle.secondary, + url="https://github.com/neoarz/Syntrel/blob/main/cogs/melonx/gamecrash.py", + emoji="<:githubicon:1417717356846776340>", + ) + ) + view.add_item( + discord.ui.Button( + label="MeloNX Discord", + style=discord.ButtonStyle.primary, + url="https://discord.gg/EMXB2XYQgA", + emoji="<:Discord:1428762057758474280>", + ) + ) if context.interaction: await context.interaction.response.send_message(embed=embed, view=view) else: await context.send(embed=embed, view=view) - return crash diff --git a/cogs/melonx/ios26.py b/cogs/melonx/ios26.py index 0cc4526..cd0a24c 100644 --- a/cogs/melonx/ios26.py +++ b/cogs/melonx/ios26.py @@ -6,47 +6,50 @@ import time def ios26_command(): - @commands.hybrid_command( - name="26", description="How can I run MeloNX on iOS 26?" - ) + @commands.hybrid_command(name="26", description="How can I run MeloNX on iOS 26?") async def ios26(self, context): embed = discord.Embed( color=0x963155, description=( - '# "How can I run MeloNX on iOS 26?"\n\n---\n\n' + - '### StikDebug\n' + - '1. Make sure StikDebug is on the latest version.\n' + - '2. Turn on "Picture in Picture" in StikDebug\'s settings.\n\n' + - '### MeloNX\n' + - 'Make sure you\'re on the latest version of MeloNX.\n' + - '## Disclaimer:\n\n' + - 'If you\'re on iOS 18 or below, and emulation is essential to you:\n\n' + - '## <:error:1424007141768822824> DO NOT UPDATE <:error:1424007141768822824> \n\n' + - 'iOS 26 has many issues related to emulation.' - ) + '# "How can I run MeloNX on iOS 26?"\n\n---\n\n' + + "### StikDebug\n" + + "1. Make sure StikDebug is on the latest version.\n" + + '2. Turn on "Picture in Picture" in StikDebug\'s settings.\n\n' + + "### MeloNX\n" + + "Make sure you're on the latest version of MeloNX.\n" + + "## Disclaimer:\n\n" + + "If you're on iOS 18 or below, and emulation is essential to you:\n\n" + + "## <:error:1424007141768822824> DO NOT UPDATE <:error:1424007141768822824> \n\n" + + "iOS 26 has many issues related to emulation." + ), ) - embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png") - embed.set_footer(text=f'Last Edited by Meshal :D') + embed.set_author( + name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png" + ) + embed.set_footer(text=f"Last Edited by Meshal :D") embed.timestamp = discord.utils.utcnow() view = discord.ui.View() - view.add_item(discord.ui.Button( - label="Edit Command", - style=discord.ButtonStyle.secondary, - url="https://github.com/neoarz/Syntrel/blob/main/cogs/melonx/ios26.py", - emoji="<:githubicon:1417717356846776340>" - )) - view.add_item(discord.ui.Button( - label="MeloNX Discord", - style=discord.ButtonStyle.primary, - url="https://discord.gg/EMXB2XYQgA", - emoji="<:Discord:1428762057758474280>" - )) + view.add_item( + discord.ui.Button( + label="Edit Command", + style=discord.ButtonStyle.secondary, + url="https://github.com/neoarz/Syntrel/blob/main/cogs/melonx/ios26.py", + emoji="<:githubicon:1417717356846776340>", + ) + ) + view.add_item( + discord.ui.Button( + label="MeloNX Discord", + style=discord.ButtonStyle.primary, + url="https://discord.gg/EMXB2XYQgA", + emoji="<:Discord:1428762057758474280>", + ) + ) if context.interaction: await context.interaction.response.send_message(embed=embed, view=view) else: await context.send(embed=embed, view=view) - return ios26 diff --git a/cogs/melonx/legal.py b/cogs/melonx/legal.py index 932b438..ed40c3f 100644 --- a/cogs/melonx/legal.py +++ b/cogs/melonx/legal.py @@ -6,60 +6,62 @@ import time def legal_command(): - @commands.hybrid_command( - name="legal", description="Legality of emulators." - ) + @commands.hybrid_command(name="legal", description="Legality of emulators.") async def legal(self, context): embed = discord.Embed( color=0x963155, - description = ( - '# Legality\n\n' + - '---\n\n' + - '## Overview\n' + - 'Emulators themselves are **legal**, as long as you use them with **legally dumped copies** of games **you** own, ' + - 'or with **homebrew software**.\n\n' + - 'Read about the landmark case [**Sony v. Bleem!** (2000)](https://www.copyright.gov/fair-use/summaries/sonycomputer-bleem-9thcir2000.pdf), ' + - 'which helped set the precedent for emulation being legal. You can also watch ' + - '[this video](https://www.youtube.com/watch?v=yj9Gk84jRiE) for more information.\n\n' - '## Legal Basis\n' + - 'According to the [**U.S. Copyright Act**](https://www.copyright.gov/title17/92chap1.html#117) ' + - '(the law under which Discord operates), you **must own a legal copy** of any game you play on **MeloNX**.\n\n' + - '- Downloading games you do not own is considered **piracy** and is **illegal**.\n' + - '- Even if another copy is identical, it still belongs to someone else, you are **not entitled** to it.\n\n' + - '## Our Stance on Piracy\n' + - 'We **do not support piracy**. Doing so would give Nintendo legal grounds to take us down.\n' + - 'And yes โ€” **Nintendo is aware of MeloNX\'s existence.**\n\n' + - 'We are not required to monitor user behavior, but we **strongly encourage** everyone to use only **legally obtained copies** of games.\n\n' + - '## Enforcement\n' + - 'If you are found using pirated games with MeloNX, you will be **banned** from this Discord server **without warning**.\n\n' + - '## Final Note\n' + - 'Thank you for understanding and respecting the hard work that went into creating both the emulator MeloNX is built on, ' + - 'and **MeloNX itself.**' - ) - + description=( + "# Legality\n\n" + + "---\n\n" + + "## Overview\n" + + "Emulators themselves are **legal**, as long as you use them with **legally dumped copies** of games **you** own, " + + "or with **homebrew software**.\n\n" + + "Read about the landmark case [**Sony v. Bleem!** (2000)](https://www.copyright.gov/fair-use/summaries/sonycomputer-bleem-9thcir2000.pdf), " + + "which helped set the precedent for emulation being legal. You can also watch " + + "[this video](https://www.youtube.com/watch?v=yj9Gk84jRiE) for more information.\n\n" + "## Legal Basis\n" + + "According to the [**U.S. Copyright Act**](https://www.copyright.gov/title17/92chap1.html#117) " + + "(the law under which Discord operates), you **must own a legal copy** of any game you play on **MeloNX**.\n\n" + + "- Downloading games you do not own is considered **piracy** and is **illegal**.\n" + + "- Even if another copy is identical, it still belongs to someone else, you are **not entitled** to it.\n\n" + + "## Our Stance on Piracy\n" + + "We **do not support piracy**. Doing so would give Nintendo legal grounds to take us down.\n" + + "And yes โ€” **Nintendo is aware of MeloNX's existence.**\n\n" + + "We are not required to monitor user behavior, but we **strongly encourage** everyone to use only **legally obtained copies** of games.\n\n" + + "## Enforcement\n" + + "If you are found using pirated games with MeloNX, you will be **banned** from this Discord server **without warning**.\n\n" + + "## Final Note\n" + + "Thank you for understanding and respecting the hard work that went into creating both the emulator MeloNX is built on, " + + "and **MeloNX itself.**" + ), ) - embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png") - embed.set_footer(text=f'Last Edited by stossy11') + embed.set_author( + name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png" + ) + embed.set_footer(text=f"Last Edited by stossy11") embed.timestamp = discord.utils.utcnow() view = discord.ui.View() - view.add_item(discord.ui.Button( - label="Edit Command", - style=discord.ButtonStyle.secondary, - url="https://github.com/neoarz/Syntrel/blob/main/cogs/melonx/legal.py", - emoji="<:githubicon:1417717356846776340>" - )) - view.add_item(discord.ui.Button( - label="MeloNX Discord", - style=discord.ButtonStyle.primary, - url="https://discord.gg/EMXB2XYQgA", - emoji="<:Discord:1428762057758474280>" - )) + view.add_item( + discord.ui.Button( + label="Edit Command", + style=discord.ButtonStyle.secondary, + url="https://github.com/neoarz/Syntrel/blob/main/cogs/melonx/legal.py", + emoji="<:githubicon:1417717356846776340>", + ) + ) + view.add_item( + discord.ui.Button( + label="MeloNX Discord", + style=discord.ButtonStyle.primary, + url="https://discord.gg/EMXB2XYQgA", + emoji="<:Discord:1428762057758474280>", + ) + ) if context.interaction: await context.interaction.response.send_message(embed=embed, view=view) else: await context.send(embed=embed, view=view) - return legal diff --git a/cogs/melonx/melonx.py b/cogs/melonx/melonx.py index b2c4d98..6dbc759 100644 --- a/cogs/melonx/melonx.py +++ b/cogs/melonx/melonx.py @@ -49,7 +49,7 @@ class MelonxSelect(discord.ui.Select): async def callback(self, interaction: discord.Interaction): command_name = self.values[0] command = self.bot.get_command(command_name) - + if command: try: ctx = await self.bot.get_context(interaction.message) @@ -58,44 +58,57 @@ class MelonxSelect(discord.ui.Select): embed = discord.Embed( title="Command Executed", description=f"Successfully executed `/{command_name}`", - color=0x00FF00 + color=0x00FF00, + ) + embed.set_author( + name="MeloNX", + icon_url="https://yes.nighty.works/raw/TLGaVa.png", ) - embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png") await interaction.response.edit_message(embed=embed, view=None) except discord.Forbidden: - guild_info = f"server {interaction.guild.name} (ID: {interaction.guild.id})" if interaction.guild else "DM or private channel" - self.bot.logger.warning(f"Bot missing permissions in {guild_info} - cannot execute {command_name} command") - + guild_info = ( + f"server {interaction.guild.name} (ID: {interaction.guild.id})" + if interaction.guild + else "DM or private channel" + ) + self.bot.logger.warning( + f"Bot missing permissions in {guild_info} - cannot execute {command_name} command" + ) + if interaction.guild is None: embed = discord.Embed( title="Error", description="This command cannot be executed in DMs.", - color=0xFF0000 + color=0xFF0000, ) else: embed = discord.Embed( title="Permission Error", description="The bot needs the `send messages` permission to execute this command.", - color=0xFF0000 + color=0xFF0000, ) - embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png") + embed.set_author( + name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png" + ) await interaction.response.edit_message(embed=embed, view=None) except Exception as e: self.bot.logger.error(f"Error executing {command_name} command: {e}") embed = discord.Embed( title="Error", description="An error occurred while executing the command.", - color=0x963155 + color=0x963155, + ) + embed.set_author( + name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png" ) - embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png") await interaction.response.edit_message(embed=embed, view=None) else: embed = discord.Embed( - title="Error", - description="Command not found!", - color=0x963155 + title="Error", description="Command not found!", color=0x963155 + ) + embed.set_author( + name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png" ) - embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png") await interaction.response.edit_message(embed=embed, view=None) @@ -116,10 +129,14 @@ def melonx_command(): description="This command can only be used in servers.", color=0xE02B2B, ) - embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png") - + embed.set_author( + name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png" + ) + if context.interaction: - await context.interaction.response.send_message(embed=embed, ephemeral=True) + await context.interaction.response.send_message( + embed=embed, ephemeral=True + ) else: await context.send(embed=embed, ephemeral=True) return @@ -130,10 +147,14 @@ def melonx_command(): description="The bot needs send messages permissions in this channel.", color=0xE02B2B, ) - embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png") - + embed.set_author( + name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png" + ) + if context.interaction: - await context.interaction.response.send_message(embed=embed, ephemeral=True) + await context.interaction.response.send_message( + embed=embed, ephemeral=True + ) else: await context.send(embed=embed, ephemeral=True) return @@ -141,15 +162,19 @@ def melonx_command(): embed = discord.Embed( title="MeloNX Commands", description="Choose a command from the dropdown below to get help with specific issues:", - color=0x963155 + color=0x963155, ) - embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png") - + embed.set_author( + name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png" + ) + view = MelonxView(self.bot) - + if context.interaction: - await context.interaction.response.send_message(embed=embed, view=view, ephemeral=True) + await context.interaction.response.send_message( + embed=embed, view=view, ephemeral=True + ) else: await context.send(embed=embed, view=view) - + return melonx diff --git a/cogs/melonx/mods.py b/cogs/melonx/mods.py index 24f9480..1c64509 100644 --- a/cogs/melonx/mods.py +++ b/cogs/melonx/mods.py @@ -13,41 +13,46 @@ def mods_command(): embed = discord.Embed( color=0x963155, description=( - '# How do I install mods within MeloNX? (Limited Support)\n\n---\n\n' + - '### **romFS/exeFS mods**:\n' + - '1. Obtain your title ID of your game by copying it from MeloNX, Hold down on the game and click game info.\n' + - '2. Copy it and then go to Files-> MeloNX-> mods-> contents\n' + - '3. In the contents folder create a new folder and name it the title ID you copied earlier.\n' + - '4. Now place all your mods for that game in the folder you just made (these should be folders with the mod name, do not mess with the file structure of the mod after unzipping it.)\n\n' + - '### **Atmosphere mods**: \n' + - '1. Obtain your title ID of your game by copying it from MeloNX, Hold down on the game and click game info.\n' + - '2. Copy it and then go to Files-> MeloNX-> sdcard-> atmosphere-> contents\n' + - '3. In the contents folder create a new folder and name it the title ID you copied earlier.\n' + - '4. Now place all your mods for that game in the folder you just made (these should be folders with the mod name, do not mess with the file structure of the mod after unzipping it.)' - ) + "# How do I install mods within MeloNX? (Limited Support)\n\n---\n\n" + + "### **romFS/exeFS mods**:\n" + + "1. Obtain your title ID of your game by copying it from MeloNX, Hold down on the game and click game info.\n" + + "2. Copy it and then go to Files-> MeloNX-> mods-> contents\n" + + "3. In the contents folder create a new folder and name it the title ID you copied earlier.\n" + + "4. Now place all your mods for that game in the folder you just made (these should be folders with the mod name, do not mess with the file structure of the mod after unzipping it.)\n\n" + + "### **Atmosphere mods**: \n" + + "1. Obtain your title ID of your game by copying it from MeloNX, Hold down on the game and click game info.\n" + + "2. Copy it and then go to Files-> MeloNX-> sdcard-> atmosphere-> contents\n" + + "3. In the contents folder create a new folder and name it the title ID you copied earlier.\n" + + "4. Now place all your mods for that game in the folder you just made (these should be folders with the mod name, do not mess with the file structure of the mod after unzipping it.)" + ), ) - embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png") - embed.set_footer(text=f'Last Edited by Meshal :D') + embed.set_author( + name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png" + ) + embed.set_footer(text=f"Last Edited by Meshal :D") embed.timestamp = discord.utils.utcnow() view = discord.ui.View() - view.add_item(discord.ui.Button( - label="Edit Command", - style=discord.ButtonStyle.secondary, - url="https://github.com/neoarz/Syntrel/blob/main/cogs/melonx/mods.py", - emoji="<:githubicon:1417717356846776340>" - )) - view.add_item(discord.ui.Button( - label="MeloNX Discord", - style=discord.ButtonStyle.primary, - url="https://discord.gg/EMXB2XYQgA", - emoji="<:Discord:1428762057758474280>" - )) - + view.add_item( + discord.ui.Button( + label="Edit Command", + style=discord.ButtonStyle.secondary, + url="https://github.com/neoarz/Syntrel/blob/main/cogs/melonx/mods.py", + emoji="<:githubicon:1417717356846776340>", + ) + ) + view.add_item( + discord.ui.Button( + label="MeloNX Discord", + style=discord.ButtonStyle.primary, + url="https://discord.gg/EMXB2XYQgA", + emoji="<:Discord:1428762057758474280>", + ) + ) + if context.interaction: await context.interaction.response.send_message(embed=embed, view=view) else: await context.send(embed=embed, view=view) - return mods diff --git a/cogs/melonx/requirements.py b/cogs/melonx/requirements.py index 3022bbe..9ae3780 100644 --- a/cogs/melonx/requirements.py +++ b/cogs/melonx/requirements.py @@ -13,36 +13,41 @@ def requirements_command(): embed = discord.Embed( color=0x963155, description=( - '# "What does MeloNX require?"\n\n---\n\n' + - '- JIT is **Mandatory**, because of this MeloNX will never be on the App Store / TestFlight\n' + - '- A Modded Nintendo Switch\n' + - '- The Increased Memory Limit Entitlement\n' + - '- A device with a **A12/M1** chip and **4GB Ram** or higher\n' + - '- TrollStore is supported with limited functionality for iOS 15' - ) + '# "What does MeloNX require?"\n\n---\n\n' + + "- JIT is **Mandatory**, because of this MeloNX will never be on the App Store / TestFlight\n" + + "- A Modded Nintendo Switch\n" + + "- The Increased Memory Limit Entitlement\n" + + "- A device with a **A12/M1** chip and **4GB Ram** or higher\n" + + "- TrollStore is supported with limited functionality for iOS 15" + ), ) - embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png") - embed.set_footer(text=f'Last Edited by Meshal :D') + embed.set_author( + name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png" + ) + embed.set_footer(text=f"Last Edited by Meshal :D") embed.timestamp = discord.utils.utcnow() view = discord.ui.View() - view.add_item(discord.ui.Button( - label="Edit Command", - style=discord.ButtonStyle.secondary, - url="https://github.com/neoarz/Syntrel/blob/main/cogs/melonx/requirements.py", - emoji="<:githubicon:1417717356846776340>" - )) - view.add_item(discord.ui.Button( - label="MeloNX Discord", - style=discord.ButtonStyle.primary, - url="https://discord.gg/EMXB2XYQgA", - emoji="<:Discord:1428762057758474280>" - )) + view.add_item( + discord.ui.Button( + label="Edit Command", + style=discord.ButtonStyle.secondary, + url="https://github.com/neoarz/Syntrel/blob/main/cogs/melonx/requirements.py", + emoji="<:githubicon:1417717356846776340>", + ) + ) + view.add_item( + discord.ui.Button( + label="MeloNX Discord", + style=discord.ButtonStyle.primary, + url="https://discord.gg/EMXB2XYQgA", + emoji="<:Discord:1428762057758474280>", + ) + ) if context.interaction: await context.interaction.response.send_message(embed=embed, view=view) else: await context.send(embed=embed, view=view) - return requirements diff --git a/cogs/melonx/transfer.py b/cogs/melonx/transfer.py index 044f2ed..a4cb545 100644 --- a/cogs/melonx/transfer.py +++ b/cogs/melonx/transfer.py @@ -7,52 +7,58 @@ import time def transfer_command(): @commands.hybrid_command( - name="transfer", description="How to transfer save files from other emulators or platforms" + name="transfer", + description="How to transfer save files from other emulators or platforms", ) async def transfer(self, context): embed = discord.Embed( color=0x963155, description=( - '# How do I transfer my save files from another emulator, my pc, or other platform?\n\n---\n\n' + - '### **Ryujinx Based**: \n' + - '1. Go too Ryujinx\'s main directory -> copy and and transfer the **"bis"** folder to your iDevice.\n' + - '2. Replace the **"bis"** folder in MeloNX with the one you transferred over.\n\n' + - '### **Yuzu Based**:\n' + - '1. Go to Yuzu\'s main directory and locate "**nand**" then go to -> users -> save\n' + - '2. Get the **title ID** of the game you want to transfer to by locating the game on MeloNX, hold down on it and press "Game Info" \n' + - '3. Then search the title ID within the **save** folder\n' + - '4. Open that folder labeled your title ID and copy of all of the contents to your iDevice\n' + - '5. Boot the game once in MeloNX so the save directories appear and is the latest created\n' + - '6. On your iDevice, go into files-> MeloNX -> bis -> user -> save\n' + - '7. In the save folder, there will be many folders named 0000001, 0000002, etc\n' + - '8. Sort by last modified or open each one too see the modified date/time of the files\n' + - '9. Once you\'ve found the newest one, inside will be two folders named 1 and 0, one will have a brand new save file inside.\n' + - '10. drop the contents copied from the title ID folder in Yuzu into that directory and press "replace" when prompted.\n' + - '11. Launch the game again in MeloNX to verify the transfer.' - ) + "# How do I transfer my save files from another emulator, my pc, or other platform?\n\n---\n\n" + + "### **Ryujinx Based**: \n" + + '1. Go too Ryujinx\'s main directory -> copy and and transfer the **"bis"** folder to your iDevice.\n' + + '2. Replace the **"bis"** folder in MeloNX with the one you transferred over.\n\n' + + "### **Yuzu Based**:\n" + + '1. Go to Yuzu\'s main directory and locate "**nand**" then go to -> users -> save\n' + + '2. Get the **title ID** of the game you want to transfer to by locating the game on MeloNX, hold down on it and press "Game Info" \n' + + "3. Then search the title ID within the **save** folder\n" + + "4. Open that folder labeled your title ID and copy of all of the contents to your iDevice\n" + + "5. Boot the game once in MeloNX so the save directories appear and is the latest created\n" + + "6. On your iDevice, go into files-> MeloNX -> bis -> user -> save\n" + + "7. In the save folder, there will be many folders named 0000001, 0000002, etc\n" + + "8. Sort by last modified or open each one too see the modified date/time of the files\n" + + "9. Once you've found the newest one, inside will be two folders named 1 and 0, one will have a brand new save file inside.\n" + + '10. drop the contents copied from the title ID folder in Yuzu into that directory and press "replace" when prompted.\n' + + "11. Launch the game again in MeloNX to verify the transfer." + ), ) - embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png") - embed.set_footer(text=f'Last Edited by Meshal :D') + embed.set_author( + name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png" + ) + embed.set_footer(text=f"Last Edited by Meshal :D") embed.timestamp = discord.utils.utcnow() view = discord.ui.View() - view.add_item(discord.ui.Button( - label="Edit Command", - style=discord.ButtonStyle.secondary, - url="https://github.com/neoarz/Syntrel/blob/main/cogs/melonx/transfer.py", - emoji="<:githubicon:1417717356846776340>" - )) - view.add_item(discord.ui.Button( - label="MeloNX Discord", - style=discord.ButtonStyle.primary, - url="https://discord.gg/EMXB2XYQgA", - emoji="<:Discord:1428762057758474280>" - )) + view.add_item( + discord.ui.Button( + label="Edit Command", + style=discord.ButtonStyle.secondary, + url="https://github.com/neoarz/Syntrel/blob/main/cogs/melonx/transfer.py", + emoji="<:githubicon:1417717356846776340>", + ) + ) + view.add_item( + discord.ui.Button( + label="MeloNX Discord", + style=discord.ButtonStyle.primary, + url="https://discord.gg/EMXB2XYQgA", + emoji="<:Discord:1428762057758474280>", + ) + ) if context.interaction: await context.interaction.response.send_message(embed=embed, view=view) else: await context.send(embed=embed, view=view) - return transfer diff --git a/cogs/miscellaneous/__init__.py b/cogs/miscellaneous/__init__.py index 780936e..b63d115 100644 --- a/cogs/miscellaneous/__init__.py +++ b/cogs/miscellaneous/__init__.py @@ -30,10 +30,16 @@ class Miscellaneous(commands.GroupCog, name="misc"): embed = discord.Embed( title="Miscellaneous Commands", description="Use `.misc ` or `/misc `.", - color=0x7289DA + color=0x7289DA, + ) + embed.set_author( + name="Miscellaneous", icon_url="https://yes.nighty.works/raw/YxMC0r.png" + ) + embed.add_field( + name="Available", + value="dontasktoask, rr, depart, labubu, duck, tryitandsee, piracy, keanu, support, docs, sigma, silly, color", + inline=False, ) - embed.set_author(name="Miscellaneous", icon_url="https://yes.nighty.works/raw/YxMC0r.png") - embed.add_field(name="Available", value="dontasktoask, rr, depart, labubu, duck, tryitandsee, piracy, keanu, support, docs, sigma, silly, color", inline=False) await context.send(embed=embed) async def _invoke_hybrid(self, context: Context, name: str): @@ -98,7 +104,9 @@ class Miscellaneous(commands.GroupCog, name="misc"): await self._invoke_hybrid(context, "sigma") @miscellaneous_group.command(name="silly") - async def miscellaneous_group_silly(self, context: Context, message_type: str = "regular"): + async def miscellaneous_group_silly( + self, context: Context, message_type: str = "regular" + ): await self._invoke_hybrid(context, "silly", message_type=message_type) @miscellaneous_group.command(name="color") @@ -107,119 +115,83 @@ class Miscellaneous(commands.GroupCog, name="misc"): @commands.check(_require_group_prefix) @commands.hybrid_command( - name="dontasktoask", - description="Shows the 'Don't Ask to Ask' image." + name="dontasktoask", description="Shows the 'Don't Ask to Ask' image." ) async def dontasktoask(self, context): return await dontasktoask_command()(self, context) @commands.check(_require_group_prefix) - @commands.hybrid_command( - name="rr", - description="Rickroll" - ) + @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="depart", - description="Show the departure meme" - ) + @commands.hybrid_command(name="depart", description="Show the departure meme") async def depart(self, context): return await depart_command()(self, context) @commands.check(_require_group_prefix) - @commands.hybrid_command( - name="labubu", - description="Labubu ASCII art" - ) + @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="duck", - description="Duck ASCII art" - ) + @commands.hybrid_command(name="duck", description="Duck ASCII art") async def duck(self, context): return await duck_command()(self, context) @commands.check(_require_group_prefix) - @commands.hybrid_command( - name="tryitandsee", - description="Try it and see" - ) + @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" - ) + @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" - ) + @commands.hybrid_command(name="keanu", description="Reeves") async def keanu(self, context): return await keanu_command()(self, context) @commands.check(_require_group_prefix) - @commands.hybrid_command( - name="support", - description="Support?" - ) + @commands.hybrid_command(name="support", description="Support?") async def support(self, context): return await support_command()(self, context) @commands.check(_require_group_prefix) - @commands.hybrid_command( - name="docs", - description="Shows the docs image." - ) + @commands.hybrid_command(name="docs", description="Shows the docs image.") async def docs(self, context): return await docs_command()(self, context) @commands.check(_require_group_prefix) - @commands.hybrid_command( - name="sigma", - description="i feel so sigma!" - ) + @commands.hybrid_command(name="sigma", description="i feel so sigma!") async def sigma(self, context): return await sigma_command()(self, context) @commands.check(_require_group_prefix) - @commands.hybrid_command( - name="silly", - description="Sends a silly message :3" + @commands.hybrid_command(name="silly", description="Sends a silly message :3") + @app_commands.describe(message_type="Type of message to send (regular or animated)") + @app_commands.choices( + message_type=[ + app_commands.Choice(name="Regular", value="regular"), + app_commands.Choice(name="Animated", value="animated"), + ] ) - @app_commands.describe( - message_type="Type of message to send (regular or animated)" - ) - @app_commands.choices(message_type=[ - app_commands.Choice(name="Regular", value="regular"), - app_commands.Choice(name="Animated", value="animated") - ]) async def silly(self, context, message_type: str = "regular"): return await silly_command()(self, context, message_type=message_type) @commands.check(_require_group_prefix) - @commands.hybrid_command( - name="color", - description="Get a random color." - ) + @commands.hybrid_command(name="color", description="Get a random color.") async def color(self, context): return await color_command()(self, context) + async def setup(bot) -> None: cog = Miscellaneous(bot) await bot.add_cog(cog) - + bot.logger.info("Loaded extension 'miscellaneous.dontasktoask'") bot.logger.info("Loaded extension 'miscellaneous.rr'") bot.logger.info("Loaded extension 'miscellaneous.depart'") @@ -232,4 +204,4 @@ async def setup(bot) -> None: bot.logger.info("Loaded extension 'miscellaneous.docs'") bot.logger.info("Loaded extension 'miscellaneous.sigma'") bot.logger.info("Loaded extension 'miscellaneous.silly'") - bot.logger.info("Loaded extension 'miscellaneous.color'") \ No newline at end of file + bot.logger.info("Loaded extension 'miscellaneous.color'") diff --git a/cogs/miscellaneous/color.py b/cogs/miscellaneous/color.py index 2840173..6cb1ed3 100644 --- a/cogs/miscellaneous/color.py +++ b/cogs/miscellaneous/color.py @@ -3,6 +3,7 @@ from discord.ext import commands import random import colorsys + def color_command(): @commands.hybrid_command(name="color", description="Get a random color.") async def color(self, context): @@ -13,25 +14,39 @@ def color_command(): color = discord.Color(random_color_int) r, g, b = color.r, color.g, color.b - + rgb_decimal = (r / 255, g / 255, b / 255) - + h, l, s = colorsys.rgb_to_hls(rgb_decimal[0], rgb_decimal[1], rgb_decimal[2]) - h_hsv, s_hsv, v_hsv = colorsys.rgb_to_hsv(rgb_decimal[0], rgb_decimal[1], rgb_decimal[2]) + h_hsv, s_hsv, v_hsv = colorsys.rgb_to_hsv( + rgb_decimal[0], rgb_decimal[1], rgb_decimal[2] + ) embed = discord.Embed(color=color) - embed.set_author(name="Random Color", icon_url="https://yes.nighty.works/raw/YxMC0r.png") - + embed.set_author( + name="Random Color", icon_url="https://yes.nighty.works/raw/YxMC0r.png" + ) + embed.add_field(name="Hex", value=str(color)) embed.add_field(name="RGB", value=f"rgb({r}, {g}, {b})") - embed.add_field(name="RGB Decimal", value=f"{rgb_decimal[0]:.3f}, {rgb_decimal[1]:.3f}, {rgb_decimal[2]:.3f}") - - embed.add_field(name="HSL", value=f"hsl({h*360:.0f}, {s*100:.0f}%, {l*100:.0f}%)") - embed.add_field(name="HSV", value=f"hsv({h_hsv*360:.0f}, {s_hsv*100:.0f}%, {v_hsv*100:.0f}%)") + embed.add_field( + name="RGB Decimal", + value=f"{rgb_decimal[0]:.3f}, {rgb_decimal[1]:.3f}, {rgb_decimal[2]:.3f}", + ) + + embed.add_field( + name="HSL", value=f"hsl({h * 360:.0f}, {s * 100:.0f}%, {l * 100:.0f}%)" + ) + embed.add_field( + name="HSV", + value=f"hsv({h_hsv * 360:.0f}, {s_hsv * 100:.0f}%, {v_hsv * 100:.0f}%)", + ) embed.add_field(name="Integer", value=str(random_color_int)) - embed.set_thumbnail(url=f"https://singlecolorimage.com/get/{str(color).replace('#', '')}/150x150") + embed.set_thumbnail( + url=f"https://singlecolorimage.com/get/{str(color).replace('#', '')}/150x150" + ) await context.send(embed=embed) - - return color \ No newline at end of file + + return color diff --git a/cogs/miscellaneous/depart.py b/cogs/miscellaneous/depart.py index 8b5dc91..a0d2b80 100644 --- a/cogs/miscellaneous/depart.py +++ b/cogs/miscellaneous/depart.py @@ -26,5 +26,5 @@ def depart_command(): await inter.followup.send(file=file, ephemeral=True) else: await context.send(file=file) - + return depart diff --git a/cogs/miscellaneous/docs.py b/cogs/miscellaneous/docs.py index 6fdc036..8322b48 100644 --- a/cogs/miscellaneous/docs.py +++ b/cogs/miscellaneous/docs.py @@ -4,14 +4,12 @@ from discord.ext.commands import Context import aiohttp import io + def docs_command(): - @commands.hybrid_command( - name="docs", - description="Shows the docs image." - ) + @commands.hybrid_command(name="docs", description="Shows the docs image.") async def docs(self, context): url = "https://yes.nighty.works/raw/akdx0q.webp" - + async with aiohttp.ClientSession() as session: async with session.get(url) as resp: data = await resp.read() @@ -20,10 +18,16 @@ def docs_command(): if getattr(context, "interaction", None): inter = context.interaction if not inter.response.is_done(): - await inter.response.send_message("## Dont be like the rest, read the docs", file=file, ephemeral=False) + await inter.response.send_message( + "## Dont be like the rest, read the docs", + file=file, + ephemeral=False, + ) else: - await inter.followup.send("## Dont be like the rest, read the docs", file=file, ephemeral=True) + await inter.followup.send( + "## Dont be like the rest, read the docs", file=file, ephemeral=True + ) else: await context.send("## Dont be like the rest, read the docs", file=file) - + return docs diff --git a/cogs/miscellaneous/dontasktoask.py b/cogs/miscellaneous/dontasktoask.py index 5d0aa7d..8f35ca0 100644 --- a/cogs/miscellaneous/dontasktoask.py +++ b/cogs/miscellaneous/dontasktoask.py @@ -4,14 +4,14 @@ from discord.ext.commands import Context import aiohttp import io + def dontasktoask_command(): @commands.hybrid_command( - name="dontasktoask", - description="Shows the 'Don't Ask to Ask' image." + name="dontasktoask", description="Shows the 'Don't Ask to Ask' image." ) async def dontasktoask(self, context): image_url = "https://yes.nighty.works/raw/KecbCr.jpg" - + async with aiohttp.ClientSession() as session: async with session.get(image_url) as resp: data = await resp.read() @@ -25,5 +25,5 @@ def dontasktoask_command(): await inter.followup.send(file=file, ephemeral=True) else: await context.send(file=file) - + return dontasktoask diff --git a/cogs/miscellaneous/duck.py b/cogs/miscellaneous/duck.py index 5f9b429..67c3822 100644 --- a/cogs/miscellaneous/duck.py +++ b/cogs/miscellaneous/duck.py @@ -27,10 +27,12 @@ def duck_command(): if getattr(context, "interaction", None): inter = context.interaction if not inter.response.is_done(): - await inter.response.send_message(f"```ansi\n{duck_art}\n```", ephemeral=False) + await inter.response.send_message( + f"```ansi\n{duck_art}\n```", ephemeral=False + ) else: await inter.followup.send(f"```ansi\n{duck_art}\n```", ephemeral=True) else: await context.send(f"```ansi\n{duck_art}\n```") - + return duck diff --git a/cogs/miscellaneous/keanu.py b/cogs/miscellaneous/keanu.py index 9384484..309de4a 100644 --- a/cogs/miscellaneous/keanu.py +++ b/cogs/miscellaneous/keanu.py @@ -28,14 +28,16 @@ def keanu_command(): "https://yes.nighty.works/raw/C7gy4v.jpg", "https://yes.nighty.works/raw/XqHg1q.jpg", "https://yes.nighty.works/raw/RUXNK7.png", - "https://yes.nighty.works/raw/CBNs9L.jpg" + "https://yes.nighty.works/raw/CBNs9L.jpg", ] - + embed = discord.Embed( description="## Reeves", color=0x7289DA, ) - embed.set_author(name="Keanu", icon_url="https://yes.nighty.works/raw/YxMC0r.png") + embed.set_author( + name="Keanu", icon_url="https://yes.nighty.works/raw/YxMC0r.png" + ) embed.set_image(url=random.choice(images)) if getattr(context, "interaction", None): inter = context.interaction @@ -45,5 +47,5 @@ def keanu_command(): await inter.followup.send(embed=embed, ephemeral=True) else: await context.send(embed=embed) - + return keanu diff --git a/cogs/miscellaneous/labubu.py b/cogs/miscellaneous/labubu.py index 4f2d9bc..ac9f8ab 100644 --- a/cogs/miscellaneous/labubu.py +++ b/cogs/miscellaneous/labubu.py @@ -49,9 +49,11 @@ def labubu_command(): description=f"```ansi\n{labubu_art}\n```", color=0x7289DA, ) - embed.set_author(name="Labubu", icon_url="https://yes.nighty.works/raw/YxMC0r.png") + embed.set_author( + name="Labubu", icon_url="https://yes.nighty.works/raw/YxMC0r.png" + ) embed.set_footer(text=f"May look broken on mobile") - + if getattr(context, "interaction", None): inter = context.interaction if not inter.response.is_done(): @@ -60,5 +62,5 @@ def labubu_command(): await inter.followup.send(embed=embed, ephemeral=True) else: await context.send(embed=embed) - + return labubu diff --git a/cogs/miscellaneous/piracy.py b/cogs/miscellaneous/piracy.py index 7cd30dd..f764a9b 100644 --- a/cogs/miscellaneous/piracy.py +++ b/cogs/miscellaneous/piracy.py @@ -26,5 +26,5 @@ def piracy_command(): await inter.followup.send(file=file, ephemeral=True) else: await context.send(file=file) - + return piracy diff --git a/cogs/miscellaneous/rickroll.py b/cogs/miscellaneous/rickroll.py index 6579f10..dd0503e 100644 --- a/cogs/miscellaneous/rickroll.py +++ b/cogs/miscellaneous/rickroll.py @@ -26,5 +26,5 @@ def rr_command(): await inter.followup.send(file=file, ephemeral=True) else: await context.send(file=file) - + return rr diff --git a/cogs/miscellaneous/sigma.py b/cogs/miscellaneous/sigma.py index 9103536..e989dd0 100644 --- a/cogs/miscellaneous/sigma.py +++ b/cogs/miscellaneous/sigma.py @@ -26,5 +26,5 @@ def sigma_command(): await inter.followup.send(file=file, ephemeral=True) else: await context.send(file=file) - + return sigma diff --git a/cogs/miscellaneous/silly.py b/cogs/miscellaneous/silly.py index eafd0ae..0770903 100644 --- a/cogs/miscellaneous/silly.py +++ b/cogs/miscellaneous/silly.py @@ -13,7 +13,7 @@ def silly_command(): message = "https://yes.nighty.works/raw/LX4nqt.gif" else: message = ":3" - + interaction = getattr(context, "interaction", None) if interaction is not None: await interaction.response.defer(ephemeral=True) @@ -28,5 +28,5 @@ def silly_command(): except: pass await context.channel.send(message) - - return silly \ No newline at end of file + + return silly diff --git a/cogs/miscellaneous/support.py b/cogs/miscellaneous/support.py index a171425..f1cb2eb 100644 --- a/cogs/miscellaneous/support.py +++ b/cogs/miscellaneous/support.py @@ -4,14 +4,12 @@ from discord.ext.commands import Context import aiohttp import io + def support_command(): - @commands.hybrid_command( - name="support", - description="Shows the support image." - ) + @commands.hybrid_command(name="support", description="Shows the support image.") async def support(self, context): url = "https://yes.nighty.works/raw/wGzHIV.gif" - + async with aiohttp.ClientSession() as session: async with session.get(url) as resp: data = await resp.read() @@ -25,5 +23,5 @@ def support_command(): await inter.followup.send(file=file, ephemeral=True) else: await context.send(file=file) - + return support diff --git a/cogs/miscellaneous/tryitandsee.py b/cogs/miscellaneous/tryitandsee.py index 5ca5bf9..2db4d96 100644 --- a/cogs/miscellaneous/tryitandsee.py +++ b/cogs/miscellaneous/tryitandsee.py @@ -10,7 +10,7 @@ def tryitandsee_command(): ) async def tryitandsee(self, context): link = "https://tryitands.ee/" - + if getattr(context, "interaction", None): inter = context.interaction if not inter.response.is_done(): @@ -19,5 +19,5 @@ def tryitandsee_command(): await inter.followup.send(link, ephemeral=True) else: await context.send(link) - + return tryitandsee diff --git a/cogs/moderation/__init__.py b/cogs/moderation/__init__.py index 350a737..4ce98d5 100644 --- a/cogs/moderation/__init__.py +++ b/cogs/moderation/__init__.py @@ -25,9 +25,13 @@ class Moderation(commands.GroupCog, name="moderation"): embed = discord.Embed( title="Moderation Commands", description="Use `.moderation ` or `/moderation `.", - color=0x7289DA + color=0x7289DA, + ) + embed.add_field( + name="Available", + value="ban, kick, purge, warnings, archive, hackban, nick, timeout", + inline=False, ) - embed.add_field(name="Available", value="ban, kick, purge, warnings, archive, hackban, nick, timeout", inline=False) await context.send(embed=embed) async def _invoke_hybrid(self, context: Context, name: str, **kwargs): @@ -68,15 +72,28 @@ class Moderation(commands.GroupCog, name="moderation"): return results[:25] @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) + 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"): + 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, user: discord.Member = None): + async def moderation_group_purge( + self, context: Context, amount: int, user: discord.Member = None + ): await self._invoke_hybrid(context, "purge", amount=amount, user=user) @moderation_group.command(name="warnings") @@ -88,45 +105,57 @@ class Moderation(commands.GroupCog, name="moderation"): await self._invoke_hybrid(context, "archive", limit=limit) @moderation_group.command(name="hackban") - async def moderation_group_hackban(self, context: Context, user_id: str, *, reason: str = "Not specified"): + async def moderation_group_hackban( + self, context: Context, user_id: str, *, 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): + async def moderation_group_nick( + self, context: Context, user: discord.User, *, nickname: str = None + ): await self._invoke_hybrid(context, "nick", user=user, nickname=nickname) @moderation_group.command(name="timeout") - async def moderation_group_timeout(self, context: Context, user: discord.User, duration: str, *, reason: str = "Not specified"): - await self._invoke_hybrid(context, "timeout", user=user, duration=duration, reason=reason) + async def moderation_group_timeout( + self, + context: Context, + user: discord.User, + duration: str, + *, + reason: str = "Not specified", + ): + await self._invoke_hybrid( + context, "timeout", user=user, duration=duration, reason=reason + ) @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.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." - ) + @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." - ) + @commands.hybrid_command(name="purge", description="Delete a number of messages.") async def purge(self, context, amount: int, user: discord.Member = None): return await purge_command()(self, context, amount=amount, user=user) @commands.check(_require_group_prefix) @commands.hybrid_command( - name="warnings", - description="Manage warnings of a user on a server." + name="warnings", description="Manage warnings of a user on a server." ) async def warnings(self, context): return await warnings_command()(self, context) @@ -134,7 +163,7 @@ class Moderation(commands.GroupCog, name="moderation"): @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." + 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) @@ -142,23 +171,21 @@ class Moderation(commands.GroupCog, name="moderation"): @commands.check(_require_group_prefix) @commands.hybrid_command( name="hackban", - description="Bans a user without the user having to be in the server." + description="Bans a user without the user having to be in the server.", ) async def hackban(self, context, user_id: str, *, 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." + 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) @commands.check(_require_group_prefix) @commands.hybrid_command( - name="timeout", - description="Timeout a user for a specified duration." + name="timeout", description="Timeout a user for a specified duration." ) @app_commands.describe( user="The user that should be timed out.", @@ -166,13 +193,23 @@ class Moderation(commands.GroupCog, name="moderation"): reason="The reason why the user should be timed out.", ) @app_commands.autocomplete(duration=timeout_duration_autocomplete) - async def timeout(self, context, user: discord.User, duration: str, *, reason: str = "Not specified"): - return await timeout_command()(self, context, user=user, duration=duration, reason=reason) + async def timeout( + self, + context, + user: discord.User, + duration: str, + *, + reason: str = "Not specified", + ): + return await timeout_command()( + self, context, user=user, duration=duration, reason=reason + ) + 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'") diff --git a/cogs/moderation/archive.py b/cogs/moderation/archive.py index 3c0617d..7f3edd0 100644 --- a/cogs/moderation/archive.py +++ b/cogs/moderation/archive.py @@ -23,7 +23,7 @@ def archive_command(): :param limit: The limit of messages that should be archived. Default is 10. """ log_file = f"{context.channel.id}.log" - + messages = [] async for message in context.channel.history( limit=limit, before=context.message @@ -36,10 +36,10 @@ def archive_command(): if len(attachments) >= 1 else "" ) - + message_line = f"{message.created_at.strftime('%m/%d/%Y %H:%M:%S')} {message.author} {message.id}: {message.clean_content} {attachments_text}\n" messages.append(message_line) - + with open(log_file, "w", encoding="UTF-8") as f: f.write( f'Archived messages from: #{context.channel} ({context.channel.id}) in the guild "{context.guild}" ({context.guild.id}) at {datetime.now().strftime("%m/%d/%Y %H:%M:%S")}\n' @@ -47,9 +47,9 @@ def archive_command(): for message_line in reversed(messages): f.write(message_line) - + f = discord.File(log_file) await context.send(file=f) os.remove(log_file) - - return archive \ No newline at end of file + + return archive diff --git a/cogs/moderation/ban.py b/cogs/moderation/ban.py index d5ef222..bc68f49 100644 --- a/cogs/moderation/ban.py +++ b/cogs/moderation/ban.py @@ -14,17 +14,24 @@ def ban_command(): reason="The reason why the user should be banned.", delete_messages="Delete messages from the user (choose time period).", ) - @app_commands.choices(delete_messages=[ - app_commands.Choice(name="Don't delete any messages", value="none"), - app_commands.Choice(name="Last 1 hour", value="1h"), - app_commands.Choice(name="Last 6 hours", value="6h"), - app_commands.Choice(name="Last 12 hours", value="12h"), - app_commands.Choice(name="Last 24 hours", value="1d"), - app_commands.Choice(name="Last 3 days", value="3d"), - app_commands.Choice(name="Last 7 days", value="7d"), - ]) + @app_commands.choices( + delete_messages=[ + app_commands.Choice(name="Don't delete any messages", value="none"), + app_commands.Choice(name="Last 1 hour", value="1h"), + app_commands.Choice(name="Last 6 hours", value="6h"), + app_commands.Choice(name="Last 12 hours", value="12h"), + app_commands.Choice(name="Last 24 hours", value="1d"), + app_commands.Choice(name="Last 3 days", value="3d"), + app_commands.Choice(name="Last 7 days", value="7d"), + ] + ) async def ban( - self, context, user: discord.User, *, reason: str = "Not specified", delete_messages: str = "none" + self, + context, + user: discord.User, + *, + reason: str = "Not specified", + delete_messages: str = "none", ): try: member = context.guild.get_member(user.id) @@ -38,7 +45,10 @@ def ban_command(): title="Ban", description=f"**{user}** was banned by **{context.author}**!", color=0x7289DA, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) embed.add_field(name="Reason:", value=reason) await context.send(embed=embed) return @@ -47,7 +57,10 @@ def ban_command(): title="Error!", description="I don't have permission to ban this user.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) return except Exception: @@ -55,41 +68,56 @@ def ban_command(): title="Error!", description="An error occurred while trying to ban the user.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) return - - if not context.author.guild_permissions.ban_members and context.author != context.guild.owner: + + if ( + not context.author.guild_permissions.ban_members + and context.author != context.guild.owner + ): embed = discord.Embed( title="Missing Permissions!", description="You don't have the `Ban Members` permission to use this command.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) return - + if member and member.top_role >= context.guild.me.top_role: embed = discord.Embed( title="Cannot Ban User", description="This user has a higher or equal role to me. Make sure my role is above theirs.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) return - + if member and context.author != context.guild.owner: if member.top_role >= context.author.top_role: embed = discord.Embed( - title="Cannot Ban User", + title="Cannot Ban User", description="You cannot ban this user as they have a higher or equal role to you.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) return - + delete_message_days = 0 delete_all_messages = False - + if delete_messages != "none": if delete_messages == "all": delete_all_messages = True @@ -100,7 +128,7 @@ def ban_command(): elif delete_messages.endswith("d"): days = int(delete_messages[:-1]) delete_message_days = min(days, 7) - + try: if member: try: @@ -108,41 +136,60 @@ def ban_command(): title="Ban", description=f"You were banned by **{context.author}** from **{context.guild.name}**!\nReason: {reason}", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await member.send(embed=dm_embed) except (discord.Forbidden, discord.HTTPException): pass - + if member: - await member.ban(reason=reason, delete_message_days=delete_message_days) + await member.ban( + reason=reason, delete_message_days=delete_message_days + ) else: - await context.guild.ban(user, reason=reason, delete_message_days=delete_message_days) - + await context.guild.ban( + user, reason=reason, delete_message_days=delete_message_days + ) + if delete_all_messages: await self.delete_all_user_messages(context.guild, user.id) - + embed = discord.Embed( title="Ban", description=f"**{user}** was banned by **{context.author}**!", color=0x7289DA, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) embed.add_field(name="Reason:", value=reason) - + if delete_messages != "none": if delete_all_messages: - embed.add_field(name="Messages Deleted:", value="All messages", inline=False) + embed.add_field( + name="Messages Deleted:", value="All messages", inline=False + ) else: delete_time_text = self.format_delete_time(delete_messages) - embed.add_field(name="Messages Deleted:", value=delete_time_text, inline=False) - + embed.add_field( + name="Messages Deleted:", + value=delete_time_text, + inline=False, + ) + await context.send(embed=embed) - + except discord.Forbidden: embed = discord.Embed( title="Error!", description="I don't have permission to ban this user. Make sure my role is above theirs.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) except discord.HTTPException as e: if "Cannot ban the owner of a guild" in str(e): @@ -150,37 +197,52 @@ def ban_command(): title="Cannot Ban User", description="You cannot ban the server owner.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) else: embed = discord.Embed( title="Error!", description=f"Discord API error: {str(e)}", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) except Exception as e: embed = discord.Embed( title="Debug Error!", description=f"Error type: {type(e).__name__}\nError message: {str(e)}", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) - + except Exception as e: embed = discord.Embed( title="Error!", description="An unexpected error occurred.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png" + ) await context.send(embed=embed, ephemeral=True) - async def delete_all_user_messages(self, guild: discord.Guild, user_id: int) -> None: + async def delete_all_user_messages( + self, guild: discord.Guild, user_id: int + ) -> None: for channel in guild.text_channels: try: permissions = channel.permissions_for(guild.me) - if not (permissions.read_message_history and permissions.manage_messages): + if not ( + permissions.read_message_history and permissions.manage_messages + ): continue - + deleted = True while deleted: deleted = False @@ -190,23 +252,27 @@ def ban_command(): try: await message.delete() deleted = True - except (discord.NotFound, discord.Forbidden, discord.HTTPException): + except ( + discord.NotFound, + discord.Forbidden, + discord.HTTPException, + ): continue except (discord.Forbidden, discord.HTTPException): break - + except (discord.Forbidden, discord.HTTPException): continue def format_delete_time(self, delete_option: str) -> str: time_formats = { "1h": "Last 1 hour", - "6h": "Last 6 hours", + "6h": "Last 6 hours", "12h": "Last 12 hours", "1d": "Last 24 hours", "3d": "Last 3 days", - "7d": "Last 7 days" + "7d": "Last 7 days", } return time_formats.get(delete_option, "Unknown time period") - - return ban \ No newline at end of file + + return ban diff --git a/cogs/moderation/hackban.py b/cogs/moderation/hackban.py index 009e260..e849073 100644 --- a/cogs/moderation/hackban.py +++ b/cogs/moderation/hackban.py @@ -15,9 +15,7 @@ def hackban_command(): user_id="The user ID that should be banned.", reason="The reason why the user should be banned.", ) - async def hackban( - self, context, user_id: str, *, reason: str = "Not specified" - ): + async def hackban(self, context, user_id: str, *, reason: str = "Not specified"): """ Bans a user without the user having to be in the server. @@ -34,7 +32,9 @@ def hackban_command(): title="Ban", description=f"**{user}** (ID: {user_id}) was banned by **{context.author}**!", color=0x7289DA, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png" + ) embed.add_field(name="Reason:", value=reason) await context.send(embed=embed) except Exception: @@ -42,7 +42,9 @@ def hackban_command(): title="Error!", description="An error occurred while trying to ban the user. Make sure ID is an existing ID that belongs to a user.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png" + ) await context.send(embed=embed) - + return hackban diff --git a/cogs/moderation/kick.py b/cogs/moderation/kick.py index 6c91ed8..cd08cdb 100644 --- a/cogs/moderation/kick.py +++ b/cogs/moderation/kick.py @@ -13,9 +13,7 @@ def kick_command(): user="The user that should be kicked.", reason="The reason why the user should be kicked.", ) - async def kick( - self, context, user: discord.User, *, reason: str = "Not specified" - ): + async def kick(self, context, user: discord.User, *, reason: str = "Not specified"): try: member = context.guild.get_member(user.id) if not member: @@ -26,38 +24,53 @@ def kick_command(): title="Error!", description="This user is not in the server.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) return - - if not context.author.guild_permissions.kick_members and context.author != context.guild.owner: + + if ( + not context.author.guild_permissions.kick_members + and context.author != context.guild.owner + ): embed = discord.Embed( title="Missing Permissions!", description="You don't have the `Kick Members` permission to use this command.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) return - + if member and member.top_role >= context.guild.me.top_role: embed = discord.Embed( title="Cannot Kick User", description="This user has a higher or equal role to me. Make sure my role is above theirs.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) return - + if member and context.author != context.guild.owner: if member.top_role >= context.author.top_role: embed = discord.Embed( - title="Cannot Kick User", + title="Cannot Kick User", description="You cannot kick this user as they have a higher or equal role to you.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) return - + try: if member: try: @@ -66,29 +79,37 @@ def kick_command(): title="Kick", description=f"You were kicked by **{context.author}** from **{context.guild.name}**!\nReason: {reason}", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") - + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) ) except (discord.Forbidden, discord.HTTPException): pass - + await member.kick(reason=reason) - + embed = discord.Embed( title="Kick", description=f"**{user}** was kicked by **{context.author}**!", color=0x7289DA, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) embed.add_field(name="Reason:", value=reason) - + await context.send(embed=embed) - + except discord.Forbidden: embed = discord.Embed( title="Error!", description="I don't have permission to kick this user. Make sure my role is above theirs.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) except discord.HTTPException as e: if "Cannot kick the owner of a guild" in str(e): @@ -96,28 +117,39 @@ def kick_command(): title="Cannot Kick User", description="You cannot kick the server owner.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) else: embed = discord.Embed( title="Error!", description=f"Discord API error: {str(e)}", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) except Exception as e: embed = discord.Embed( title="Debug Error!", description=f"Error type: {type(e).__name__}\nError message: {str(e)}", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) - + except Exception as e: embed = discord.Embed( title="Error!", description="An unexpected error occurred.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png" + ) await context.send(embed=embed, ephemeral=True) - + return kick diff --git a/cogs/moderation/nick.py b/cogs/moderation/nick.py index 489a75d..ff9b268 100644 --- a/cogs/moderation/nick.py +++ b/cogs/moderation/nick.py @@ -13,9 +13,7 @@ def nick_command(): user="The user that should have a new nickname.", nickname="The new nickname that should be set.", ) - async def nick( - self, context, user: discord.User, *, nickname: str = None - ): + async def nick(self, context, user: discord.User, *, nickname: str = None): """ Change the nickname of a user on a server. @@ -28,15 +26,19 @@ def nick_command(): title="Missing Permissions!", description="You are missing the permission(s) `manage_nicknames` to execute this command!", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png" + ) return await context.send(embed=embed, ephemeral=True) - + if not context.guild.me.guild_permissions.manage_nicknames: embed = discord.Embed( title="Missing Permissions!", description="I am missing the permission(s) `manage_nicknames` to execute this command!", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png" + ) return await context.send(embed=embed, ephemeral=True) member = context.guild.get_member(user.id) or await context.guild.fetch_member( @@ -48,14 +50,18 @@ def nick_command(): title="Nickname", description=f"**{member}'s** new nickname is **{nickname}**!", color=0x7289DA, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png" + ) await context.send(embed=embed) except: embed = discord.Embed( title="Missing Permissions!", description="An error occurred while trying to change the nickname of the user. Make sure my role is above the role of the user you want to change the nickname.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png" + ) await context.send(embed=embed, ephemeral=True) - + return nick diff --git a/cogs/moderation/purge.py b/cogs/moderation/purge.py index ed5b9a5..dd9a9bc 100644 --- a/cogs/moderation/purge.py +++ b/cogs/moderation/purge.py @@ -3,6 +3,7 @@ from discord import app_commands from discord.ext import commands from discord.ext.commands import Context + def purge_command(): @commands.hybrid_command( name="purge", @@ -12,36 +13,39 @@ def purge_command(): @commands.bot_has_permissions(manage_messages=True) @app_commands.describe( amount="The amount of messages that should be deleted.", - user="The user whose messages should be deleted (optional)." + user="The user whose messages should be deleted (optional).", ) async def purge(self, context, amount: int, user: discord.Member = None): if context.interaction: await context.defer(ephemeral=True) - + if user: deleted_count = 0 - + def check(message): nonlocal deleted_count if message.author == user and deleted_count < amount: deleted_count += 1 return True return False - + purged_messages = await context.channel.purge(limit=300, check=check) else: purged_messages = await context.channel.purge(limit=amount) - + embed = discord.Embed( title="Purge", - description=f"**{context.author}** cleared **{len(purged_messages)}** messages!" + (f" from **{user}**" if user else ""), + description=f"**{context.author}** cleared **{len(purged_messages)}** messages!" + + (f" from **{user}**" if user else ""), color=0x7289DA, ) - embed.set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") - + embed.set_author( + name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png" + ) + if context.interaction: await context.send(embed=embed, ephemeral=True) else: await context.send(embed=embed, delete_after=10) - - return purge \ No newline at end of file + + return purge diff --git a/cogs/moderation/timeout.py b/cogs/moderation/timeout.py index 69fb37b..b5eb127 100644 --- a/cogs/moderation/timeout.py +++ b/cogs/moderation/timeout.py @@ -13,6 +13,7 @@ def timeout_command(): app_commands.Choice(name="1 day", value="1d"), app_commands.Choice(name="1 week", value="1w"), ] + @commands.hybrid_command( name="timeout", description="Timeout a user for a specified duration.", @@ -24,7 +25,12 @@ def timeout_command(): ) @app_commands.choices(duration=DURATION_CHOICES) async def timeout( - self, context, user: discord.User, duration: str, *, reason: str = "Not specified" + self, + context, + user: discord.User, + duration: str, + *, + reason: str = "Not specified", ): try: member = context.guild.get_member(user.id) @@ -36,16 +42,25 @@ def timeout_command(): title="Error!", description="This user is not in the server.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) return - if not context.author.guild_permissions.moderate_members and context.author != context.guild.owner: + if ( + not context.author.guild_permissions.moderate_members + and context.author != context.guild.owner + ): embed = discord.Embed( title="Missing Permissions!", description="You don't have the `Timeout Members` permission to use this command.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) return @@ -54,7 +69,10 @@ def timeout_command(): title="Cannot Timeout User", description="This user has a higher or equal role to me. Make sure my role is above theirs.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) return @@ -64,7 +82,10 @@ def timeout_command(): title="Cannot Timeout User", description="You cannot timeout this user as they have a higher or equal role to you.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) return @@ -74,7 +95,10 @@ def timeout_command(): title="Invalid Duration", description="Choose one of: 60 secs, 5 mins, 10 mins, 1 hour, 1 day, 1 week.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) return @@ -89,9 +113,14 @@ def timeout_command(): title="Timeout", description=f"**{user}** was timed out by **{context.author}**!", color=0x7289DA, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) embed.add_field(name="Reason:", value=reason) - embed.add_field(name="Duration:", value=_format_duration(duration), inline=False) + embed.add_field( + name="Duration:", value=_format_duration(duration), inline=False + ) await context.send(embed=embed) except discord.Forbidden: @@ -99,21 +128,30 @@ def timeout_command(): title="Error!", description="I don't have permission to timeout this user. Make sure my role is above theirs.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) except discord.HTTPException as e: embed = discord.Embed( title="Error!", description=f"Discord API error: {str(e)}", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) except Exception as e: embed = discord.Embed( title="Debug Error!", description=f"Error type: {type(e).__name__}\nError message: {str(e)}", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", + icon_url="https://yes.nighty.works/raw/CPKHQd.png", + ) await context.send(embed=embed, ephemeral=True) except Exception: @@ -121,13 +159,19 @@ def timeout_command(): title="Error!", description="An unexpected error occurred.", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png" + ) await context.send(embed=embed, ephemeral=True) @timeout.autocomplete("duration") async def duration_autocomplete(interaction: discord.Interaction, current: str): query = (current or "").lower() - filtered = [c for c in DURATION_CHOICES if query in c.name.lower() or query in c.value.lower()] + filtered = [ + c + for c in DURATION_CHOICES + if query in c.name.lower() or query in c.value.lower() + ] return filtered[:25] return timeout @@ -172,5 +216,3 @@ def _format_duration(duration: str) -> str: return f"{value} seconds" except Exception: return duration - - diff --git a/cogs/moderation/warnings.py b/cogs/moderation/warnings.py index c803185..1657a19 100644 --- a/cogs/moderation/warnings.py +++ b/cogs/moderation/warnings.py @@ -5,13 +5,17 @@ from discord.ext.commands import Context def warnings_command(): - async def send_embed(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(): await interaction.followup.send(embed=embed, ephemeral=ephemeral) else: - await interaction.response.send_message(embed=embed, ephemeral=ephemeral) + await interaction.response.send_message( + embed=embed, ephemeral=ephemeral + ) else: await context.send(embed=embed) @@ -30,16 +34,20 @@ def warnings_command(): title="Missing Permissions!", description="You are missing the permission(s) `manage_messages` to execute this command!", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png" + ) return await self.send_embed(context, embed, ephemeral=True) - + if context.invoked_subcommand is None: embed = discord.Embed( title="Warning", description="Please specify a subcommand.\n\n**Subcommands:**\n`add` - Add a warning to a user.\n`remove` - Remove a warning from a user.\n`list` - List all warnings of a user.", color=0x7289DA, ) - embed.set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + embed.set_author( + name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png" + ) await self.send_embed(context, embed) @warning.command( @@ -65,7 +73,9 @@ def warnings_command(): title="Missing Permissions!", description="You are missing the permission(s) `manage_messages` to execute this command!", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png" + ) return await self.send_embed(context, embed, ephemeral=True) member = context.guild.get_member(user.id) or await context.guild.fetch_member( user.id @@ -78,7 +88,9 @@ def warnings_command(): description=f"**{member}** was warned by **{context.author}**!\nTotal warns for this user: {total}", color=0x7289DA, ) - embed.set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + embed.set_author( + name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png" + ) embed.add_field(name="Reason:", value=reason) await self.send_embed(context, embed) try: @@ -87,7 +99,9 @@ def warnings_command(): description=f"You were warned by **{context.author}** in **{context.guild.name}**!\nReason: {reason}", color=0xE02B2B, ) - dm_embed.set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + dm_embed.set_author( + name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png" + ) await member.send(embed=dm_embed) except: fallback = discord.Embed( @@ -120,7 +134,9 @@ def warnings_command(): title="Missing Permissions!", description="You are missing the permission(s) `manage_messages` to execute this command!", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png" + ) return await self.send_embed(context, embed, ephemeral=True) member = context.guild.get_member(user.id) or await context.guild.fetch_member( user.id @@ -131,7 +147,9 @@ def warnings_command(): description=f"Removed the warning **#{warn_id}** from **{member}**!\nTotal warns for this user: {total}", color=0x7289DA, ) - embed.set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + embed.set_author( + name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png" + ) await self.send_embed(context, embed) @warning.command( @@ -151,11 +169,15 @@ def warnings_command(): title="Missing Permissions!", description="You are missing the permission(s) `manage_messages` to execute this command!", color=0xE02B2B, - ).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + ).set_author( + name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png" + ) return await self.send_embed(context, embed, ephemeral=True) warnings_list = await self.bot.database.get_warnings(user.id, context.guild.id) embed = discord.Embed(title=f"Warnings of {user}", color=0x7289DA) - embed.set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png") + embed.set_author( + name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png" + ) description = "" if len(warnings_list) == 0: description = "This user has no warnings." @@ -165,6 +187,4 @@ def warnings_command(): embed.description = description await self.send_embed(context, embed) - - return warning diff --git a/cogs/owner/cog_management.py b/cogs/owner/cog_management.py index e78d8e1..2ef729e 100644 --- a/cogs/owner/cog_management.py +++ b/cogs/owner/cog_management.py @@ -8,13 +8,17 @@ class CogManagement(commands.Cog, name="cog_management"): def __init__(self, bot) -> None: self.bot = bot - async def send_embed(self, context: Context, embed: discord.Embed, *, ephemeral: bool = False) -> None: + async def send_embed( + self, context: Context, embed: discord.Embed, *, ephemeral: bool = False + ) -> None: interaction = getattr(context, "interaction", None) if interaction is not None: if interaction.response.is_done(): await interaction.followup.send(embed=embed, ephemeral=ephemeral) else: - await interaction.response.send_message(embed=embed, ephemeral=ephemeral) + await interaction.response.send_message( + embed=embed, ephemeral=ephemeral + ) else: await context.send(embed=embed) @@ -38,15 +42,20 @@ class CogManagement(commands.Cog, name="cog_management"): except Exception as e: embed = discord.Embed( title="Error", - description=f"Could not load the `{cog}` cog.\n```{str(e)}```", color=0xE02B2B + description=f"Could not load the `{cog}` cog.\n```{str(e)}```", + color=0xE02B2B, + ) + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") await self.send_embed(context, embed, ephemeral=True) return embed = discord.Embed( description=f"Successfully loaded the `{cog}` cog.", color=0x7289DA ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" + ) await self.send_embed(context, embed) @commands.hybrid_command( @@ -69,15 +78,20 @@ class CogManagement(commands.Cog, name="cog_management"): except Exception as e: embed = discord.Embed( title="Error", - description=f"Could not unload the `{cog}` cog.\n```{str(e)}```", color=0xE02B2B + description=f"Could not unload the `{cog}` cog.\n```{str(e)}```", + color=0xE02B2B, + ) + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") await self.send_embed(context, embed, ephemeral=True) return embed = discord.Embed( description=f"Successfully unloaded the `{cog}` cog.", color=0x7289DA ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" + ) await self.send_embed(context, embed) @commands.hybrid_command( @@ -100,16 +114,22 @@ class CogManagement(commands.Cog, name="cog_management"): except Exception as e: embed = discord.Embed( title="Error", - description=f"Could not reload the `{cog}` cog.\n```{str(e)}```", color=0xE02B2B + description=f"Could not reload the `{cog}` cog.\n```{str(e)}```", + color=0xE02B2B, + ) + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") await self.send_embed(context, embed, ephemeral=True) return embed = discord.Embed( title="Cog Management", - description=f"Successfully reloaded the `{cog}` cog.", color=0x7289DA + description=f"Successfully reloaded the `{cog}` cog.", + color=0x7289DA, + ) + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") await self.send_embed(context, embed) async def cog_command_error(self, context: Context, error) -> None: @@ -117,9 +137,11 @@ class CogManagement(commands.Cog, name="cog_management"): embed = discord.Embed( title="Permission Denied", description="You are not the owner of the bot!", - color=0xE02B2B + color=0xE02B2B, + ) + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") await self.send_embed(context, embed, ephemeral=True) else: raise error diff --git a/cogs/owner/invite.py b/cogs/owner/invite.py index e8b8a52..dafa19e 100644 --- a/cogs/owner/invite.py +++ b/cogs/owner/invite.py @@ -10,13 +10,17 @@ class Invite(commands.Cog, name="invite"): def __init__(self, bot) -> None: self.bot = bot - async def send_embed(self, context: Context, embed: discord.Embed, *, ephemeral: bool = False) -> None: + async def send_embed( + self, context: Context, embed: discord.Embed, *, ephemeral: bool = False + ) -> None: interaction = getattr(context, "interaction", None) if interaction is not None: if interaction.response.is_done(): await interaction.followup.send(embed=embed, ephemeral=ephemeral) else: - await interaction.response.send_message(embed=embed, ephemeral=ephemeral) + await interaction.response.send_message( + embed=embed, ephemeral=ephemeral + ) else: await context.send(embed=embed) @@ -38,9 +42,15 @@ class Invite(commands.Cog, name="invite"): await context.send("Bot is not ready. Try again shortly.") return invite_link = os.getenv("INVITE_LINK") - embed = discord.Embed(title="Install", description=f"Install me by clicking [here]({invite_link}).", color=0x7289DA) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") - + embed = discord.Embed( + title="Install", + description=f"Install me by clicking [here]({invite_link}).", + color=0x7289DA, + ) + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" + ) + await self.send_embed(context, embed, ephemeral=False) async def cog_command_error(self, context: Context, error) -> None: @@ -48,9 +58,11 @@ class Invite(commands.Cog, name="invite"): embed = discord.Embed( title="Permission Denied", description="You are not the owner of this bot.", - color=0xE02B2B + color=0xE02B2B, + ) + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") await self.send_embed(context, embed, ephemeral=True) else: raise error diff --git a/cogs/owner/logs.py b/cogs/owner/logs.py index 1c85e93..6758b20 100644 --- a/cogs/owner/logs.py +++ b/cogs/owner/logs.py @@ -10,18 +10,24 @@ from utils.checks import is_owner_or_friend class Logs(commands.Cog, name="logs"): def __init__(self, bot) -> None: self.bot = bot - - async def send_embed(self, context: Context, embed: discord.Embed, *, ephemeral: bool = False) -> None: + + async def send_embed( + self, context: Context, embed: discord.Embed, *, ephemeral: bool = False + ) -> None: interaction = getattr(context, "interaction", None) if interaction is not None: if interaction.response.is_done(): await interaction.followup.send(embed=embed, ephemeral=ephemeral) else: - await interaction.response.send_message(embed=embed, ephemeral=ephemeral) + await interaction.response.send_message( + embed=embed, ephemeral=ephemeral + ) else: await context.send(embed=embed) - async def send_file(self, context: Context, *, file: discord.File, ephemeral: bool = False) -> None: + async def send_file( + self, context: Context, *, file: discord.File, ephemeral: bool = False + ) -> None: interaction = getattr(context, "interaction", None) if interaction is not None: if interaction.response.is_done(): @@ -35,7 +41,9 @@ class Logs(commands.Cog, name="logs"): name="logs", description="View the bot's log file", ) - @app_commands.describe(lines="Number of lines to read from the end of the log file (default: 50, max: 200)") + @app_commands.describe( + lines="Number of lines to read from the end of the log file (default: 50, max: 200)" + ) @app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True) @app_commands.allowed_installs(guilds=True, users=True) @is_owner_or_friend() @@ -44,12 +52,15 @@ class Logs(commands.Cog, name="logs"): lines = 200 elif lines < 1: lines = 1 - + log_file_path = os.getenv("LOG_FILE", "logs/discord.log") - + if not os.path.isabs(log_file_path): - log_file_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), log_file_path) - + log_file_path = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))), + log_file_path, + ) + try: if not os.path.exists(log_file_path): embed = discord.Embed( @@ -57,47 +68,57 @@ class Logs(commands.Cog, name="logs"): description=f"Log file not found at: `{log_file_path}`", color=0xE02B2B, ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" + ) await self.send_embed(context, embed, ephemeral=True) return - - with open(log_file_path, 'r', encoding='utf-8', errors='replace') as f: + + with open(log_file_path, "r", encoding="utf-8", errors="replace") as f: all_lines = f.readlines() - + if not all_lines: embed = discord.Embed( title="Logs", description="Log file is empty.", color=0x7289DA, ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" + ) await self.send_embed(context, embed, ephemeral=True) return - + selected_lines = all_lines[-lines:] if len(all_lines) > lines else all_lines - log_content = ''.join(selected_lines) - + log_content = "".join(selected_lines) + log_file = f"logs.txt" with open(log_file, "w", encoding="utf-8") as f: - f.write(f"Bot logs extracted at {discord.utils.utcnow().strftime('%Y-%m-%d %H:%M:%S')} UTC\n") - f.write(f"Last {len(selected_lines)} lines from {os.path.basename(log_file_path)}\n") + f.write( + f"Bot logs extracted at {discord.utils.utcnow().strftime('%Y-%m-%d %H:%M:%S')} UTC\n" + ) + f.write( + f"Last {len(selected_lines)} lines from {os.path.basename(log_file_path)}\n" + ) f.write(f"Total lines in log file: {len(all_lines)}\n") f.write("-" * 50 + "\n\n") f.write(ascii_plain + "\n\n") f.write(log_content) - + file_obj = discord.File(log_file) await self.send_file(context, file=file_obj, ephemeral=True) - + os.remove(log_file) - + except PermissionError: embed = discord.Embed( title="Error", description=f"Permission denied when trying to read log file: `{log_file_path}`", color=0xE02B2B, ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" + ) await self.send_embed(context, embed, ephemeral=True) except Exception as e: embed = discord.Embed( @@ -105,7 +126,9 @@ class Logs(commands.Cog, name="logs"): description=f"An error occurred while reading the log file:\n```\n{str(e)}\n```", color=0xE02B2B, ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" + ) await self.send_embed(context, embed, ephemeral=True) async def cog_command_error(self, context: Context, error) -> None: @@ -115,7 +138,9 @@ class Logs(commands.Cog, name="logs"): description="You are not the owner of this bot!", color=0xE02B2B, ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" + ) await self.send_embed(context, embed, ephemeral=True) else: raise error @@ -123,4 +148,3 @@ class Logs(commands.Cog, name="logs"): async def setup(bot) -> None: await bot.add_cog(Logs(bot)) - \ No newline at end of file diff --git a/cogs/owner/say.py b/cogs/owner/say.py index a2ae0a9..7a48d6a 100644 --- a/cogs/owner/say.py +++ b/cogs/owner/say.py @@ -9,13 +9,24 @@ class Say(commands.Cog, name="say"): def __init__(self, bot) -> None: self.bot = bot - async def send_embed(self, context: Context, embed: discord.Embed, *, ephemeral: bool = False, allowed_mentions: discord.AllowedMentions = None) -> None: + async def send_embed( + self, + context: Context, + embed: discord.Embed, + *, + ephemeral: bool = False, + allowed_mentions: discord.AllowedMentions = None, + ) -> None: interaction = getattr(context, "interaction", None) if interaction is not None: if interaction.response.is_done(): - await interaction.followup.send(embed=embed, ephemeral=ephemeral, allowed_mentions=allowed_mentions) + await interaction.followup.send( + embed=embed, ephemeral=ephemeral, allowed_mentions=allowed_mentions + ) else: - await interaction.response.send_message(embed=embed, ephemeral=ephemeral, allowed_mentions=allowed_mentions) + await interaction.response.send_message( + embed=embed, ephemeral=ephemeral, allowed_mentions=allowed_mentions + ) else: await context.send(embed=embed, allowed_mentions=allowed_mentions) @@ -42,9 +53,9 @@ class Say(commands.Cog, name="say"): self.bot.logger.info( f"Say command used in DMs by {context.author} (ID: {context.author.id}): {message}" ) - + allowed_mentions = discord.AllowedMentions.none() - + interaction = getattr(context, "interaction", None) if interaction is not None: is_in_guild = context.guild is not None @@ -57,7 +68,9 @@ class Say(commands.Cog, name="say"): except: pass except discord.Forbidden: - await interaction.followup.send(message, allowed_mentions=allowed_mentions) + await interaction.followup.send( + message, allowed_mentions=allowed_mentions + ) else: try: await context.message.delete() @@ -88,28 +101,34 @@ class Say(commands.Cog, name="say"): self.bot.logger.info( f"Embed command used in DMs by {context.author} (ID: {context.author.id}): {message}" ) - + allowed_mentions = discord.AllowedMentions.none() embed = discord.Embed( title="Say", description=message, color=0x7289DA, ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") - + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" + ) + interaction = getattr(context, "interaction", None) if interaction is not None: is_in_guild = context.guild is not None await interaction.response.defer(ephemeral=is_in_guild) try: - await context.channel.send(embed=embed, allowed_mentions=allowed_mentions) + await context.channel.send( + embed=embed, allowed_mentions=allowed_mentions + ) if is_in_guild: try: await interaction.delete_original_response() except: pass except discord.Forbidden: - await interaction.followup.send(embed=embed, allowed_mentions=allowed_mentions) + await interaction.followup.send( + embed=embed, allowed_mentions=allowed_mentions + ) else: try: await context.message.delete() @@ -117,7 +136,6 @@ class Say(commands.Cog, name="say"): pass await context.send(embed=embed, allowed_mentions=allowed_mentions) - async def cog_command_error(self, context: Context, error) -> None: if isinstance(error, (commands.NotOwner, commands.CheckFailure)): embed = discord.Embed( @@ -125,7 +143,9 @@ class Say(commands.Cog, name="say"): description="You are not the owner of this bot!", color=0xE02B2B, ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" + ) await self.send_embed(context, embed, ephemeral=True) else: raise error diff --git a/cogs/owner/shutdown.py b/cogs/owner/shutdown.py index 7c3aff6..ef7c73f 100644 --- a/cogs/owner/shutdown.py +++ b/cogs/owner/shutdown.py @@ -9,14 +9,18 @@ from discord.ext.commands import Context class Shutdown(commands.Cog, name="shutdown"): def __init__(self, bot) -> None: self.bot = bot - - async def send_embed(self, context: Context, embed: discord.Embed, *, ephemeral: bool = False) -> None: + + async def send_embed( + self, context: Context, embed: discord.Embed, *, ephemeral: bool = False + ) -> None: interaction = getattr(context, "interaction", None) if interaction is not None: if interaction.response.is_done(): await interaction.followup.send(embed=embed, ephemeral=ephemeral) else: - await interaction.response.send_message(embed=embed, ephemeral=ephemeral) + await interaction.response.send_message( + embed=embed, ephemeral=ephemeral + ) else: await context.send(embed=embed) @@ -42,7 +46,7 @@ class Shutdown(commands.Cog, name="shutdown"): self.bot.logger.info( f"Executed shutdown command by {context.author} (ID: {context.author.id}) in DMs" ) - + embed = discord.Embed( title="Shutdown", description="Shutting down. Bye! ", @@ -59,7 +63,9 @@ class Shutdown(commands.Cog, name="shutdown"): description="You are not the owner of this bot!", color=0xE02B2B, ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" + ) await self.send_embed(context, embed, ephemeral=True) else: raise error diff --git a/cogs/owner/sync.py b/cogs/owner/sync.py index 80e7929..470c1a0 100644 --- a/cogs/owner/sync.py +++ b/cogs/owner/sync.py @@ -8,13 +8,17 @@ class Sync(commands.Cog, name="sync"): def __init__(self, bot) -> None: self.bot = bot - async def send_embed(self, context: Context, embed: discord.Embed, *, ephemeral: bool = False) -> None: + async def send_embed( + self, context: Context, embed: discord.Embed, *, ephemeral: bool = False + ) -> None: interaction = getattr(context, "interaction", None) if interaction is not None: if interaction.response.is_done(): await interaction.followup.send(embed=embed, ephemeral=ephemeral) else: - await interaction.response.send_message(embed=embed, ephemeral=ephemeral) + await interaction.response.send_message( + embed=embed, ephemeral=ephemeral + ) else: await context.send(embed=embed) @@ -40,7 +44,9 @@ class Sync(commands.Cog, name="sync"): description="Slash commands have been globally synchronized.", color=0x7289DA, ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" + ) await self.send_embed(context, embed) return elif scope == "guild": @@ -51,7 +57,9 @@ class Sync(commands.Cog, name="sync"): description="Slash commands have been synchronized in this guild.", color=0x7289DA, ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" + ) await self.send_embed(context, embed) return embed = discord.Embed( @@ -59,7 +67,9 @@ class Sync(commands.Cog, name="sync"): description="The scope must be `global` or `guild`.", color=0xE02B2B, ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" + ) await self.send_embed(context, embed, ephemeral=True) @commands.hybrid_command( @@ -87,7 +97,9 @@ class Sync(commands.Cog, name="sync"): description="Slash commands have been globally unsynchronized.", color=0x7289DA, ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" + ) await self.send_embed(context, embed) return elif scope == "guild": @@ -98,7 +110,9 @@ class Sync(commands.Cog, name="sync"): description="Slash commands have been unsynchronized in this guild.", color=0x7289DA, ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" + ) await self.send_embed(context, embed) return embed = discord.Embed( @@ -106,10 +120,11 @@ class Sync(commands.Cog, name="sync"): description="The scope must be `global` or `guild`.", color=0xE02B2B, ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" + ) await self.send_embed(context, embed, ephemeral=True) - async def cog_command_error(self, context: Context, error) -> None: if isinstance(error, commands.NotOwner): embed = discord.Embed( @@ -117,7 +132,9 @@ class Sync(commands.Cog, name="sync"): description="You are not the owner of this bot!", color=0xE02B2B, ) - embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp") + embed.set_author( + name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp" + ) await self.send_embed(context, embed, ephemeral=True) else: raise error diff --git a/cogs/sidestore/__init__.py b/cogs/sidestore/__init__.py index 7b0049d..e0466c3 100644 --- a/cogs/sidestore/__init__.py +++ b/cogs/sidestore/__init__.py @@ -27,9 +27,12 @@ class Sidestore(commands.GroupCog, name="sidestore"): embed = discord.Embed( title="SideStore Commands", description="Choose a command from the dropdown below to get help with specific issues:", - color=0x8e82f9 + color=0x8E82F9, + ) + embed.set_author( + name="SideStore", + icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true", ) - 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) @@ -38,9 +41,12 @@ class Sidestore(commands.GroupCog, name="sidestore"): embed = discord.Embed( title="SideStore Commands", description="Choose a command from the dropdown below to get help with specific issues:", - color=0x8e82f9 + color=0x8E82F9, + ) + embed.set_author( + name="SideStore", + icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true", ) - 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) @@ -97,17 +103,17 @@ class Sidestore(commands.GroupCog, name="sidestore"): async def sidestore_group_sparse(self, context: Context): await self._invoke_hybrid(context, "sparse") - @app_commands.command( - name="help", - description="SideStore troubleshooting help" - ) + @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 + color=0x8E82F9, + ) + embed.set_author( + name="SideStore", + icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true", ) - embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true") view = SidestoreView(self.bot) @@ -115,80 +121,70 @@ class Sidestore(commands.GroupCog, name="sidestore"): @commands.check(_require_group_prefix) @commands.hybrid_command( - name="refresh", - description="Help with refreshing or installing apps" + 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" + 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" + 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" + 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" + 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" + 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" + 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" - ) + @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" + 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'") diff --git a/cogs/sidestore/afc.py b/cogs/sidestore/afc.py index cdf0a07..de043a2 100644 --- a/cogs/sidestore/afc.py +++ b/cogs/sidestore/afc.py @@ -11,36 +11,42 @@ def afc_command(): ) async def afc(self, context): embed = discord.Embed( - color=0x8e82f9, + color=0x8E82F9, description=( - '# AFC Connection Failure\n\n---\n\n' + - '1. Make sure Wi-Fi is connected to a stable network\n' + - '2. Make sure StosVPN is connected and updated\n' + - '3. If issue still persists, create and import a new pairing file using `idevice_pair`. See [Pairing File instructions](https://docs.sidestore.io/docs/installation/pairing-file) for details' - ) + "# AFC Connection Failure\n\n---\n\n" + + "1. Make sure Wi-Fi is connected to a stable network\n" + + "2. Make sure StosVPN is connected and updated\n" + + "3. If issue still persists, create and import a new pairing file using `idevice_pair`. See [Pairing File instructions](https://docs.sidestore.io/docs/installation/pairing-file) for details" + ), ) - embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true") - embed.set_footer(text=f'Last Edited by CelloSerenity') + embed.set_author( + name="SideStore", + icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true", + ) + embed.set_footer(text=f"Last Edited by CelloSerenity") embed.timestamp = discord.utils.utcnow() view = discord.ui.View() - view.add_item(discord.ui.Button( - label="Edit Command", - style=discord.ButtonStyle.secondary, - url="https://github.com/neoarz/Syntrel/blob/main/cogs/sidestore/afc.py", - emoji="<:githubicon:1417717356846776340>" - )) - view.add_item(discord.ui.Button( - label="Documentation", - style=discord.ButtonStyle.primary, - url="https://docs.sidestore.io/docs/troubleshooting/common-issues#afc-connection-failure", - emoji="<:sidestorepride:1417717648795631787>" - )) + view.add_item( + discord.ui.Button( + label="Edit Command", + style=discord.ButtonStyle.secondary, + url="https://github.com/neoarz/Syntrel/blob/main/cogs/sidestore/afc.py", + emoji="<:githubicon:1417717356846776340>", + ) + ) + view.add_item( + discord.ui.Button( + label="Documentation", + style=discord.ButtonStyle.primary, + url="https://docs.sidestore.io/docs/troubleshooting/common-issues#afc-connection-failure", + emoji="<:sidestorepride:1417717648795631787>", + ) + ) if context.interaction: await context.interaction.response.send_message(embed=embed, view=view) else: await context.send(embed=embed, view=view) - return afc diff --git a/cogs/sidestore/code.py b/cogs/sidestore/code.py index 37cba4a..f33d26a 100644 --- a/cogs/sidestore/code.py +++ b/cogs/sidestore/code.py @@ -11,49 +11,53 @@ def code_command(): ) async def code(self, context): embed = discord.Embed( - color=0x8e82f9, + color=0x8E82F9, description=( - '## Verification Code Not Received When Signing In with Apple ID\n\n---\n\n' + - - '1. **For iOS versions below 18.1:**\n' + - ' - Open the "Settings" app\n' + - ' - Tap on your name at the top of the screen\n' + - ' - Navigate to "Sign-In and Security"\n' + - ' - Select "Two-Factor Authentication"\n' + - ' - Choose "Get Verification Code"\n\n' + - '2. **For iOS versions 18.1 and above:**\n' + - ' - Visit [iCloud](https://www.icloud.com) on a web browser\n' + - ' - Click "Sign In"\n' + - ' - On an Apple device, you may see two options: "Sign In" and "Use Different Apple Account"\n' + - ' - Select the bottom option, "Use Different Apple Account"\n' + - ' - Enter your Apple ID and password, DO NOT USE A PASSKEY\n' + - ' - Apple will send you a verification code\n' + - ' - Use this code in SideStore to complete the sign-in process\n' + "## Verification Code Not Received When Signing In with Apple ID\n\n---\n\n" + + "1. **For iOS versions below 18.1:**\n" + + ' - Open the "Settings" app\n' + + " - Tap on your name at the top of the screen\n" + + ' - Navigate to "Sign-In and Security"\n' + + ' - Select "Two-Factor Authentication"\n' + + ' - Choose "Get Verification Code"\n\n' + + "2. **For iOS versions 18.1 and above:**\n" + + " - Visit [iCloud](https://www.icloud.com) on a web browser\n" + + ' - Click "Sign In"\n' + + ' - On an Apple device, you may see two options: "Sign In" and "Use Different Apple Account"\n' + + ' - Select the bottom option, "Use Different Apple Account"\n' + + " - Enter your Apple ID and password, DO NOT USE A PASSKEY\n" + + " - Apple will send you a verification code\n" + + " - Use this code in SideStore to complete the sign-in process\n" + ), + ) + embed.set_author( + name="SideStore", + icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true", + ) + embed.set_footer(text=f"Last Edited by CelloSerenity") + embed.timestamp = discord.utils.utcnow() + + view = discord.ui.View() + view.add_item( + discord.ui.Button( + label="Edit Command", + style=discord.ButtonStyle.secondary, + url="https://github.com/neoarz/Syntrel/blob/main/cogs/sidestore/code.py", + emoji="<:githubicon:1417717356846776340>", + ) + ) + view.add_item( + discord.ui.Button( + label="Documentation", + style=discord.ButtonStyle.primary, + url="https://docs.sidestore.io/docs/troubleshooting/#sign-in-issues", + emoji="<:sidestorepride:1417717648795631787>", ) ) - embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true") - embed.set_footer(text=f'Last Edited by CelloSerenity') - embed.timestamp = discord.utils.utcnow() - - view = discord.ui.View() - view.add_item(discord.ui.Button( - label="Edit Command", - style=discord.ButtonStyle.secondary, - url="https://github.com/neoarz/Syntrel/blob/main/cogs/sidestore/code.py", - emoji="<:githubicon:1417717356846776340>" - )) - view.add_item(discord.ui.Button( - label="Documentation", - style=discord.ButtonStyle.primary, - url="https://docs.sidestore.io/docs/troubleshooting/#sign-in-issues", - emoji="<:sidestorepride:1417717648795631787>" - )) - if context.interaction: await context.interaction.response.send_message(embed=embed, view=view) else: await context.send(embed=embed, view=view) - return code diff --git a/cogs/sidestore/crash.py b/cogs/sidestore/crash.py index cb26f07..a902f54 100644 --- a/cogs/sidestore/crash.py +++ b/cogs/sidestore/crash.py @@ -11,36 +11,39 @@ def crash_command(): ) async def crash(self, context): embed = discord.Embed( - color=0x8e82f9, + color=0x8E82F9, description=( - '# SideStore Crashing After Refresh\n\n---\n\n' + - 'First, to try and save your data:\n' + - "1. DON'T DELETE SIDESTORE, reinstall with AltServer's `Sideload .ipa`.\n" + - "If that doesn't work:\n" + - - '1. Delete your current SideStore. Reinstall with AltServer.\n' + - '2. Import your pairing file and sign into SideStore.\n' + - '3. Download the SideStore .ipa file, and save it to your Files app.\n' + - '4. Import the "Sidestore.ipa" file into SideStore, just like how you import any other IPA.\n\n' + - 'This process ensures SideStore is refreshed without issues.' - ) + "# SideStore Crashing After Refresh\n\n---\n\n" + + "First, to try and save your data:\n" + + "1. DON'T DELETE SIDESTORE, reinstall with AltServer's `Sideload .ipa`.\n" + + "If that doesn't work:\n" + + "1. Delete your current SideStore. Reinstall with AltServer.\n" + + "2. Import your pairing file and sign into SideStore.\n" + + "3. Download the SideStore .ipa file, and save it to your Files app.\n" + + '4. Import the "Sidestore.ipa" file into SideStore, just like how you import any other IPA.\n\n' + + "This process ensures SideStore is refreshed without issues." + ), ) - embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true") - embed.set_footer(text=f'Last Edited by CelloSerenity') + embed.set_author( + name="SideStore", + icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true", + ) + embed.set_footer(text=f"Last Edited by CelloSerenity") embed.timestamp = discord.utils.utcnow() view = discord.ui.View() - view.add_item(discord.ui.Button( - label="Edit Command", - style=discord.ButtonStyle.secondary, - url="https://github.com/neoarz/Syntrel/blob/main/cogs/sidestore/crash.py", - emoji="<:githubicon:1417717356846776340>" - )) + view.add_item( + discord.ui.Button( + label="Edit Command", + style=discord.ButtonStyle.secondary, + url="https://github.com/neoarz/Syntrel/blob/main/cogs/sidestore/crash.py", + emoji="<:githubicon:1417717356846776340>", + ) + ) if context.interaction: await context.interaction.response.send_message(embed=embed, view=view) else: await context.send(embed=embed, view=view) - return crash diff --git a/cogs/sidestore/half.py b/cogs/sidestore/half.py index 97f2b52..90e8d1f 100644 --- a/cogs/sidestore/half.py +++ b/cogs/sidestore/half.py @@ -11,42 +11,48 @@ def half_command(): ) async def half(self, context): embed = discord.Embed( - color=0x8e82f9, + color=0x8E82F9, description=( - '# SideStore/IPAs Stuck Halfway Through Installing or Refreshing\n\n---\n' + - '- Make sure you are on the latest version of SideStore\n' + - '- Restart SideStore\n' + - '- Restart device\n' + - '- Clear Cache\n' + - '- Change Anisette Server\n' + - '- Reset adi.pb\n' + - '- Sign out from SideStore and sign back in\n' + - '- Recreate pairing file\n' + - '- Reinstall SideStore\n\n' - ) + "# SideStore/IPAs Stuck Halfway Through Installing or Refreshing\n\n---\n" + + "- Make sure you are on the latest version of SideStore\n" + + "- Restart SideStore\n" + + "- Restart device\n" + + "- Clear Cache\n" + + "- Change Anisette Server\n" + + "- Reset adi.pb\n" + + "- Sign out from SideStore and sign back in\n" + + "- Recreate pairing file\n" + + "- Reinstall SideStore\n\n" + ), ) - embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true") - embed.set_footer(text=f'Last Edited by CelloSerenity') + embed.set_author( + name="SideStore", + icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true", + ) + embed.set_footer(text=f"Last Edited by CelloSerenity") embed.timestamp = discord.utils.utcnow() view = discord.ui.View() - view.add_item(discord.ui.Button( - label="Edit Command", - style=discord.ButtonStyle.secondary, - url="https://github.com/neoarz/Syntrel/blob/main/cogs/sidestore/half.py", - emoji="<:githubicon:1417717356846776340>" - )) - view.add_item(discord.ui.Button( - label="Documentation", - style=discord.ButtonStyle.primary, - url="https://docs.sidestore.io/docs/troubleshooting/common-issues#sidestore-hangs-halfway-through-installation", - emoji="<:sidestorepride:1417717648795631787>" - )) + view.add_item( + discord.ui.Button( + label="Edit Command", + style=discord.ButtonStyle.secondary, + url="https://github.com/neoarz/Syntrel/blob/main/cogs/sidestore/half.py", + emoji="<:githubicon:1417717356846776340>", + ) + ) + view.add_item( + discord.ui.Button( + label="Documentation", + style=discord.ButtonStyle.primary, + url="https://docs.sidestore.io/docs/troubleshooting/common-issues#sidestore-hangs-halfway-through-installation", + emoji="<:sidestorepride:1417717648795631787>", + ) + ) if context.interaction: await context.interaction.response.send_message(embed=embed, view=view) else: await context.send(embed=embed, view=view) - return half diff --git a/cogs/sidestore/pairing.py b/cogs/sidestore/pairing.py index e4176ea..afc31ac 100644 --- a/cogs/sidestore/pairing.py +++ b/cogs/sidestore/pairing.py @@ -11,47 +11,53 @@ def pairing_command(): ) async def pairing(self, context): embed = discord.Embed( - color=0x8e82f9, + color=0x8E82F9, description=( - '# Cannot Choose Pairing File\n\n---\n\n' + - '1. **Check File Extension:**\n' + - " Make sure your pairing file's extension ends with `.mobiledevicepairing` or `.plist`\n" + - ' - If it doesn\'t, double-check to see if you had zipped your pairing file before sending it to your phone. Failing to do so may lead to the file being corrupted during transport\n\n' + - '2. **Move Pairing File:**\n' + - ' If you are unable to select the pairing file from within the app:\n' + - ' - Rename the file to `ALTPairingFile.mobiledevicepairing`\n' + - ' - Try moving the pairing file to the root directory of the SideStore folder in the Files app under "On My iPhone/iPad"\n\n' + - '3. **Certificate Signing:**\n' + - " When signing SideStore with certain certificates, you won't be able to select the pairing file from within the app\n" + - ' - Try the fix mentioned above\n' + - ' - If you do not see the SideStore folder in the Files app:\n' + - ' โ€ข Connect your phone to your computer\n' + - ' โ€ข Drag and drop the pairing file into the SideStore app\'s files section\n' + - ' โ€ข Ensure the file is renamed to `ALTPairingFile.mobiledevicepairing`\n' - ) + "# Cannot Choose Pairing File\n\n---\n\n" + + "1. **Check File Extension:**\n" + + " Make sure your pairing file's extension ends with `.mobiledevicepairing` or `.plist`\n" + + " - If it doesn't, double-check to see if you had zipped your pairing file before sending it to your phone. Failing to do so may lead to the file being corrupted during transport\n\n" + + "2. **Move Pairing File:**\n" + + " If you are unable to select the pairing file from within the app:\n" + + " - Rename the file to `ALTPairingFile.mobiledevicepairing`\n" + + ' - Try moving the pairing file to the root directory of the SideStore folder in the Files app under "On My iPhone/iPad"\n\n' + + "3. **Certificate Signing:**\n" + + " When signing SideStore with certain certificates, you won't be able to select the pairing file from within the app\n" + + " - Try the fix mentioned above\n" + + " - If you do not see the SideStore folder in the Files app:\n" + + " โ€ข Connect your phone to your computer\n" + + " โ€ข Drag and drop the pairing file into the SideStore app's files section\n" + + " โ€ข Ensure the file is renamed to `ALTPairingFile.mobiledevicepairing`\n" + ), ) - embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true") - embed.set_footer(text=f'Last Edited by CelloSerenity') + embed.set_author( + name="SideStore", + icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true", + ) + embed.set_footer(text=f"Last Edited by CelloSerenity") embed.timestamp = discord.utils.utcnow() view = discord.ui.View() - view.add_item(discord.ui.Button( - label="Edit Command", - style=discord.ButtonStyle.secondary, - url="https://github.com/neoarz/Syntrel/blob/main/cogs/sidestore/pairing.py", - emoji="<:githubicon:1417717356846776340>" - )) - view.add_item(discord.ui.Button( - label="Documentation", - style=discord.ButtonStyle.primary, - url="https://docs.sidestore.io/docs/troubleshooting/#cannot-choose-pairing-file", - emoji="<:sidestorepride:1417717648795631787>" - )) + view.add_item( + discord.ui.Button( + label="Edit Command", + style=discord.ButtonStyle.secondary, + url="https://github.com/neoarz/Syntrel/blob/main/cogs/sidestore/pairing.py", + emoji="<:githubicon:1417717356846776340>", + ) + ) + view.add_item( + discord.ui.Button( + label="Documentation", + style=discord.ButtonStyle.primary, + url="https://docs.sidestore.io/docs/troubleshooting/#cannot-choose-pairing-file", + emoji="<:sidestorepride:1417717648795631787>", + ) + ) if context.interaction: await context.interaction.response.send_message(embed=embed, view=view) else: await context.send(embed=embed, view=view) - return pairing diff --git a/cogs/sidestore/refresh.py b/cogs/sidestore/refresh.py index 08a47fe..016595f 100644 --- a/cogs/sidestore/refresh.py +++ b/cogs/sidestore/refresh.py @@ -11,37 +11,43 @@ def refresh_command(): ) async def refresh(self, context): embed = discord.Embed( - color=0x8e82f9, + color=0x8E82F9, description=( - '# Can\'t Refresh or Install Apps\n\n---\n\n' + - '1. Make sure your device is connected to a stable Wi-Fi network and not using cellular data.\n' + - '2. Verify VPN is connected in the StosVPN app.\n' + - '3. **Create a brand new pairing file.**\n' + - ' - If none of the above worked, it is very likely that the pairing file is corrupted. You can reference the documentation on how to create a new pairing file [here](https://docs.sidestore.io/docs/installation/pairing-file).\n' - ) + "# Can't Refresh or Install Apps\n\n---\n\n" + + "1. Make sure your device is connected to a stable Wi-Fi network and not using cellular data.\n" + + "2. Verify VPN is connected in the StosVPN app.\n" + + "3. **Create a brand new pairing file.**\n" + + " - If none of the above worked, it is very likely that the pairing file is corrupted. You can reference the documentation on how to create a new pairing file [here](https://docs.sidestore.io/docs/installation/pairing-file).\n" + ), ) - embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true") - embed.set_footer(text=f'Last Edited by neoarz') + embed.set_author( + name="SideStore", + icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true", + ) + embed.set_footer(text=f"Last Edited by neoarz") embed.timestamp = discord.utils.utcnow() view = discord.ui.View() - view.add_item(discord.ui.Button( - label="Edit Command", - style=discord.ButtonStyle.secondary, - url="https://github.com/neoarz/Syntrel/blob/main/cogs/sidestore/refresh.py", - emoji="<:githubicon:1417717356846776340>" - )) - view.add_item(discord.ui.Button( - label="Documentation", - style=discord.ButtonStyle.primary, - url="https://docs.sidestore.io/docs/installation/pairing-file", - emoji="<:sidestorepride:1417717648795631787>" - )) + view.add_item( + discord.ui.Button( + label="Edit Command", + style=discord.ButtonStyle.secondary, + url="https://github.com/neoarz/Syntrel/blob/main/cogs/sidestore/refresh.py", + emoji="<:githubicon:1417717356846776340>", + ) + ) + view.add_item( + discord.ui.Button( + label="Documentation", + style=discord.ButtonStyle.primary, + url="https://docs.sidestore.io/docs/installation/pairing-file", + emoji="<:sidestorepride:1417717648795631787>", + ) + ) if context.interaction: await context.interaction.response.send_message(embed=embed, view=view) else: await context.send(embed=embed, view=view) - return refresh diff --git a/cogs/sidestore/server.py b/cogs/sidestore/server.py index 612eb70..ba80685 100644 --- a/cogs/sidestore/server.py +++ b/cogs/sidestore/server.py @@ -11,41 +11,47 @@ def server_command(): ) async def server(self, context): embed = discord.Embed( - color=0x8e82f9, + color=0x8E82F9, description=( - '# SideStore Freezing or Displaying an Error Code During Sign-In\n\n---\n\n' + - '1. **Change the Anisette Server:**\n' + - ' The most common solution is to switch to a different Anisette server. Do this:\n' + - ' - Open Sidestore settings\n' + - ' - Scroll down to the "Anisette Server" option\n' + - ' - Select a different server from the list\n' + - ' - You might need to try a few servers from the list and find which works best for you\n\n' + - '2. **Host Your Own Anisette Server:**\n' + - ' If you prefer, you can set up your own Anisette server. Detailed instructions for hosting an Anisette server are available in the official documentation and can be found [here](https://docs.sidestore.io/docs/advanced/anisette).\n\n' - ) + "# SideStore Freezing or Displaying an Error Code During Sign-In\n\n---\n\n" + + "1. **Change the Anisette Server:**\n" + + " The most common solution is to switch to a different Anisette server. Do this:\n" + + " - Open Sidestore settings\n" + + ' - Scroll down to the "Anisette Server" option\n' + + " - Select a different server from the list\n" + + " - You might need to try a few servers from the list and find which works best for you\n\n" + + "2. **Host Your Own Anisette Server:**\n" + + " If you prefer, you can set up your own Anisette server. Detailed instructions for hosting an Anisette server are available in the official documentation and can be found [here](https://docs.sidestore.io/docs/advanced/anisette).\n\n" + ), ) - embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true") - embed.set_footer(text=f'Last Edited by CelloSerenity') + embed.set_author( + name="SideStore", + icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true", + ) + embed.set_footer(text=f"Last Edited by CelloSerenity") embed.timestamp = discord.utils.utcnow() view = discord.ui.View() - view.add_item(discord.ui.Button( - label="Edit Command", - style=discord.ButtonStyle.secondary, - url="https://github.com/neoarz/Syntrel/blob/main/cogs/sidestore/server.py", - emoji="<:githubicon:1417717356846776340>" - )) - view.add_item(discord.ui.Button( - label="Documentation", - style=discord.ButtonStyle.primary, - url="https://docs.sidestore.io/docs/troubleshooting/#sidestore-freezing-or-displaying-an-error-code-during-sign-in", - emoji="<:sidestorepride:1417717648795631787>" - )) + view.add_item( + discord.ui.Button( + label="Edit Command", + style=discord.ButtonStyle.secondary, + url="https://github.com/neoarz/Syntrel/blob/main/cogs/sidestore/server.py", + emoji="<:githubicon:1417717356846776340>", + ) + ) + view.add_item( + discord.ui.Button( + label="Documentation", + style=discord.ButtonStyle.primary, + url="https://docs.sidestore.io/docs/troubleshooting/#sidestore-freezing-or-displaying-an-error-code-during-sign-in", + emoji="<:sidestorepride:1417717648795631787>", + ) + ) if context.interaction: await context.interaction.response.send_message(embed=embed, view=view) else: await context.send(embed=embed, view=view) - return server diff --git a/cogs/sidestore/sidestore.py b/cogs/sidestore/sidestore.py index 078bc5d..6d9676d 100644 --- a/cogs/sidestore/sidestore.py +++ b/cogs/sidestore/sidestore.py @@ -53,14 +53,14 @@ class SidestoreSelect(discord.ui.Select): label="UDID Error", value="udid", description="SideStore could not determine device UDID", - ) + ), ] super().__init__(placeholder="Choose a SideStore command...", options=options) async def callback(self, interaction: discord.Interaction): command_name = self.values[0] command = self.bot.get_command(command_name) - + if command: try: ctx = await self.bot.get_context(interaction.message) @@ -69,44 +69,60 @@ class SidestoreSelect(discord.ui.Select): embed = discord.Embed( title="Command Executed", description=f"Successfully executed `/{command_name}`", - color=0x00FF00 + color=0x00FF00, + ) + embed.set_author( + name="SideStore", + icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true", ) - embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true") await interaction.response.edit_message(embed=embed, view=None) except discord.Forbidden: - guild_info = f"server {interaction.guild.name} (ID: {interaction.guild.id})" if interaction.guild else "DM or private channel" - self.bot.logger.warning(f"Bot missing permissions in {guild_info} - cannot execute {command_name} command") - + guild_info = ( + f"server {interaction.guild.name} (ID: {interaction.guild.id})" + if interaction.guild + else "DM or private channel" + ) + self.bot.logger.warning( + f"Bot missing permissions in {guild_info} - cannot execute {command_name} command" + ) + if interaction.guild is None: embed = discord.Embed( title="Error", description="This command cannot be executed in DMs.", - color=0xFF0000 + color=0xFF0000, ) else: embed = discord.Embed( title="Permission Error", description="The bot needs the `send messages` permission to execute this command.", - color=0xFF0000 + color=0xFF0000, ) - embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true") + embed.set_author( + name="SideStore", + icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true", + ) await interaction.response.edit_message(embed=embed, view=None) except Exception as e: self.bot.logger.error(f"Error executing {command_name} command: {e}") embed = discord.Embed( title="Error", description="An error occurred while executing the command.", - color=0xFF0000 + color=0xFF0000, + ) + embed.set_author( + name="SideStore", + icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true", ) - embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true") await interaction.response.edit_message(embed=embed, view=None) else: embed = discord.Embed( - title="Error", - description="Command not found!", - color=0xFF0000 + title="Error", description="Command not found!", color=0xFF0000 + ) + embed.set_author( + name="SideStore", + icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true", ) - embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true") await interaction.response.edit_message(embed=embed, view=None) @@ -127,10 +143,15 @@ def sidestore_command(): description="This command can only be used in servers.", color=0xE02B2B, ) - embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true") - + embed.set_author( + name="SideStore", + icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true", + ) + if context.interaction: - await context.interaction.response.send_message(embed=embed, ephemeral=True) + await context.interaction.response.send_message( + embed=embed, ephemeral=True + ) else: await context.send(embed=embed, ephemeral=True) return @@ -141,10 +162,15 @@ def sidestore_command(): description="The bot needs send messages permissions in this channel.", color=0xE02B2B, ) - embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true") - + embed.set_author( + name="SideStore", + icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true", + ) + if context.interaction: - await context.interaction.response.send_message(embed=embed, ephemeral=True) + await context.interaction.response.send_message( + embed=embed, ephemeral=True + ) else: await context.send(embed=embed, ephemeral=True) return @@ -152,15 +178,20 @@ def sidestore_command(): embed = discord.Embed( title="SideStore Commands", description="Choose a command from the dropdown below to get help with specific issues:", - color=0x8e82f9 + color=0x8E82F9, ) - embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true") - + embed.set_author( + name="SideStore", + icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true", + ) + view = SidestoreView(self.bot) - + if context.interaction: - await context.interaction.response.send_message(embed=embed, view=view, ephemeral=True) + await context.interaction.response.send_message( + embed=embed, view=view, ephemeral=True + ) else: await context.send(embed=embed, view=view) - + return sidestore diff --git a/cogs/sidestore/sparse.py b/cogs/sidestore/sparse.py index e039bc8..1735322 100644 --- a/cogs/sidestore/sparse.py +++ b/cogs/sidestore/sparse.py @@ -11,36 +11,42 @@ def sparse_command(): ) async def sparse(self, context): embed = discord.Embed( - color=0x8e82f9, + color=0x8E82F9, description=( - '# SparseRestore "Bypass 3 App Limit" Exploit\n\n---\n\n' + - 'The SparseRestore exploit allows you to bypass the 3-app sideloading limit. It is compatible with iOS/iPadOS versions **15.2 to 18.1 beta 4**, (not including **17.7.1** and **17.7.2**).\n\n' + - 'iOS/iPadOS versions **17.0** (not including **16.7** and **16.7.10**) are recommended to use [Trollstore](https://ios.cfw.guide/installing-trollstore/)\n\n' + - 'If you\'re on a supported version and want to sideload more than three apps, follow the detailed instructions found in our documentation' - ) + '# SparseRestore "Bypass 3 App Limit" Exploit\n\n---\n\n' + + "The SparseRestore exploit allows you to bypass the 3-app sideloading limit. It is compatible with iOS/iPadOS versions **15.2 to 18.1 beta 4**, (not including **17.7.1** and **17.7.2**).\n\n" + + "iOS/iPadOS versions **17.0** (not including **16.7** and **16.7.10**) are recommended to use [Trollstore](https://ios.cfw.guide/installing-trollstore/)\n\n" + + "If you're on a supported version and want to sideload more than three apps, follow the detailed instructions found in our documentation" + ), ) - embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true") - embed.set_footer(text=f'Last Edited by neoarz') + embed.set_author( + name="SideStore", + icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true", + ) + embed.set_footer(text=f"Last Edited by neoarz") embed.timestamp = discord.utils.utcnow() view = discord.ui.View() - view.add_item(discord.ui.Button( - label="Edit Command", - style=discord.ButtonStyle.secondary, - url="https://github.com/neoarz/Syntrel/blob/main/cogs/sidestore/sparse.py", - emoji="<:githubicon:1417717356846776340>" - )) - view.add_item(discord.ui.Button( - label="Documentation", - style=discord.ButtonStyle.primary, - url="https://docs.sidestore.io/docs/advanced/sparserestore", - emoji="<:sidestorepride:1417717648795631787>" - )) + view.add_item( + discord.ui.Button( + label="Edit Command", + style=discord.ButtonStyle.secondary, + url="https://github.com/neoarz/Syntrel/blob/main/cogs/sidestore/sparse.py", + emoji="<:githubicon:1417717356846776340>", + ) + ) + view.add_item( + discord.ui.Button( + label="Documentation", + style=discord.ButtonStyle.primary, + url="https://docs.sidestore.io/docs/advanced/sparserestore", + emoji="<:sidestorepride:1417717648795631787>", + ) + ) if context.interaction: await context.interaction.response.send_message(embed=embed, view=view) else: await context.send(embed=embed, view=view) - return sparse diff --git a/cogs/sidestore/udid.py b/cogs/sidestore/udid.py index 4c651ca..1d9a024 100644 --- a/cogs/sidestore/udid.py +++ b/cogs/sidestore/udid.py @@ -11,35 +11,41 @@ def udid_command(): ) async def udid(self, context): embed = discord.Embed( - color=0x8e82f9, + color=0x8E82F9, description=( - '# SideStore Could Not Determine Device UDID\n\n---\n\n' + - 'This error usually occurs when the pairing file is corrupted. Please create a new pairing file using `idevice_pair` and try again.\n\n' + - 'If you forgot how to create a new pairing file, you can refer to the documentation below.' - ) + "# SideStore Could Not Determine Device UDID\n\n---\n\n" + + "This error usually occurs when the pairing file is corrupted. Please create a new pairing file using `idevice_pair` and try again.\n\n" + + "If you forgot how to create a new pairing file, you can refer to the documentation below." + ), ) - embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true") - embed.set_footer(text=f'Last Edited by CelloSerenity') + embed.set_author( + name="SideStore", + icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true", + ) + embed.set_footer(text=f"Last Edited by CelloSerenity") embed.timestamp = discord.utils.utcnow() view = discord.ui.View() - view.add_item(discord.ui.Button( - label="Edit Command", - style=discord.ButtonStyle.secondary, - url="https://github.com/neoarz/Syntrel/blob/main/cogs/sidestore/udid.py", - emoji="<:githubicon:1417717356846776340>" - )) - view.add_item(discord.ui.Button( - label="Documentation", - style=discord.ButtonStyle.secondary, - url="https://docs.sidestore.io/docs/installation/pairing-file/", - emoji="<:sidestorepride:1417717648795631787>" - )) + view.add_item( + discord.ui.Button( + label="Edit Command", + style=discord.ButtonStyle.secondary, + url="https://github.com/neoarz/Syntrel/blob/main/cogs/sidestore/udid.py", + emoji="<:githubicon:1417717356846776340>", + ) + ) + view.add_item( + discord.ui.Button( + label="Documentation", + style=discord.ButtonStyle.secondary, + url="https://docs.sidestore.io/docs/installation/pairing-file/", + emoji="<:sidestorepride:1417717648795631787>", + ) + ) if context.interaction: await context.interaction.response.send_message(embed=embed, view=view) else: await context.send(embed=embed, view=view) - return udid diff --git a/cogs/utilities/__init__.py b/cogs/utilities/__init__.py index c05a9fa..d377a96 100644 --- a/cogs/utilities/__init__.py +++ b/cogs/utilities/__init__.py @@ -20,10 +20,14 @@ class Utilities(commands.GroupCog, name="utils"): embed = discord.Embed( title="Utilities Commands", description="Use `.utils ` or `/utils `.", - color=0x7289DA + color=0x7289DA, + ) + embed.set_author( + name="Utilities", icon_url="https://yes.nighty.works/raw/8VLDcg.webp" + ) + embed.add_field( + name="Available", value="translate, codepreview, dictionary", inline=False ) - embed.set_author(name="Utilities", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") - embed.add_field(name="Available", value="translate, codepreview, dictionary", inline=False) await context.send(embed=embed) async def _invoke_hybrid(self, context: Context, name: str, **kwargs): @@ -47,62 +51,68 @@ class Utilities(commands.GroupCog, name="utils"): @app_commands.describe( text="The text to translate", to_lang="Target language (e.g., 'en', 'es', 'fr')", - from_lang="Source language (leave empty for auto-detect)" + from_lang="Source language (leave empty for auto-detect)", ) @app_commands.autocomplete(to_lang=language_autocomplete) @app_commands.autocomplete(from_lang=language_autocomplete) - 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) + 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 + ) @utilities_group.command(name="codepreview") async def utilities_group_codepreview(self, context: Context, url: str = None): await self._invoke_hybrid(context, "codepreview", url=url) @utilities_group.command(name="dictionary") - @app_commands.describe( - word="The word to look up" - ) + @app_commands.describe(word="The word to look up") async def utilities_group_dictionary(self, context: Context, word: str = None): await self._invoke_hybrid(context, "dictionary", word=word) @commands.check(_require_group_prefix) @commands.hybrid_command( - name="translate", - description="Translate text to another language" + name="translate", description="Translate text to another language" ) @app_commands.describe( text="The text to translate", to_lang="Target language (e.g., 'en', 'es', 'fr')", - from_lang="Source language (leave empty for auto-detect)" + from_lang="Source language (leave empty for auto-detect)", ) @app_commands.autocomplete(to_lang=language_autocomplete) @app_commands.autocomplete(from_lang=language_autocomplete) - 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 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 + ) @commands.check(_require_group_prefix) @commands.hybrid_command( - name="codepreview", - description="Preview code from GitHub URLs" + name="codepreview", description="Preview code from GitHub URLs" ) async def codepreview(self, context, url: str = None): return await codepreview_command()(self, context, url=url) @commands.check(_require_group_prefix) @commands.hybrid_command( - name="dictionary", - description="Get the definition of a word" - ) - @app_commands.describe( - word="The word to look up" + name="dictionary", description="Get the definition of a word" ) + @app_commands.describe(word="The word to look up") async def dictionary(self, context, word: str = None): return await dictionary_command()(self, context, word=word) + async def setup(bot) -> None: cog = Utilities(bot) await bot.add_cog(cog) - + bot.logger.info("Loaded extension 'utilities.translate'") bot.logger.info("Loaded extension 'utilities.codepreview'") bot.logger.info("Loaded extension 'utilities.dictionary'") diff --git a/cogs/utilities/codepreview.py b/cogs/utilities/codepreview.py index 6681d9d..7a56332 100644 --- a/cogs/utilities/codepreview.py +++ b/cogs/utilities/codepreview.py @@ -9,40 +9,44 @@ import json def codepreview_command(): LANGUAGE_MAP = { - '.py': 'python', - '.js': 'js', - '.ts': 'ts', - '.java': 'java', - '.cpp': 'cpp', - '.c': 'c', - '.cs': 'cs', - '.go': 'go', - '.rs': 'rust', - '.rb': 'ruby', - '.php': 'php', - '.html': 'html', - '.css': 'css', - '.json': 'json', - '.xml': 'xml', - '.yaml': 'yaml', - '.yml': 'yaml', - '.ini': 'ini', - '.toml': 'toml', - '.lua': 'lua', - '.sh': 'bash', - '.md': 'markdown', - '.sql': 'sql', - '.diff': 'diff', - '.txt': '', + ".py": "python", + ".js": "js", + ".ts": "ts", + ".java": "java", + ".cpp": "cpp", + ".c": "c", + ".cs": "cs", + ".go": "go", + ".rs": "rust", + ".rb": "ruby", + ".php": "php", + ".html": "html", + ".css": "css", + ".json": "json", + ".xml": "xml", + ".yaml": "yaml", + ".yml": "yaml", + ".ini": "ini", + ".toml": "toml", + ".lua": "lua", + ".sh": "bash", + ".md": "markdown", + ".sql": "sql", + ".diff": "diff", + ".txt": "", } - async def send_embed(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(): await interaction.followup.send(embed=embed, ephemeral=ephemeral) else: - await interaction.response.send_message(embed=embed, ephemeral=ephemeral) + await interaction.response.send_message( + embed=embed, ephemeral=ephemeral + ) else: await context.send(embed=embed) @@ -50,7 +54,7 @@ def codepreview_command(): for ext, lang in LANGUAGE_MAP.items(): if filename.endswith(ext): return lang - return '' + return "" async def fetch_github_content(url): try: @@ -64,12 +68,12 @@ def codepreview_command(): async def fetch_pr_diff(owner, repo, pr_number): try: - api_url = f'https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}' + api_url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}" headers = { - 'Accept': 'application/vnd.github.v3.diff', - 'User-Agent': 'Discord-Bot' + "Accept": "application/vnd.github.v3.diff", + "User-Agent": "Discord-Bot", } - + async with aiohttp.ClientSession() as session: async with session.get(api_url, headers=headers) as response: if response.status == 200: @@ -81,98 +85,95 @@ def codepreview_command(): async def fetch_pr_info(owner, repo, pr_number): try: - api_url = f'https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}' + api_url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}" headers = { - 'Accept': 'application/vnd.github.v3+json', - 'User-Agent': 'Discord-Bot' + "Accept": "application/vnd.github.v3+json", + "User-Agent": "Discord-Bot", } - + async with aiohttp.ClientSession() as session: async with session.get(api_url, headers=headers) as response: if response.status == 200: pr_data = await response.json() return { - 'title': pr_data.get('title', ''), - 'number': pr_data.get('number', pr_number), - 'state': pr_data.get('state', ''), - 'merged': pr_data.get('merged', False), - 'additions': pr_data.get('additions', 0), - 'deletions': pr_data.get('deletions', 0), - 'changed_files': pr_data.get('changed_files', 0), - 'user': pr_data.get('user', {}).get('login', ''), - 'base_branch': pr_data.get('base', {}).get('ref', ''), - 'head_branch': pr_data.get('head', {}).get('ref', '') + "title": pr_data.get("title", ""), + "number": pr_data.get("number", pr_number), + "state": pr_data.get("state", ""), + "merged": pr_data.get("merged", False), + "additions": pr_data.get("additions", 0), + "deletions": pr_data.get("deletions", 0), + "changed_files": pr_data.get("changed_files", 0), + "user": pr_data.get("user", {}).get("login", ""), + "base_branch": pr_data.get("base", {}).get("ref", ""), + "head_branch": pr_data.get("head", {}).get("ref", ""), } except Exception: pass return None def parse_github_url(url): - pr_pattern = r'https://github\.com/([^/]+)/([^/]+)/pull/(\d+)(?:/files)?' + pr_pattern = r"https://github\.com/([^/]+)/([^/]+)/pull/(\d+)(?:/files)?" pr_match = re.match(pr_pattern, url) - + if pr_match: owner, repo, pr_number = pr_match.groups() - return { - 'type': 'pr', - 'owner': owner, - 'repo': repo, - 'pr_number': pr_number - } - - raw_pattern = r'https://raw\.githubusercontent\.com/([^/]+)/([^/]+)/([^/]+)/(.+?)$' + return {"type": "pr", "owner": owner, "repo": repo, "pr_number": pr_number} + + raw_pattern = ( + r"https://raw\.githubusercontent\.com/([^/]+)/([^/]+)/([^/]+)/(.+?)$" + ) raw_match = re.match(raw_pattern, url) - + if raw_match: owner, repo, branch, filepath = raw_match.groups() - + return { - 'type': 'file', - 'owner': owner, - 'repo': repo, - 'branch': branch, - 'filepath': filepath, - 'raw_url': url, - 'start_line': None, - 'end_line': None + "type": "file", + "owner": owner, + "repo": repo, + "branch": branch, + "filepath": filepath, + "raw_url": url, + "start_line": None, + "end_line": None, } - - pattern = r'https://github\.com/([^/]+)/([^/]+)/blob/([^/]+)/(.+?)(?:#L(\d+)(?:-L(\d+))?)?$' + + pattern = r"https://github\.com/([^/]+)/([^/]+)/blob/([^/]+)/(.+?)(?:#L(\d+)(?:-L(\d+))?)?$" match = re.match(pattern, url) - + if match: owner, repo, branch, filepath, start_line, end_line = match.groups() - - raw_url = f'https://raw.githubusercontent.com/{owner}/{repo}/{branch}/{filepath}' - + + raw_url = ( + f"https://raw.githubusercontent.com/{owner}/{repo}/{branch}/{filepath}" + ) + return { - 'type': 'file', - 'owner': owner, - 'repo': repo, - 'branch': branch, - 'filepath': filepath, - 'raw_url': raw_url, - 'start_line': int(start_line) if start_line else None, - 'end_line': int(end_line) if end_line else None + "type": "file", + "owner": owner, + "repo": repo, + "branch": branch, + "filepath": filepath, + "raw_url": raw_url, + "start_line": int(start_line) if start_line else None, + "end_line": int(end_line) if end_line else None, } return None def extract_lines(content, start_line, end_line): - lines = content.split('\n') - + lines = content.split("\n") + if start_line and end_line: - return '\n'.join(lines[start_line-1:end_line]) + return "\n".join(lines[start_line - 1 : end_line]) elif start_line: - return lines[start_line-1] if start_line <= len(lines) else content + return lines[start_line - 1] if start_line <= len(lines) else content return content @commands.hybrid_command( name="codepreview", description="Preview code from GitHub URLs", ) - @app_commands.describe( - url="GitHub URL to preview code from" - ) + @app_commands.describe(url="GitHub URL to preview code from") async def codepreview(self, context, url: str = None): if isinstance(context.channel, discord.DMChannel): embed = discord.Embed( @@ -180,8 +181,10 @@ def codepreview_command(): description="This command can only be used in servers.", color=0xE02B2B, ) - embed.set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") - + embed.set_author( + name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp" + ) + interaction = getattr(context, "interaction", None) if interaction is not None: if not interaction.response.is_done(): @@ -198,8 +201,10 @@ def codepreview_command(): description="The bot needs the `send messages` permission in this channel.", color=0xE02B2B, ) - embed.set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") - + embed.set_author( + name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp" + ) + interaction = getattr(context, "interaction", None) if interaction is not None: if not interaction.response.is_done(): @@ -209,12 +214,18 @@ def codepreview_command(): else: await context.send(embed=embed, ephemeral=True) return - + if not url or not url.strip(): - if context.message and context.message.reference and context.message.reference.resolved: + if ( + context.message + and context.message.reference + and context.message.reference.resolved + ): replied_message = context.message.reference.resolved - if hasattr(replied_message, 'content') and replied_message.content: - github_pattern = r'https://(?:github\.com|raw\.githubusercontent\.com)/[^\s]+' + if hasattr(replied_message, "content") and replied_message.content: + github_pattern = ( + r"https://(?:github\.com|raw\.githubusercontent\.com)/[^\s]+" + ) urls = re.findall(github_pattern, replied_message.content) if urls: url = urls[0] @@ -223,7 +234,10 @@ def codepreview_command(): title="Error", description="No GitHub URL found in the replied message.", color=0xE02B2B, - ).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") + ).set_author( + name="Utility", + icon_url="https://yes.nighty.works/raw/8VLDcg.webp", + ) await send_embed(context, embed, ephemeral=True) return else: @@ -231,7 +245,10 @@ def codepreview_command(): title="Error", description="The replied message has no content to extract GitHub URL from.", color=0xE02B2B, - ).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") + ).set_author( + name="Utility", + icon_url="https://yes.nighty.works/raw/8VLDcg.webp", + ) await send_embed(context, embed, ephemeral=True) return else: @@ -239,23 +256,31 @@ def codepreview_command(): title="Error", description="Please provide a GitHub URL or reply to a message containing a GitHub URL.", color=0xE02B2B, - ).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") + ).set_author( + name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp" + ) await send_embed(context, embed, ephemeral=True) return - - if not url.startswith('https://github.com/') and not url.startswith('https://raw.githubusercontent.com/'): + + if not url.startswith("https://github.com/") and not url.startswith( + "https://raw.githubusercontent.com/" + ): embed = discord.Embed( title="Error", description="Please provide a valid GitHub URL.", color=0xE02B2B, - ).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") + ).set_author( + name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp" + ) await send_embed(context, embed, ephemeral=True) return # Check if bot has send messages permission before starting processing try: test_embed = discord.Embed(title="Testing permissions...", color=0x7289DA) - test_embed.set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") + test_embed.set_author( + name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp" + ) await context.channel.send(embed=test_embed, delete_after=0.1) except discord.Forbidden: embed = discord.Embed( @@ -263,8 +288,10 @@ def codepreview_command(): description="The bot needs the `send messages` permission to execute this command.", color=0xE02B2B, ) - embed.set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") - + embed.set_author( + name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp" + ) + interaction = getattr(context, "interaction", None) if interaction is not None: if not interaction.response.is_done(): @@ -274,135 +301,177 @@ def codepreview_command(): else: await context.send(embed=embed, ephemeral=True) return - + parsed = parse_github_url(url) - + if not parsed: embed = discord.Embed( title="Error", description="Invalid GitHub URL format. Please provide a valid GitHub blob URL or PR URL.", color=0xE02B2B, - ).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") + ).set_author( + name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp" + ) await send_embed(context, embed, ephemeral=True) return - - if parsed.get('type') == 'pr': - pr_info = await fetch_pr_info(parsed['owner'], parsed['repo'], parsed['pr_number']) - diff_content = await fetch_pr_diff(parsed['owner'], parsed['repo'], parsed['pr_number']) - + + if parsed.get("type") == "pr": + pr_info = await fetch_pr_info( + parsed["owner"], parsed["repo"], parsed["pr_number"] + ) + diff_content = await fetch_pr_diff( + parsed["owner"], parsed["repo"], parsed["pr_number"] + ) + if not pr_info or not diff_content: embed = discord.Embed( title="Error", description="Failed to fetch pull request information. The PR might not exist or be accessible.", color=0xE02B2B, - ).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") + ).set_author( + name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp" + ) await send_embed(context, embed, ephemeral=True) return - + pr_url = f"https://github.com/{parsed['owner']}/{parsed['repo']}/pull/{parsed['pr_number']}" - - if pr_info['merged']: - pr_color = 0x6f42c1 + + if pr_info["merged"]: + pr_color = 0x6F42C1 pr_status = "Merged" - elif pr_info['state'] == 'open': + elif pr_info["state"] == "open": pr_color = 0x57F287 pr_status = "Open" else: pr_color = 0xE02B2B pr_status = "Closed" - + embed = discord.Embed( title=f"Pull Request #{pr_info['number']}: {pr_info['title'][:100]}", description=f"**Repository:** [{parsed['owner']}/{parsed['repo']}]({pr_url})\n**Author:** {pr_info['user']}\n**Status:** {pr_status}", color=pr_color, ) - embed.set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") - embed.add_field(name="Changes", value=f"**+{pr_info['additions']}** / **-{pr_info['deletions']}**", inline=True) - embed.add_field(name="Files Changed", value=f"{pr_info['changed_files']}", inline=True) - embed.add_field(name="Branches", value=f"`{pr_info['base_branch']}` โ† `{pr_info['head_branch']}`", inline=False) - embed.set_footer(text=f"Requested by {context.author.name}", icon_url=context.author.display_avatar.url) - + embed.set_author( + name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp" + ) + embed.add_field( + name="Changes", + value=f"**+{pr_info['additions']}** / **-{pr_info['deletions']}**", + inline=True, + ) + embed.add_field( + name="Files Changed", value=f"{pr_info['changed_files']}", inline=True + ) + embed.add_field( + name="Branches", + value=f"`{pr_info['base_branch']}` โ† `{pr_info['head_branch']}`", + inline=False, + ) + embed.set_footer( + text=f"Requested by {context.author.name}", + icon_url=context.author.display_avatar.url, + ) + interaction = getattr(context, "interaction", None) if interaction is not None and not interaction.response.is_done(): await interaction.response.defer(ephemeral=True) - + await context.channel.send(embed=embed) - + max_diff_length = 1900 max_lines = 100 - - diff_lines = diff_content.split('\n') - + + diff_lines = diff_content.split("\n") + if len(diff_lines) > max_lines: diff_lines = diff_lines[:max_lines] - diff_lines.append(f"\n... ({len(diff_content.split(chr(10))) - max_lines} more lines omitted)") - + diff_lines.append( + f"\n... ({len(diff_content.split(chr(10))) - max_lines} more lines omitted)" + ) + current_chunk = "" - + for line in diff_lines: - test_chunk = current_chunk + line + '\n' - + test_chunk = current_chunk + line + "\n" + if len(test_chunk) + 10 > max_diff_length: if current_chunk.strip(): - remaining_lines = len(diff_lines) - len(current_chunk.split('\n')) + remaining_lines = len(diff_lines) - len( + current_chunk.split("\n") + ) if remaining_lines > 0: - current_chunk += f"\n... ({remaining_lines} more lines omitted)" - await context.channel.send(f"```diff\n{current_chunk.rstrip()}\n```") + current_chunk += ( + f"\n... ({remaining_lines} more lines omitted)" + ) + await context.channel.send( + f"```diff\n{current_chunk.rstrip()}\n```" + ) break else: current_chunk = test_chunk else: if current_chunk.strip(): - await context.channel.send(f"```diff\n{current_chunk.rstrip()}\n```") - + await context.channel.send( + f"```diff\n{current_chunk.rstrip()}\n```" + ) + if interaction is not None: try: await interaction.delete_original_response() except: pass return - - content = await fetch_github_content(parsed['raw_url']) - + + content = await fetch_github_content(parsed["raw_url"]) + if not content: embed = discord.Embed( title="Error", description="Failed to fetch content from GitHub. The file might not exist or be accessible.", color=0xE02B2B, - ).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") + ).set_author( + name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp" + ) await send_embed(context, embed, ephemeral=True) return - - if parsed['start_line']: - code = extract_lines(content, parsed['start_line'], parsed['end_line']) + + if parsed["start_line"]: + code = extract_lines(content, parsed["start_line"], parsed["end_line"]) line_info = f" (Lines {parsed['start_line']}" - if parsed['end_line']: + if parsed["end_line"]: line_info += f"-{parsed['end_line']}" line_info += ")" else: code = content line_info = "" - - code_lines = code.split('\n') + + code_lines = code.split("\n") if len(code_lines) > 100: - code = '\n'.join(code_lines[:100]) + code = "\n".join(code_lines[:100]) code += f"\n\n... ({len(code_lines) - 100} more lines omitted)" - - filename = parsed['filepath'].split('/')[-1] + + filename = parsed["filepath"].split("/")[-1] language = get_language_from_filename(filename) - + embed = discord.Embed( title="Code Preview", description=f"**Repository URL:** [{parsed['owner']}/{parsed['repo']}]({url})", color=0x7289DA, ) - embed.set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") - embed.add_field(name="File", value=f"`{parsed['filepath']}`{line_info}", inline=True) + embed.set_author( + name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp" + ) + embed.add_field( + name="File", value=f"`{parsed['filepath']}`{line_info}", inline=True + ) embed.add_field(name="Branch", value=f"`{parsed['branch']}`", inline=True) - embed.set_footer(text=f"Requested by {context.author.name}", icon_url=context.author.display_avatar.url) - + embed.set_footer( + text=f"Requested by {context.author.name}", + icon_url=context.author.display_avatar.url, + ) + code_block = f"```{language}\n{code}\n```" - + if len(code_block) > 1990: interaction = getattr(context, "interaction", None) if interaction is not None: @@ -411,19 +480,21 @@ def codepreview_command(): await context.channel.send(embed=embed) else: await context.channel.send(embed=embed) - + max_code_length = 1980 - len(language) - 8 - code_lines = code.split('\n') + code_lines = code.split("\n") current_chunk = [] current_length = 0 - + for line in code_lines: line_length = len(line) + 1 if current_length + line_length > max_code_length: remaining_lines = len(code_lines) - len(current_chunk) if remaining_lines > 0: - current_chunk.append(f"\n... ({remaining_lines} more lines omitted)") - chunk_text = '\n'.join(current_chunk) + current_chunk.append( + f"\n... ({remaining_lines} more lines omitted)" + ) + chunk_text = "\n".join(current_chunk) await context.channel.send(f"```{language}\n{chunk_text}\n```") break else: @@ -431,9 +502,9 @@ def codepreview_command(): current_length += line_length else: if current_chunk: - chunk_text = '\n'.join(current_chunk) + chunk_text = "\n".join(current_chunk) await context.channel.send(f"```{language}\n{chunk_text}\n```") - + if interaction is not None: try: await interaction.delete_original_response() @@ -454,4 +525,4 @@ def codepreview_command(): await context.channel.send(embed=embed) await context.channel.send(code_block) - return codepreview \ No newline at end of file + return codepreview diff --git a/cogs/utilities/dictionary.py b/cogs/utilities/dictionary.py index 02e0e11..feaf37a 100644 --- a/cogs/utilities/dictionary.py +++ b/cogs/utilities/dictionary.py @@ -6,21 +6,24 @@ import aiohttp def dictionary_command(): - - async def send_embed(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(): await interaction.followup.send(embed=embed, ephemeral=ephemeral) else: - await interaction.response.send_message(embed=embed, ephemeral=ephemeral) + await interaction.response.send_message( + embed=embed, ephemeral=ephemeral + ) else: await context.send(embed=embed) async def fetch_definition(word: str) -> dict: try: url = f"https://api.dictionaryapi.dev/api/v2/entries/en/{word.lower()}" - + async with aiohttp.ClientSession() as session: async with session.get(url) as response: if response.status == 200: @@ -29,7 +32,10 @@ def dictionary_command(): elif response.status == 404: return {"success": False, "error": "Word not found"} else: - return {"success": False, "error": f"API returned status code {response.status}"} + return { + "success": False, + "error": f"API returned status code {response.status}", + } except aiohttp.ClientError: return {"success": False, "error": "Network error occurred"} except Exception as e: @@ -39,21 +45,26 @@ def dictionary_command(): name="dictionary", description="Get the definition of a word", ) - @app_commands.describe( - word="The word to look up" - ) + @app_commands.describe(word="The word to look up") async def dictionary(self, context, word: str = None): if not word or not word.strip(): - if context.message and context.message.reference and context.message.reference.resolved: + if ( + context.message + and context.message.reference + and context.message.reference.resolved + ): replied_message = context.message.reference.resolved - if hasattr(replied_message, 'content') and replied_message.content: + if hasattr(replied_message, "content") and replied_message.content: word = replied_message.content.strip().split()[0] else: embed = discord.Embed( title="Error", description="The replied message has no text content to look up.", color=0xE02B2B, - ).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") + ).set_author( + name="Utility", + icon_url="https://yes.nighty.works/raw/8VLDcg.webp", + ) await send_embed(context, embed, ephemeral=True) return else: @@ -61,110 +72,125 @@ def dictionary_command(): title="Error", description="Please provide a word to look up or reply to a message with a word.", color=0xE02B2B, - ).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") + ).set_author( + name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp" + ) await send_embed(context, embed, ephemeral=True) return - + word = word.strip().split()[0] - + interaction = getattr(context, "interaction", None) if interaction is not None and not interaction.response.is_done(): await interaction.response.defer() - + result = await fetch_definition(word) - + if not result["success"]: error_message = result.get("error", "Unknown error") - + if error_message == "Word not found": description = f"Could not find a definition for **{word}**.\nPlease check the spelling and try again." else: description = f"Failed to fetch definition: {error_message}" - + embed = discord.Embed( title="Error", description=description, color=0xE02B2B, - ).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") + ).set_author( + name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp" + ) await send_embed(context, embed, ephemeral=True) return - + data = result["data"] - + if not data or len(data) == 0: embed = discord.Embed( title="Error", description=f"No definition found for **{word}**.", color=0xE02B2B, - ).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") + ).set_author( + name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp" + ) await send_embed(context, embed, ephemeral=True) return - + entry = data[0] word_title = entry.get("word", word) phonetic = entry.get("phonetic", "") origin = entry.get("origin", "") meanings = entry.get("meanings", []) - + embed = discord.Embed( title=f"Dictionary", description=f"**```{word_title}```**", color=0x7289DA, ) - embed.set_author(name="Utilities", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") - + embed.set_author( + name="Utilities", icon_url="https://yes.nighty.works/raw/8VLDcg.webp" + ) + if phonetic: embed.add_field(name="Pronunciation", value=f"`{phonetic}`", inline=False) - + max_meanings = 3 for idx, meaning in enumerate(meanings[:max_meanings]): part_of_speech = meaning.get("partOfSpeech", "").capitalize() definitions = meaning.get("definitions", []) - + if definitions: def_text = "" examples = [] - + for def_idx, definition in enumerate(definitions[:2], 1): def_line = definition.get("definition", "") example = definition.get("example", "") - + if def_line: def_text += f"{def_idx}. {def_line}\n" if example: examples.append(f"{def_idx}. {example}") - + if def_text: - field_name = f"{part_of_speech}" if part_of_speech else f"Definition {idx + 1}" - embed.add_field(name=field_name, value=def_text.strip(), inline=False) - + field_name = ( + f"{part_of_speech}" + if part_of_speech + else f"Definition {idx + 1}" + ) + embed.add_field( + name=field_name, value=def_text.strip(), inline=False + ) + if examples: example_text = "\n".join(examples) - embed.add_field(name="Examples", value=example_text, inline=False) - + embed.add_field( + name="Examples", value=example_text, inline=False + ) + if idx < len(meanings[:max_meanings]) - 1: embed.add_field(name="โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€", value="", inline=False) - + if origin and len(origin) < 1000: embed.add_field(name="Origin", value=origin, inline=False) - + synonyms = [] antonyms = [] - + for meaning in meanings: for definition in meaning.get("definitions", []): synonyms.extend(definition.get("synonyms", [])) antonyms.extend(definition.get("antonyms", [])) - + if synonyms: synonym_text = ", ".join(synonyms[:10]) embed.add_field(name="Synonyms", value=synonym_text, inline=True) - + if antonyms: antonym_text = ", ".join(antonyms[:10]) embed.add_field(name="Antonyms", value=antonym_text, inline=True) - await send_embed(context, embed) return dictionary diff --git a/cogs/utilities/translate.py b/cogs/utilities/translate.py index 20631b9..ecf30e4 100644 --- a/cogs/utilities/translate.py +++ b/cogs/utilities/translate.py @@ -10,164 +10,173 @@ import urllib.parse languages = { - "auto": "Auto-detect", - "en": "English", - "es": "Spanish", - "fr": "French", - "de": "German", - "it": "Italian", - "pt": "Portuguese", - "ru": "Russian", - "ja": "Japanese", - "ko": "Korean", - "zh-CN": "Chinese (Simplified)", - "zh-TW": "Chinese (Traditional)", - "ar": "Arabic", - "hi": "Hindi", - "th": "Thai", - "vi": "Vietnamese", - "nl": "Dutch", - "pl": "Polish", - "tr": "Turkish", - "sv": "Swedish", - "da": "Danish", - "no": "Norwegian", - "fi": "Finnish", - "cs": "Czech", - "sk": "Slovak", - "hu": "Hungarian", - "ro": "Romanian", - "bg": "Bulgarian", - "hr": "Croatian", - "sr": "Serbian", - "sl": "Slovenian", - "et": "Estonian", - "lv": "Latvian", - "lt": "Lithuanian", - "uk": "Ukrainian", - "be": "Belarusian", - "mk": "Macedonian", - "sq": "Albanian", - "mt": "Maltese", - "is": "Icelandic", - "ga": "Irish", - "cy": "Welsh", - "gd": "Scots Gaelic", - "eu": "Basque", - "ca": "Catalan", - "gl": "Galician", - "eo": "Esperanto", - "la": "Latin", - "af": "Afrikaans", - "sw": "Swahili", - "zu": "Zulu", - "xh": "Xhosa", - "yo": "Yoruba", - "ig": "Igbo", - "ha": "Hausa", - "am": "Amharic", - "om": "Oromo", - "ti": "Tigrinya", - "so": "Somali", - "rw": "Kinyarwanda", - "lg": "Ganda", - "ny": "Chichewa", - "sn": "Shona", - "st": "Sesotho", - "tn": "Tswana", - "ts": "Tsonga", - "ss": "Swati", - "nr": "Ndebele", - "nso": "Northern Sotho", - "ve": "Venda", - "bn": "Bengali", - "gu": "Gujarati", - "kn": "Kannada", - "ml": "Malayalam", - "mr": "Marathi", - "ne": "Nepali", - "or": "Odia", - "pa": "Punjabi", - "si": "Sinhala", - "ta": "Tamil", - "te": "Telugu", - "ur": "Urdu", - "as": "Assamese", - "bho": "Bhojpuri", - "doi": "Dogri", - "gom": "Konkani", - "mai": "Maithili", - "mni-Mtei": "Meiteilon", - "sa": "Sanskrit", - "id": "Indonesian", - "ms": "Malay", - "tl": "Filipino", - "jv": "Javanese", - "su": "Sundanese", - "ceb": "Cebuano", - "hil": "Hiligaynon", - "ilo": "Iloko", - "pam": "Kapampangan", - "war": "Waray", - "my": "Myanmar", - "km": "Khmer", - "lo": "Lao", - "ka": "Georgian", - "hy": "Armenian", - "az": "Azerbaijani", - "kk": "Kazakh", - "ky": "Kyrgyz", - "mn": "Mongolian", - "tk": "Turkmen", - "ug": "Uyghur", - "uz": "Uzbek", - "tg": "Tajik", - "fa": "Persian", - "ps": "Pashto", - "sd": "Sindhi", - "he": "Hebrew", - "yi": "Yiddish", - "iw": "Hebrew", - "el": "Greek", - "lt": "Lithuanian", - "lv": "Latvian", - } + "auto": "Auto-detect", + "en": "English", + "es": "Spanish", + "fr": "French", + "de": "German", + "it": "Italian", + "pt": "Portuguese", + "ru": "Russian", + "ja": "Japanese", + "ko": "Korean", + "zh-CN": "Chinese (Simplified)", + "zh-TW": "Chinese (Traditional)", + "ar": "Arabic", + "hi": "Hindi", + "th": "Thai", + "vi": "Vietnamese", + "nl": "Dutch", + "pl": "Polish", + "tr": "Turkish", + "sv": "Swedish", + "da": "Danish", + "no": "Norwegian", + "fi": "Finnish", + "cs": "Czech", + "sk": "Slovak", + "hu": "Hungarian", + "ro": "Romanian", + "bg": "Bulgarian", + "hr": "Croatian", + "sr": "Serbian", + "sl": "Slovenian", + "et": "Estonian", + "lv": "Latvian", + "lt": "Lithuanian", + "uk": "Ukrainian", + "be": "Belarusian", + "mk": "Macedonian", + "sq": "Albanian", + "mt": "Maltese", + "is": "Icelandic", + "ga": "Irish", + "cy": "Welsh", + "gd": "Scots Gaelic", + "eu": "Basque", + "ca": "Catalan", + "gl": "Galician", + "eo": "Esperanto", + "la": "Latin", + "af": "Afrikaans", + "sw": "Swahili", + "zu": "Zulu", + "xh": "Xhosa", + "yo": "Yoruba", + "ig": "Igbo", + "ha": "Hausa", + "am": "Amharic", + "om": "Oromo", + "ti": "Tigrinya", + "so": "Somali", + "rw": "Kinyarwanda", + "lg": "Ganda", + "ny": "Chichewa", + "sn": "Shona", + "st": "Sesotho", + "tn": "Tswana", + "ts": "Tsonga", + "ss": "Swati", + "nr": "Ndebele", + "nso": "Northern Sotho", + "ve": "Venda", + "bn": "Bengali", + "gu": "Gujarati", + "kn": "Kannada", + "ml": "Malayalam", + "mr": "Marathi", + "ne": "Nepali", + "or": "Odia", + "pa": "Punjabi", + "si": "Sinhala", + "ta": "Tamil", + "te": "Telugu", + "ur": "Urdu", + "as": "Assamese", + "bho": "Bhojpuri", + "doi": "Dogri", + "gom": "Konkani", + "mai": "Maithili", + "mni-Mtei": "Meiteilon", + "sa": "Sanskrit", + "id": "Indonesian", + "ms": "Malay", + "tl": "Filipino", + "jv": "Javanese", + "su": "Sundanese", + "ceb": "Cebuano", + "hil": "Hiligaynon", + "ilo": "Iloko", + "pam": "Kapampangan", + "war": "Waray", + "my": "Myanmar", + "km": "Khmer", + "lo": "Lao", + "ka": "Georgian", + "hy": "Armenian", + "az": "Azerbaijani", + "kk": "Kazakh", + "ky": "Kyrgyz", + "mn": "Mongolian", + "tk": "Turkmen", + "ug": "Uyghur", + "uz": "Uzbek", + "tg": "Tajik", + "fa": "Persian", + "ps": "Pashto", + "sd": "Sindhi", + "he": "Hebrew", + "yi": "Yiddish", + "iw": "Hebrew", + "el": "Greek", + "lt": "Lithuanian", + "lv": "Latvian", +} -async def language_autocomplete(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 languages.items(): if current in code.lower() or current in name.lower(): display_name = f"{code} - {name}" if len(display_name) > 100: display_name = f"{code} - {name[:90]}..." choices.append(app_commands.Choice(name=display_name, value=code)) - + if len(choices) >= 25: break - + if not choices: popular = ["en", "es", "fr", "de", "it", "pt", "ru", "ja", "ko", "zh-CN"] for code in popular: name = languages.get(code, code) choices.append(app_commands.Choice(name=f"{code} - {name}", value=code)) - + return choices -def translate_command(): - async def send_embed(context, embed: discord.Embed, *, ephemeral: bool = False) -> None: +def translate_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(): await interaction.followup.send(embed=embed, ephemeral=ephemeral) else: - await interaction.response.send_message(embed=embed, ephemeral=ephemeral) + await interaction.response.send_message( + embed=embed, ephemeral=ephemeral + ) else: await context.send(embed=embed) - async def _translate_with_google_web(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" @@ -208,9 +217,11 @@ def translate_command(): "sl": from_lang, "tl": to_lang, "dt": ["t", "bd"], - "q": chunk + "q": chunk, } - async with session.get(base_url, headers=headers, params=params) as response: + async with session.get( + base_url, headers=headers, params=params + ) as response: if response.status != 200: return None result_text = (await response.text()).strip() @@ -232,12 +243,11 @@ def translate_command(): return { "translatedText": "".join(translated_parts).strip(), - "detectedSourceLanguage": detected_lang_overall + "detectedSourceLanguage": detected_lang_overall, } except Exception: return None - @commands.hybrid_command( name="translate", description="Translate text to another language", @@ -245,22 +255,31 @@ def translate_command(): @app_commands.describe( text="The text to translate", to_lang="Target language (e.g., 'en', 'es', 'fr')", - from_lang="Source language (leave empty for auto-detect)" + from_lang="Source language (leave empty for auto-detect)", ) @app_commands.autocomplete(to_lang=language_autocomplete) @app_commands.autocomplete(from_lang=language_autocomplete) - async def translate(self, 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: + if ( + context.message + and context.message.reference + and context.message.reference.resolved + ): replied_message = context.message.reference.resolved - if hasattr(replied_message, 'content') and replied_message.content: + if hasattr(replied_message, "content") and replied_message.content: text = replied_message.content else: embed = discord.Embed( title="Error", 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") + ).set_author( + name="Utility", + icon_url="https://yes.nighty.works/raw/8VLDcg.webp", + ) await send_embed(context, embed, ephemeral=True) return else: @@ -268,55 +287,61 @@ def translate_command(): title="Error", 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") + ).set_author( + name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp" + ) await send_embed(context, embed, ephemeral=True) return - + 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") + ).set_author( + name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp" + ) await send_embed(context, embed, ephemeral=True) return - + 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") + ).set_author( + name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp" + ) await send_embed(context, embed, ephemeral=True) return - + 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 = languages.get(detected_lang, detected_lang) to_lang_name = languages.get(to_lang, to_lang) - + embed = discord.Embed( title="Translation", description=f"**Original:** {text}\n\n**Translated:** {result['translatedText']}", color=0x7289DA, ) - embed.set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp") + 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 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") + ).set_author( + name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp" + ) await send_embed(context, embed, ephemeral=True) - - - - - return translate \ No newline at end of file + return translate diff --git a/requirements.txt b/requirements.txt index 5f857c8..4d6eb73 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ yt-dlp Pillow pillow-heif pytz -gTTS \ No newline at end of file +gTTS +ruff \ No newline at end of file diff --git a/utils/__init__.py b/utils/__init__.py index 9ac8be5..7d3323c 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -5,7 +5,13 @@ from .signal import setup_signal_handlers from .contributors import generate_contributors_image __all__ = [ - 'ascii', 'ascii_plain', 'gradient_text', 'gradient_text_selective', - 'LoggingFormatter', 'setup_logger', 'get_uptime', 'setup_signal_handlers', - 'generate_contributors_image' -] \ No newline at end of file + "ascii", + "ascii_plain", + "gradient_text", + "gradient_text_selective", + "LoggingFormatter", + "setup_logger", + "get_uptime", + "setup_signal_handlers", + "generate_contributors_image", +] diff --git a/utils/ascii_art.py b/utils/ascii_art.py index 204c6df..365c9be 100644 --- a/utils/ascii_art.py +++ b/utils/ascii_art.py @@ -3,9 +3,11 @@ import math + def gradient_text(text, start_color, end_color): def rgb_interp(start, end, t): return tuple(int(start[i] + (end[i] - start[i]) * t) for i in range(3)) + lines = text.splitlines() gradient_lines = [] total_chars = sum(len(line) for line in lines if line.strip()) @@ -23,24 +25,27 @@ def gradient_text(text, start_color, end_color): gradient_lines.append(colored_line) return "\n".join(gradient_lines) -def gradient_text_selective(text, start_color, end_color, gradient_word, white_prefix=""): + +def gradient_text_selective( + text, start_color, end_color, gradient_word, white_prefix="" +): def rgb_interp(start, end, t): return tuple(int(start[i] + (end[i] - start[i]) * t) for i in range(3)) - + lines = text.splitlines() result_lines = [] - + for line in lines: if gradient_word in line and white_prefix in line: prefix_start = line.find(white_prefix) word_start = line.find(gradient_word) - + if prefix_start != -1 and word_start != -1: before_prefix = line[:prefix_start] prefix_part = line[prefix_start:word_start] word_part = gradient_word - after_word = line[word_start + len(gradient_word):] - + after_word = line[word_start + len(gradient_word) :] + colored_before = "" total_chars = sum(1 for char in before_prefix if char.strip()) idx = 0 @@ -52,9 +57,9 @@ def gradient_text_selective(text, start_color, end_color, gradient_word, white_p idx += 1 else: colored_before += char - + white_prefix_colored = f"\033[38;2;255;255;255m{prefix_part}\033[0m" - + colored_word = "" word_chars = [char for char in word_part if char.strip()] for i, char in enumerate(word_part): @@ -64,8 +69,10 @@ def gradient_text_selective(text, start_color, end_color, gradient_word, white_p colored_word += f"\033[38;2;{r};{g};{b}m{char}\033[0m" else: colored_word += char - - result_lines.append(colored_before + white_prefix_colored + colored_word + after_word) + + result_lines.append( + colored_before + white_prefix_colored + colored_word + after_word + ) else: colored_line = "" total_chars = sum(1 for char in line if char.strip()) @@ -92,7 +99,7 @@ def gradient_text_selective(text, start_color, end_color, gradient_word, white_p else: colored_line += char result_lines.append(colored_line) - + return "\n".join(result_lines) @@ -105,8 +112,8 @@ _ascii_art = """ โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• """ -_start_rgb = (88, 101, 242) -_end_rgb = (114, 137, 218) +_start_rgb = (88, 101, 242) +_end_rgb = (114, 137, 218) ascii = gradient_text_selective(_ascii_art, _start_rgb, _end_rgb, "neoarz", "Made by ") diff --git a/utils/checks.py b/utils/checks.py index 4b57965..1bdbc29 100644 --- a/utils/checks.py +++ b/utils/checks.py @@ -1,15 +1,17 @@ import os from discord.ext import commands + def get_owner_friend_ids(): owner_friends = os.getenv("OWNER_FRIENDS", "") if not owner_friends.strip(): return [] return [int(id.strip()) for id in owner_friends.split(",") if id.strip().isdigit()] + def is_owner_or_friend(): async def predicate(ctx): owner_friend_ids = get_owner_friend_ids() return ctx.author.id in owner_friend_ids or await ctx.bot.is_owner(ctx.author) - return commands.check(predicate) + return commands.check(predicate) diff --git a/utils/contributors.py b/utils/contributors.py index b47d34d..d9b97f3 100644 --- a/utils/contributors.py +++ b/utils/contributors.py @@ -8,32 +8,32 @@ from PIL import Image def fetch_contributors(owner, repo): contributors = [] page = 1 - + while True: url = f"https://api.github.com/repos/{owner}/{repo}/contributors" params = {"page": page, "per_page": 100} - + headers = { "User-Agent": "github-contributors-graph", - "Accept": "application/vnd.github.v3+json" + "Accept": "application/vnd.github.v3+json", } - + response = requests.get(url, headers=headers, params=params) - + if not response.ok: return [] - + data = response.json() - + if not data: break - + contributors.extend(data) page += 1 - - if response.headers.get('X-RateLimit-Remaining') == '0': + + if response.headers.get("X-RateLimit-Remaining") == "0": break - + return contributors @@ -41,65 +41,66 @@ def download_avatar(avatar_url, size): try: if "avatars.githubusercontent.com" in avatar_url: avatar_url = f"{avatar_url}?s={size}" - + response = requests.get(avatar_url, timeout=10) - + if not response.ok: return None - + img = Image.open(BytesIO(response.content)) - + if img.size != (size, size): img = img.resize((size, size), Image.Resampling.LANCZOS) - + return img - + except Exception: return None -def generate_contributors_image(owner="neoarz", repo="syntrel", size=64, images_per_row=20): +def generate_contributors_image( + owner="neoarz", repo="syntrel", size=64, images_per_row=20 +): contributors = fetch_contributors(owner, repo) - + if not contributors: return None - + images = [] - + for contributor in contributors: avatar_url = contributor.get("avatar_url") - + if not avatar_url: continue - + img = download_avatar(avatar_url, size) - + if img is None: continue - + images.append(img) - + if not images: return None - + actual_images_per_row = min(len(images), images_per_row) width = size * actual_images_per_row row_count = ceil(len(images) / images_per_row) height = size * row_count - + canvas = Image.new("RGB", (width, height), color="black") - + for idx, img in enumerate(images): col = idx % images_per_row row = idx // images_per_row x = col * size y = row * size - + canvas.paste(img, (x, y)) - + buffer = BytesIO() canvas.save(buffer, "PNG") buffer.seek(0) - - return buffer + return buffer diff --git a/utils/logging.py b/utils/logging.py index c5c86ec..4b5b444 100644 --- a/utils/logging.py +++ b/utils/logging.py @@ -37,12 +37,14 @@ def setup_logger(): console_handler = logging.StreamHandler() console_handler.setFormatter(LoggingFormatter()) - + log_file_path = os.getenv("LOG_FILE", "logs/discord.log") log_dir = os.path.dirname(log_file_path) if log_dir: os.makedirs(log_dir, exist_ok=True) - file_handler = logging.FileHandler(filename=log_file_path, encoding="utf-8", mode="w") + file_handler = logging.FileHandler( + filename=log_file_path, encoding="utf-8", mode="w" + ) file_handler_formatter = logging.Formatter( "[{asctime}] [{levelname:<8}] {name}: {message}", "%Y-%m-%d %H:%M:%S", style="{" ) @@ -50,5 +52,5 @@ def setup_logger(): logger.addHandler(console_handler) logger.addHandler(file_handler) - + return logger diff --git a/utils/signal.py b/utils/signal.py index 678f1f8..09fb521 100644 --- a/utils/signal.py +++ b/utils/signal.py @@ -8,6 +8,6 @@ def setup_signal_handlers(bot): if bot.loop and not bot.loop.is_closed(): asyncio.create_task(bot.close()) bot.loop.call_soon_threadsafe(bot.loop.stop) - + signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) diff --git a/utils/time.py b/utils/time.py index 51adcdc..78352c3 100644 --- a/utils/time.py +++ b/utils/time.py @@ -7,7 +7,7 @@ def get_uptime(start_time: float) -> str: hours = (uptime_seconds % 86400) // 3600 minutes = (uptime_seconds % 3600) // 60 seconds = uptime_seconds % 60 - + if days > 0: return f"{days}d {hours}h {minutes}m {seconds}s" elif hours > 0: