diff --git a/suggestions/bot.py b/suggestions/bot.py index a43c6ec..d900698 100644 --- a/suggestions/bot.py +++ b/suggestions/bot.py @@ -58,7 +58,7 @@ class SuggestionsBot(commands.AutoShardedInteractionBot): def __init__(self, *args, **kwargs): - self.version: str = "Public Release 3.25" + self.version: str = "Public Release 3.26" self.main_guild_id: int = 601219766258106399 self.legacy_beta_role_id: int = 995588041991274547 self.automated_beta_role_id: int = 998173237282361425 @@ -300,6 +300,7 @@ async def on_slash_command_error( "author_id": error.user_id, "guild_id": error.guild_id, "traceback": commons.exception_as_string(exception), + "error_code": ErrorCode.UNHANDLED_ERROR.value, }, ) return await interaction.send( @@ -320,17 +321,33 @@ async def on_slash_command_error( await self.db.error_tracking.update(error, error) if attempt_code == ErrorCode.MISSING_FETCH_PERMISSIONS_IN_SUGGESTIONS_CHANNEL: + logger.debug( + "MISSING_FETCH_PERMISSIONS_IN_SUGGESTIONS_CHANNEL", + extra_metadata={ + "guild_id": interaction.guild_id, + "author_id": interaction.author.id, + "error_code": attempt_code.value, + }, + ) return await interaction.send( embed=self.error_embed( "Configuration Error", "I do not have permission to use your guilds configured suggestions channel.", - error_code=attempt_code, + error_code=attempt_code.value, error=error, ), ephemeral=True, ) elif attempt_code == ErrorCode.MISSING_FETCH_PERMISSIONS_IN_LOGS_CHANNEL: + logger.debug( + "MISSING_FETCH_PERMISSIONS_IN_LOGS_CHANNEL", + extra_metadata={ + "guild_id": interaction.guild_id, + "author_id": interaction.author.id, + "error_code": attempt_code.value, + }, + ) return await interaction.send( embed=self.error_embed( "Configuration Error", @@ -342,6 +359,14 @@ async def on_slash_command_error( ) elif attempt_code == ErrorCode.MISSING_SEND_PERMISSIONS_IN_SUGGESTION_CHANNEL: + logger.debug( + "MISSING_SEND_PERMISSIONS_IN_SUGGESTION_CHANNEL", + extra_metadata={ + "guild_id": interaction.guild_id, + "author_id": interaction.author.id, + "error_code": attempt_code.value, + }, + ) return await interaction.send( embed=self.error_embed( "Configuration Error", @@ -353,6 +378,13 @@ async def on_slash_command_error( ) if isinstance(exception, BetaOnly): + logger.critical( + "BetaOnly", + extra_metadata={ + "guild_id": interaction.guild_id, + "author_id": interaction.author.id, + }, + ) embed: disnake.Embed = disnake.Embed( title="Beta restrictions", description="This command is restricted to beta guilds only, " @@ -362,6 +394,14 @@ async def on_slash_command_error( return await interaction.send(embed=embed, ephemeral=True) elif isinstance(exception, MissingSuggestionsChannel): + logger.debug( + "MissingSuggestionsChannel", + extra_metadata={ + "guild_id": interaction.guild_id, + "author_id": interaction.author.id, + "error_code": ErrorCode.MISSING_SUGGESTIONS_CHANNEL.value, + }, + ) return await interaction.send( embed=self.error_embed( "Missing Suggestions Channel", @@ -375,6 +415,14 @@ async def on_slash_command_error( ) elif isinstance(exception, MissingLogsChannel): + logger.debug( + "MissingLogsChannel", + extra_metadata={ + "guild_id": interaction.guild_id, + "author_id": interaction.author.id, + "error_code": ErrorCode.MISSING_LOG_CHANNEL.value, + }, + ) return await interaction.send( embed=self.error_embed( "Missing Logs Channel", @@ -388,6 +436,14 @@ async def on_slash_command_error( ) elif isinstance(exception, MissingQueueLogsChannel): + logger.debug( + "MissingQueueLogsChannel", + extra_metadata={ + "guild_id": interaction.guild_id, + "author_id": interaction.author.id, + "error_code": ErrorCode.MISSING_QUEUE_LOG_CHANNEL.value, + }, + ) return await interaction.send( embed=self.error_embed( "Missing Queue Logs Channel", @@ -401,6 +457,14 @@ async def on_slash_command_error( ) elif isinstance(exception, MissingPermissionsToAccessQueueChannel): + logger.debug( + "MissingPermissionsToAccessQueueChannel", + extra_metadata={ + "guild_id": interaction.guild_id, + "author_id": interaction.author.id, + "error_code": ErrorCode.MISSING_PERMISSIONS_IN_QUEUE_CHANNEL.value, + }, + ) return await interaction.send( embed=self.error_embed( title="Missing permissions within queue logs channel", @@ -419,6 +483,7 @@ async def on_slash_command_error( "guild_id": interaction.guild_id, "suggestion_id": exception.suggestion_id, "author_id": interaction.author.id, + "error_code": ErrorCode.SUGGESTION_NOT_FOUND.value, }, ) return await interaction.send( @@ -433,6 +498,15 @@ async def on_slash_command_error( elif isinstance(exception, commands.MissingPermissions): perms = ",".join(i for i in exception.missing_permissions) + logger.debug( + "commands.MissingPermissions: %s", + perms, + extra_metadata={ + "guild_id": interaction.guild_id, + "author_id": interaction.author.id, + "error_code": ErrorCode.MISSING_PERMISSIONS.value, + }, + ) return await interaction.send( embed=self.error_embed( "Missing Permissions", @@ -445,6 +519,14 @@ async def on_slash_command_error( ) elif isinstance(exception, SuggestionNotFound): + logger.debug( + "SuggestionNotFound", + extra_metadata={ + "guild_id": interaction.guild_id, + "author_id": interaction.author.id, + "error_code": ErrorCode.SUGGESTION_NOT_FOUND.value, + }, + ) return await interaction.send( embed=self.error_embed( "Command failed", @@ -456,6 +538,14 @@ async def on_slash_command_error( ) elif isinstance(exception, SuggestionTooLong): + logger.debug( + "SuggestionTooLong", + extra_metadata={ + "guild_id": interaction.guild_id, + "author_id": interaction.author.id, + "error_code": ErrorCode.SUGGESTION_CONTENT_TOO_LONG.value, + }, + ) return await interaction.send( embed=self.error_embed( "Command failed", @@ -471,6 +561,14 @@ async def on_slash_command_error( ) elif isinstance(exception, InvalidGuildConfigOption): + logger.debug( + "InvalidGuildConfigOption", + extra_metadata={ + "guild_id": interaction.guild_id, + "author_id": interaction.author.id, + "error_code": ErrorCode.INVALID_GUILD_CONFIG_CHOICE.value, + }, + ) return await interaction.send( embed=self.error_embed( "Command failed", @@ -482,6 +580,14 @@ async def on_slash_command_error( ) elif isinstance(exception, CallableOnCooldown): + logger.debug( + "CallableOnCooldown", + extra_metadata={ + "guild_id": interaction.guild_id, + "author_id": interaction.author.id, + "error_code": ErrorCode.COMMAND_ON_COOLDOWN.value, + }, + ) return await interaction.send( embed=self.error_embed( "Command on Cooldown", @@ -493,6 +599,14 @@ async def on_slash_command_error( ) elif isinstance(exception, BlocklistedUser): + logger.debug( + "BlocklistedUser", + extra_metadata={ + "guild_id": interaction.guild_id, + "author_id": interaction.author.id, + "error_code": ErrorCode.BLOCKLISTED_USER.value, + }, + ) return await interaction.send( embed=self.error_embed( "Blocked Action", @@ -504,6 +618,14 @@ async def on_slash_command_error( ) elif isinstance(exception, InvalidFileType): + logger.debug( + "InvalidFileType", + extra_metadata={ + "guild_id": interaction.guild_id, + "author_id": interaction.author.id, + "error_code": ErrorCode.INVALID_FILE_TYPE.value, + }, + ) return await interaction.send( embed=self.error_embed( "Invalid file type", @@ -516,6 +638,14 @@ async def on_slash_command_error( ) elif isinstance(exception, ConfiguredChannelNoLongerExists): + logger.debug( + "ConfiguredChannelNoLongerExists", + extra_metadata={ + "guild_id": interaction.guild_id, + "author_id": interaction.author.id, + "error_code": ErrorCode.CONFIGURED_CHANNEL_NO_LONGER_EXISTS.value, + }, + ) return await interaction.send( embed=self.error_embed( "Configuration Error", @@ -530,6 +660,14 @@ async def on_slash_command_error( elif isinstance(exception, LocalizationKeyError): gid = interaction.guild_id if interaction.guild_id else None + logger.debug( + "LocalizationKeyError", + extra_metadata={ + "guild_id": gid, + "author_id": interaction.author.id, + "error_code": ErrorCode.MISSING_TRANSLATION.value, + }, + ) return await interaction.send( embed=self.error_embed( "Something went wrong", @@ -541,6 +679,14 @@ async def on_slash_command_error( ) elif isinstance(exception, QueueImbalance): + logger.debug( + "QueueImbalance", + extra_metadata={ + "guild_id": interaction.guild_id, + "author_id": interaction.author.id, + "error_code": ErrorCode.QUEUE_IMBALANCE.value, + }, + ) return await interaction.send( embed=self.error_embed( "Queue Imbalance", @@ -559,6 +705,8 @@ async def on_slash_command_error( extra_metadata={ "guild_id": interaction.guild_id, "author_id": interaction.author.id, + "traceback": commons.exception_as_string(exception), + "error_code": ErrorCode.GENERIC_NOT_FOUND.value, }, ) gid = interaction.guild_id if interaction.guild_id else None @@ -567,7 +715,7 @@ async def on_slash_command_error( "Command failed", "I've failed to find something, please retry whatever you were doing.\n" f"If this error persists please contact support.\n\nGuild ID: `{gid}`", - error_code=ErrorCode.GENERIC_NOT_FOUND, + error_code=ErrorCode.GENERIC_NOT_FOUND.value, error=error, ), ephemeral=True, @@ -582,6 +730,7 @@ async def on_slash_command_error( extra_metadata={ "guild_id": interaction.guild_id, "author_id": interaction.author.id, + "error_code": ErrorCode.GENERIC_FORBIDDEN.value, }, ) await interaction.send( @@ -597,6 +746,14 @@ async def on_slash_command_error( raise exception elif isinstance(exception, commands.NotOwner): + logger.debug( + "commands.NotOwner", + extra_metadata={ + "guild_id": interaction.guild_id, + "author_id": interaction.author.id, + "error_code": ErrorCode.OWNER_ONLY.value, + }, + ) await interaction.send( embed=self.error_embed( "Command failed", @@ -614,7 +771,11 @@ async def on_slash_command_error( "disnake.HTTPException: Interaction has already been acknowledged" ) logger.debug( - "disnake.HTTPException: Interaction has already been acknowledged" + "disnake.HTTPException: Interaction has already been acknowledged", + extra_metadata={ + "guild_id": interaction.guild_id, + "author_id": interaction.author.id, + }, ) return @@ -622,6 +783,14 @@ async def on_slash_command_error( interaction.id, self ) if ih is not None and not ih.has_sent_something: + logger.critical( + "Interaction was never ack'd", + extra_metadata={ + "guild_id": interaction.guild_id, + "author_id": interaction.author.id, + "error_code": ErrorCode.UNHANDLED_ERROR.value, + }, + ) gid = interaction.guild_id if interaction.guild_id else None # Fix "Bot is thinking" hanging on edge cases... await interaction.send( diff --git a/suggestions/core/suggestion_notes.py b/suggestions/core/suggestion_notes.py index d4d363b..d8d0855 100644 --- a/suggestions/core/suggestion_notes.py +++ b/suggestions/core/suggestion_notes.py @@ -30,6 +30,8 @@ async def modify_note_on_suggestions( # We should now update the suggestions message await suggestion.edit_suggestion_message(ih) + await ih.send(ih.bot.get_localized_string("NOTE_INNER_RESPONSE", ih)) + # We should tell the user a change has occurred suggestion_author_id: int = suggestion.suggestion_author_id user_config: UserConfig = await UserConfig.from_id( @@ -92,5 +94,3 @@ async def modify_note_on_suggestions( embed.set_author(name=guild.name, icon_url=icon_url) user: disnake.User = await ih.bot.fetch_user(suggestion_author_id) await user.send(embed=embed) - - await ih.send(ih.bot.get_localized_string("NOTE_INNER_RESPONSE", ih)) diff --git a/suggestions/core/suggestions_queue.py b/suggestions/core/suggestions_queue.py index 04e52f9..2570f49 100644 --- a/suggestions/core/suggestions_queue.py +++ b/suggestions/core/suggestions_queue.py @@ -148,28 +148,26 @@ async def resolve_queued_suggestion( ) icon_url = await self.bot.try_fetch_icon_url(guild_id) guild = self.state.guild_cache.get_entry(guild_id) - if ( + if not ( user_config.dm_messages_disabled or guild_config.dm_messages_disabled ): - # Set up not to message users - return - - embed: disnake.Embed = disnake.Embed( - description=self.bot.get_localized_string( - "QUEUE_INNER_USER_REJECTED", ih - ), - colour=self.bot.colors.embed_color, - timestamp=self.state.now, - ) - embed.set_author( - name=guild.name, - icon_url=icon_url, - ) - embed.set_footer(text=f"Guild ID {guild_id}") - await user.send( - embeds=[embed, await queued_suggestion.as_embed(self.bot)] - ) + # Set up to message users + embed: disnake.Embed = disnake.Embed( + description=self.bot.get_localized_string( + "QUEUE_INNER_USER_REJECTED", ih + ), + colour=self.bot.colors.embed_color, + timestamp=self.state.now, + ) + embed.set_author( + name=guild.name, + icon_url=icon_url, + ) + embed.set_footer(text=f"Guild ID {guild_id}") + await user.send( + embeds=[embed, await queued_suggestion.as_embed(self.bot)] + ) except: # Don't remove from queue on failure if suggestion is not None: diff --git a/suggestions/objects/queued_suggestion.py b/suggestions/objects/queued_suggestion.py index 0cced03..4c6737e 100644 --- a/suggestions/objects/queued_suggestion.py +++ b/suggestions/objects/queued_suggestion.py @@ -263,7 +263,7 @@ async def as_embed(self, bot: SuggestionsBot) -> Embed: if self._id and isinstance(self._id, str): # If longer then 8 it's a database generated id # and shouldn't be considered for this purpose - id_section = f" ID {self._id}" + id_section = f" ID: {self._id}" embed.set_thumbnail(user.display_avatar) embed.set_footer( diff --git a/suggestions/objects/suggestion.py b/suggestions/objects/suggestion.py index bf98692..749b0f8 100644 --- a/suggestions/objects/suggestion.py +++ b/suggestions/objects/suggestion.py @@ -473,7 +473,7 @@ async def _as_resolved_embed( embed = Embed( description=f"{results}\n\n**Suggestion**\n{self.suggestion}\n\n" f"**Submitter**\n{submitter}\n\n" - f"**{text} By**\n{resolved_by_text}\n\n", + f"**{text} By**\n{resolved_by_text}", colour=self.color, timestamp=bot.state.now, ) @@ -492,13 +492,13 @@ async def _as_resolved_embed( embed.set_author(name=guild.name, icon_url=icon_url) if self.resolution_note: - embed.description += f"**Response**\n{self.resolution_note}" + embed.description += f"\n\n**Response**\n{self.resolution_note}" if self.image_url: embed.set_image(self.image_url) if self.note: - note_desc = f"**Moderator note**\n{self.note}" + note_desc = f"\n\n**Moderator note**\n{self.note}" # TODO Resolve BT-44 and add moderator back embed.description += note_desc @@ -803,7 +803,12 @@ async def edit_message_after_finalization( await self.save_reaction_results(bot, interaction) # In place suggestion edit channel = await bot.get_or_fetch_channel(self.channel_id) - message: disnake.Message = await channel.fetch_message(self.message_id) + try: + message: disnake.Message = await channel.fetch_message(self.message_id) + except disnake.Forbidden: + raise SuggestionNotFound( + "Failed to find this suggestions message in order to resolve it." + ) try: await message.edit(embed=await self.as_embed(bot), components=None) @@ -873,8 +878,19 @@ async def archive_thread_if_required( # Don't hard crash so we can hopefully keep going return - channel = await bot.get_or_fetch_channel(self.channel_id) - message: disnake.Message = await channel.fetch_message(self.message_id) + try: + channel = await bot.get_or_fetch_channel(self.channel_id) + message: disnake.Message = await channel.fetch_message(self.message_id) + except disnake.NotFound: + # While not ideal, we ignore the error here as + # failing to archive a thread isn't a critical issue + # worth crashing on. Instead, pass this to the actual + # suggestion closing logic to handle more gracefully + # + # It'll likely still fail there but like, meh. Failing + # to find the thread here means technically the function worked + return + if not message.thread: # Suggestion has no created thread logger.debug(