From 47130fb6927ee49bb37a11df68d5bdb91cd83f4d Mon Sep 17 00:00:00 2001 From: Matsu Date: Sat, 31 Jan 2026 17:18:00 -0500 Subject: [PATCH 1/2] Enhance report.py to report content when reporting fails 1. Added _log_report_send_failure() helper method (~45 lines) - Logs failed report sends with full context (reporter, guild, channel, log channel, error) - Sends detailed notification to bot owners including the report content - Handles message truncation to fit within Discord's 2000 character limit - Includes proper error handling for owner notifications 2. Added _log_report_failure() helper method (~50 lines) - Logs failed reports from command error handlers - Extracts and logs report content when available - Sends comprehensive error notifications to bot owners - Handles cases where report content is unavailable 3. Updated on_cmd_report_error() method - Added report content extraction logic for unexpected errors - Attempts to extract from ctx.kwargs (for hybrid commands) or message content - Calls _log_report_failure() to preserve report content - Handles extraction failures gracefully 4. Updated on_cmd_emergency_error() method - Same enhancements as on_cmd_report_error() for emergency reports - Ensures emergency reports are never lost 5. Enhanced do_report() method - Wrapped log channel send operations in try-except block - Calls _log_report_send_failure() when sending fails - Re-raises exception so error handlers can process it - Preserves report content even when delivery fails --- report/report.py | 164 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 153 insertions(+), 11 deletions(-) diff --git a/report/report.py b/report/report.py index fae571cc..f203b5f8 100644 --- a/report/report.py +++ b/report/report.py @@ -169,11 +169,25 @@ async def on_cmd_report_error(self, ctx: commands.GuildContext, error): await self._send_cmd_error_response(ctx, error, f"You are on cooldown. Try again in ") return + # Unexpected error - extract and log report content logger.error(f"Unexpected error occurred: {error}") + + # Try to extract the report message from the command invocation + report_content = None try: - await ctx.bot.send_to_owners(error) - except Exception as e: - logger.error(f"Failed to send error to owners: {e}") + # For hybrid commands, the message parameter is passed as a keyword argument + if hasattr(ctx, "kwargs") and "message" in ctx.kwargs: + report_content = ctx.kwargs["message"] + # Fallback: try to extract from the message content + elif ctx.message and ctx.message.content: + # Remove the command prefix and command name to get the message + prefix_len = len(ctx.prefix) if ctx.prefix else 0 + command_len = len(ctx.invoked_with) if ctx.invoked_with else 0 + report_content = ctx.message.content[prefix_len + command_len :].strip() + except Exception as extract_error: + logger.warning(f"Failed to extract report content: {extract_error}") + + await self._log_report_failure(ctx, report_content, error, "report") @commands.hybrid_command("emergency") @commands.cooldown(1, 30.0, commands.BucketType.user) @@ -215,11 +229,25 @@ async def on_cmd_emergency_error(self, ctx: commands.GuildContext, error): await self._send_cmd_error_response(ctx, error, f"You are on cooldown. Try again in ") return + # Unexpected error - extract and log report content logger.error(f"Unexpected error occurred: {error}") + + # Try to extract the report message from the command invocation + report_content = None try: - await ctx.bot.send_to_owners(error) - except Exception as e: - logger.error(f"Failed to send error to owners: {e}") + # For hybrid commands, the message parameter is passed as a keyword argument + if hasattr(ctx, "kwargs") and "message" in ctx.kwargs: + report_content = ctx.kwargs["message"] + # Fallback: try to extract from the message content + elif ctx.message and ctx.message.content: + # Remove the command prefix and command name to get the message + prefix_len = len(ctx.prefix) if ctx.prefix else 0 + command_len = len(ctx.invoked_with) if ctx.invoked_with else 0 + report_content = ctx.message.content[prefix_len + command_len :].strip() + except Exception as extract_error: + logger.warning(f"Failed to extract report content: {extract_error}") + + await self._log_report_failure(ctx, report_content, error, "emergency") async def get_log_channel(self, guild: discord.Guild) -> TextLikeChannel | None: """Gets the log channel for the guild""" @@ -335,11 +363,17 @@ async def do_report( embed = await self.make_report_embed(channel, message, report_body, emergency) logger.info(f"Sending {report_type} report to log channel {log_channel.name} ({log_channel.id})") - if isinstance(channel, TextLikeChannel) and emergency: - await self.send_emergency_report(log_channel, embed) - else: - # Not an emergency or not a text-like channel (maybe can't retrieve members), just send the embed - await log_channel.send(embed=embed) + try: + if isinstance(channel, TextLikeChannel) and emergency: + await self.send_emergency_report(log_channel, embed) + else: + # Not an emergency or not a text-like channel (maybe can't retrieve members), just send the embed + await log_channel.send(embed=embed) + except Exception as send_error: + # Log the failure with full report content + await self._log_report_send_failure(channel, message, report_body, log_channel, send_error, report_type) + # Re-raise the exception so the user knows something went wrong + raise # Notify user if their report was truncated if was_truncated: @@ -452,6 +486,114 @@ async def notify_guild_owner_config_error(self, channel: GuildChannelOrThread, m except discord.Forbidden: logger.error("Failed to send config error notification to guild owner - no DM permissions") + async def _log_report_send_failure( # noqa: PLR0913 + self, + channel: GuildChannelOrThread, + message: discord.Message, + report_body: str, + log_channel: TextLikeChannel, + error: Exception, + report_type: str, + ): + """ + Log failed report send with full context and notify bot owners. + + Parameters: + channel (GuildChannelOrThread): The channel where the report originated + message (discord.Message): The original message + report_body (str): The report content + log_channel (TextLikeChannel): The log channel where send failed + error (Exception): The error that occurred + report_type (str): Type of report ("regular" or "emergency") + """ + # Log to console with full context + logger.error(f"Failed to send {report_type} report to log channel {log_channel.name} ({log_channel.id}): {error}") + logger.error(f"Report content ({len(report_body)} chars): {report_body}") + + # Prepare message for bot owners + owner_msg = ( + f"⚠️ **Failed to Send {report_type.capitalize()} Report**\n\n" + f"**Reporter:** {message.author.mention} ({message.author.name}, {message.author.id})\n" + f"**Guild:** {channel.guild.name} ({channel.guild.id})\n" + f"**Channel:** #{channel.name} ({channel.id})\n" + f"**Log Channel:** {log_channel.name} ({log_channel.id})\n" + f"**Error:** {type(error).__name__}: {error}\n" + ) + + # Add report content if it fits + report_section = f"\n**Report Content:**\n{report_body}" + if len(owner_msg + report_section) <= MESSAGE_BODY_LIMIT: + owner_msg += report_section + else: + # Truncate to fit + max_report_length = MESSAGE_BODY_LIMIT - len(owner_msg) - len("\n**Report Content:**\n...") + if max_report_length > 0: + truncated = report_body[:max_report_length] + owner_msg += f"\n**Report Content:**\n{truncated}..." + logger.warning(f"Truncated report content for owner notification ({len(truncated)} chars)") + + try: + await self.bot.send_to_owners(owner_msg) + logger.info("Successfully notified bot owners about failed report send") + except Exception as owner_error: + logger.error(f"Failed to send error notification to bot owners: {owner_error}") + + async def _log_report_failure( + self, ctx: commands.GuildContext, report_content: str | None, error: Exception, report_type: str = "report" + ): + """ + Log failed report with full context and notify bot owners. + + Parameters: + ctx (commands.GuildContext): The command context + report_content (str | None): The report message content, if available + error (Exception): The error that occurred + report_type (str): Type of report ("report" or "emergency") + """ + # Log to console with full context + logger.error( + f"Failed to process {report_type} from {ctx.author.name} ({ctx.author.id}) " + f"in guild {ctx.guild.name} ({ctx.guild.id}) #{ctx.channel.name} ({ctx.channel.id})" + ) + logger.error(f"Error: {error}") + + if report_content: + logger.error(f"Report content ({len(report_content)} chars): {report_content}") + else: + logger.error("Report content: ") + + # Prepare message for bot owners + base_msg = ( + f"⚠️ **Failed {report_type.capitalize()} Report**\n\n" + f"**Reporter:** {ctx.author.mention} ({ctx.author.name}, {ctx.author.id})\n" + f"**Guild:** {ctx.guild.name} ({ctx.guild.id})\n" + f"**Channel:** #{ctx.channel.name} ({ctx.channel.id})\n" + f"**Error:** {type(error).__name__}: {error}\n" + ) + + # Add report content if available + if report_content: + report_section = f"\n**Report Content:**\n{report_content}" + # Check if the full message fits within Discord's limit + if len(base_msg + report_section) <= MESSAGE_BODY_LIMIT: + base_msg += report_section + else: + # Truncate report to fit + max_report_length = MESSAGE_BODY_LIMIT - len(base_msg) - len("\n**Report Content:**\n...") + if max_report_length > 0: + truncated = report_content[:max_report_length] + base_msg += f"\n**Report Content:**\n{truncated}..." + logger.warning(f"Truncated report content for owner notification ({len(truncated)} chars)") + else: + base_msg += "\n**Report Content:** " + + # Send to bot owners + try: + await ctx.bot.send_to_owners(base_msg) + logger.info("Successfully notified bot owners about failed report") + except Exception as e: + logger.error(f"Failed to send error notification to bot owners: {e}") + async def _send_cmd_error_response(self, ctx: commands.GuildContext, error, response: str): """ Sends a command error response to the user. From cd357c9e6a58d7c84a4d787f20912e3015117bea Mon Sep 17 00:00:00 2001 From: Matsu Date: Sat, 31 Jan 2026 17:24:39 -0500 Subject: [PATCH 2/2] Updated Readme to reflect changes to report.py --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 59e6eebd..eb91e449 100644 --- a/README.md +++ b/README.md @@ -383,6 +383,16 @@ When a user triggers the emergency command, the bot will mention all online/idle * If no users in the log channel are online/idle, the cog will fall back to mentioning all non-bot users. * No number of users greather than the value of the `maxmentions` setting will be mentioned. The order of mentions is not guaranteed, so who is/isn't mentioned under this condition may be unpredictable. +#### Error Handling & Logging + +The cog includes comprehensive error handling to ensure no reports are lost: + +* **Failed report delivery**: If a report fails to send to the log channel (e.g., due to permissions issues or Discord API outages), the full report content is logged to the console and sent to bot owners via DM. +* **Command errors**: If an unexpected error occurs during report submission, the report content is preserved in logs and sent to bot owners for review. +* **Privacy maintained**: Original report messages are still deleted for privacy, but the content is preserved in secure logs when failures occur. + +This ensures that even in edge cases like misconfigured channels or temporary service disruptions, no user reports are permanently lost. + #### Configuration Commands - `[p]reports confirmation ` - Sets whether the bot will DM users a confirmation/copy of their report. Confirmations are always provided when the slash command is used.