discordbot/error_handler.py

794 lines
33 KiB
Python

import discord
from discord.ext import commands
import traceback
import os
import datetime
# Global function for storing interaction content
store_interaction_content = None
# Utility functions to store message content before sending
async def store_and_send(ctx_or_interaction, content, **kwargs):
"""Store the message content and then send it."""
# Store the content for potential error handling
if isinstance(ctx_or_interaction, commands.Context):
ctx_or_interaction._last_message_content = content
return await ctx_or_interaction.send(content, **kwargs)
else: # It's an interaction
ctx_or_interaction._last_response_content = content
if not ctx_or_interaction.response.is_done():
return await ctx_or_interaction.response.send_message(content, **kwargs)
else:
return await ctx_or_interaction.followup.send(content, **kwargs)
async def store_and_reply(ctx, content, **kwargs):
"""Store the message content and then reply to the message."""
ctx._last_message_content = content
return await ctx.reply(content, **kwargs)
def extract_message_content(ctx_or_interaction):
"""Extract message content from a Context or Interaction object."""
content = None
# Check if this is an AI command error
is_ai_command = False
if isinstance(ctx_or_interaction, commands.Context) and hasattr(
ctx_or_interaction, "command"
):
is_ai_command = (
ctx_or_interaction.command and ctx_or_interaction.command.name == "ai"
)
elif hasattr(ctx_or_interaction, "command") and ctx_or_interaction.command:
is_ai_command = ctx_or_interaction.command.name == "ai"
# For AI commands, try to load from the ai_response.txt file if it exists
if is_ai_command and os.path.exists("ai_response.txt"):
try:
with open("ai_response.txt", "r", encoding="utf-8") as f:
content = f.read()
if content:
return content
except Exception as e:
print(f"Error reading ai_response.txt: {e}")
if isinstance(ctx_or_interaction, commands.Context):
# For Context objects
if hasattr(ctx_or_interaction, "_last_message_content"):
content = ctx_or_interaction._last_message_content
elif hasattr(ctx_or_interaction, "message") and hasattr(
ctx_or_interaction.message, "content"
):
content = ctx_or_interaction.message.content
elif hasattr(ctx_or_interaction, "_internal_response"):
content = str(ctx_or_interaction._internal_response)
# Try to extract from command invocation
elif hasattr(ctx_or_interaction, "command") and hasattr(
ctx_or_interaction, "kwargs"
):
# Reconstruct command invocation
cmd_name = (
ctx_or_interaction.command.name
if hasattr(ctx_or_interaction.command, "name")
else "unknown_command"
)
args_str = (
" ".join([str(arg) for arg in ctx_or_interaction.args[1:]])
if hasattr(ctx_or_interaction, "args")
else ""
)
kwargs_str = (
" ".join([f"{k}={v}" for k, v in ctx_or_interaction.kwargs.items()])
if ctx_or_interaction.kwargs
else ""
)
content = f"Command: {cmd_name} {args_str} {kwargs_str}".strip()
else:
# For Interaction objects
if hasattr(ctx_or_interaction, "_last_response_content"):
content = ctx_or_interaction._last_response_content
elif hasattr(ctx_or_interaction, "_internal_response"):
content = str(ctx_or_interaction._internal_response)
# Try to extract from interaction data
elif hasattr(ctx_or_interaction, "data"):
try:
# Extract command name and options
cmd_name = ctx_or_interaction.data.get("name", "unknown_command")
options = ctx_or_interaction.data.get("options", [])
options_str = (
" ".join(
[f"{opt.get('name')}={opt.get('value')}" for opt in options]
)
if options
else ""
)
content = f"Slash Command: /{cmd_name} {options_str}".strip()
except (AttributeError, KeyError):
# If we can't extract structured data, try to get the raw data
content = f"Interaction Data: {str(ctx_or_interaction.data)}"
# For AI commands, add a note if we couldn't retrieve the full response
if is_ai_command and (not content or len(content) < 100):
content = (
"The AI response was too long and could not be retrieved. "
+ "This is likely due to a message that exceeded Discord's length limits. "
+ "Please try again with a shorter prompt or request fewer details."
)
return content
def log_error_details(ctx_or_interaction, error, content=None):
"""Log detailed error information to a file for debugging."""
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_dir = "error_logs"
# Create logs directory if it doesn't exist
if not os.path.exists(log_dir):
os.makedirs(log_dir)
# Create a unique filename based on timestamp
log_file = os.path.join(
log_dir, f"error_{timestamp.replace(':', '-').replace(' ', '_')}.log"
)
with open(log_file, "w", encoding="utf-8") as f:
f.write(f"=== Error Log: {timestamp} ===\n\n")
# Log error details
f.write(f"Error Type: {type(error).__name__}\n")
f.write(f"Error Message: {str(error)}\n\n")
# Log error attributes
if hasattr(error, "__dict__"):
f.write("Error Attributes:\n")
for key, value in error.__dict__.items():
f.write(f" {key}: {value}\n")
f.write("\n")
# Log cause if available
if error.__cause__:
f.write(f"Cause: {type(error.__cause__).__name__}\n")
f.write(f"Cause Message: {str(error.__cause__)}\n\n")
if hasattr(error.__cause__, "__dict__"):
f.write("Cause Attributes:\n")
for key, value in error.__cause__.__dict__.items():
f.write(f" {key}: {value}\n")
f.write("\n")
# Log traceback
f.write("Traceback:\n")
f.write(traceback.format_exc())
f.write("\n")
# Log context/interaction details
f.write("Context/Interaction Details:\n")
if isinstance(ctx_or_interaction, commands.Context):
f.write(f" Type: Context\n")
if hasattr(ctx_or_interaction, "command") and ctx_or_interaction.command:
f.write(f" Command: {ctx_or_interaction.command.name}\n")
if hasattr(ctx_or_interaction, "author") and ctx_or_interaction.author:
f.write(
f" Author: {ctx_or_interaction.author.name} (ID: {ctx_or_interaction.author.id})\n"
)
if hasattr(ctx_or_interaction, "guild") and ctx_or_interaction.guild:
f.write(
f" Guild: {ctx_or_interaction.guild.name} (ID: {ctx_or_interaction.guild.id})\n"
)
if hasattr(ctx_or_interaction, "channel") and ctx_or_interaction.channel:
channel_name = ctx_or_interaction.channel.name
if isinstance(ctx_or_interaction.channel, discord.DMChannel):
channel_name = (
f"DM with {ctx_or_interaction.channel.recipient.name}"
if ctx_or_interaction.channel.recipient
else "DM Channel"
)
f.write(
f" Channel: {channel_name} (ID: {ctx_or_interaction.channel.id})\n"
)
else:
f.write(f" Type: Interaction\n")
if hasattr(ctx_or_interaction, "user") and ctx_or_interaction.user:
f.write(
f" User: {ctx_or_interaction.user.name} (ID: {ctx_or_interaction.user.id})\n"
)
if hasattr(ctx_or_interaction, "guild") and ctx_or_interaction.guild:
f.write(
f" Guild: {ctx_or_interaction.guild.name} (ID: {ctx_or_interaction.guild.id})\n"
)
if hasattr(ctx_or_interaction, "channel") and ctx_or_interaction.channel:
channel_name = ctx_or_interaction.channel.name
if isinstance(ctx_or_interaction.channel, discord.DMChannel):
channel_name = (
f"DM with {ctx_or_interaction.channel.recipient.name}"
if ctx_or_interaction.channel.recipient
else "DM Channel"
)
f.write(
f" Channel: {channel_name} (ID: {ctx_or_interaction.channel.id})\n"
)
if hasattr(ctx_or_interaction, "command") and ctx_or_interaction.command:
f.write(f" Command: {ctx_or_interaction.command.name}\n")
f.write("\n")
# Log message content if available
if content:
f.write("Message Content:\n")
f.write(content)
f.write("\n")
print(f"Error details logged to {log_file}")
return log_file
def patch_discord_methods():
"""Patch Discord methods to store message content before sending."""
# Save original methods for Context
original_context_send = commands.Context.send
original_context_reply = commands.Context.reply
# Patch Context.send
async def patched_context_send(self, content=None, **kwargs):
if content is not None:
self._last_message_content = content
return await original_context_send(self, content, **kwargs)
# Patch Context.reply
async def patched_context_reply(self, content=None, **kwargs):
if content is not None:
self._last_message_content = content
return await original_context_reply(self, content, **kwargs)
# Apply Context patches
commands.Context.send = patched_context_send
commands.Context.reply = patched_context_reply
# For Interaction, we'll use a simpler approach that doesn't rely on patching
# the internal classes, which can vary between Discord.py versions
# Instead, we'll add a utility function to store content that can be called
# before sending messages with interactions
# This function will be available globally for use in commands
async def send_error_embed_to_owner(ctx_or_interaction, error):
"""Send an embed with error details to the bot owner."""
user_id = 452666956353503252 # Owner user ID
try:
# Get the bot instance
bot_instance = None
if isinstance(ctx_or_interaction, commands.Context):
bot_instance = ctx_or_interaction.bot
elif hasattr(ctx_or_interaction, "bot"):
bot_instance = ctx_or_interaction.bot
elif hasattr(ctx_or_interaction, "client"):
bot_instance = ctx_or_interaction.client
# Try to get from global accessor if not found
if not bot_instance:
try:
from global_bot_accessor import get_bot_instance
bot_instance = get_bot_instance()
except ImportError:
print("Failed to import global_bot_accessor")
except Exception as e:
print(f"Error getting bot instance from global_bot_accessor: {e}")
if not bot_instance:
print("Failed to get bot instance for sending error embed to owner")
return
# Get owner user
owner = await bot_instance.fetch_user(user_id)
if not owner:
print(f"Failed to fetch owner user with ID {user_id}")
return
# Create the embed
embed = discord.Embed(
title="❌ Error Report",
description=f"**Error Type:** {type(error).__name__}\n**Message:** {str(error)}",
color=0xFF0000, # Red color
timestamp=datetime.datetime.now(),
)
# Add command info
command_name = "Unknown"
if isinstance(ctx_or_interaction, commands.Context):
if hasattr(ctx_or_interaction, "command") and ctx_or_interaction.command:
command_name = ctx_or_interaction.command.name
embed.add_field(name="Command", value=f"`{command_name}`", inline=True)
else: # It's an interaction
if hasattr(ctx_or_interaction, "command") and ctx_or_interaction.command:
command_name = ctx_or_interaction.command.name
embed.add_field(
name="Slash Command", value=f"`/{command_name}`", inline=True
)
# Add user info
user_info = "Unknown"
if isinstance(ctx_or_interaction, commands.Context):
if ctx_or_interaction.author:
user_info = f"{ctx_or_interaction.author.name} (ID: {ctx_or_interaction.author.id})"
else: # It's an interaction
if ctx_or_interaction.user:
user_info = (
f"{ctx_or_interaction.user.name} (ID: {ctx_or_interaction.user.id})"
)
embed.add_field(name="User", value=user_info, inline=True)
# Add guild and channel info
guild_info = "DM"
channel_info = "DM"
if isinstance(ctx_or_interaction, commands.Context):
if ctx_or_interaction.guild:
guild_info = f"{ctx_or_interaction.guild.name} (ID: {ctx_or_interaction.guild.id})"
if ctx_or_interaction.channel:
if isinstance(ctx_or_interaction.channel, discord.DMChannel):
channel_info = (
f"DM with {ctx_or_interaction.channel.recipient.name}"
if ctx_or_interaction.channel.recipient
else "DM Channel"
)
else:
channel_info = f"#{ctx_or_interaction.channel.name} (ID: {ctx_or_interaction.channel.id})"
else: # It's an interaction
if ctx_or_interaction.guild:
guild_info = f"{ctx_or_interaction.guild.name} (ID: {ctx_or_interaction.guild.id})"
if ctx_or_interaction.channel:
if isinstance(ctx_or_interaction.channel, discord.DMChannel):
channel_info = (
f"DM with {ctx_or_interaction.channel.recipient.name}"
if ctx_or_interaction.channel.recipient
else "DM Channel"
)
else:
channel_info = f"#{ctx_or_interaction.channel.name} (ID: {ctx_or_interaction.channel.id})"
embed.add_field(name="Server", value=guild_info, inline=True)
embed.add_field(name="Channel", value=channel_info, inline=True)
# Add timestamp field
embed.add_field(
name="Timestamp",
value=f"<t:{int(datetime.datetime.now().timestamp())}:F>",
inline=True,
)
# Add traceback as a field (truncated)
tb_str = traceback.format_exc()
if len(tb_str) > 1000:
tb_str = tb_str[:997] + "..."
embed.add_field(
name="Traceback", value=f"```python\n{tb_str}\n```", inline=False
)
# Add cause if available
if error.__cause__:
embed.add_field(
name="Cause",
value=f"```{type(error.__cause__).__name__}: {str(error.__cause__)}\n```",
inline=False,
)
# Extract content for context
try:
content = extract_message_content(ctx_or_interaction)
if content and len(content) > 1000:
content = content[:997] + "..."
if content:
embed.add_field(
name="Message Content", value=f"```\n{content}\n```", inline=False
)
except Exception as e:
embed.add_field(
name="Content Extraction Error",
value=f"Failed to extract message content: {str(e)}",
inline=False,
)
# Set footer
embed.set_footer(
text=f"Error ID: {datetime.datetime.now().strftime('%Y%m%d%H%M%S')}"
)
# Send the embed to the owner
await owner.send(embed=embed)
except Exception as e:
print(f"Error sending error embed to owner: {e}")
# Fall back to simple text message if embed fails
try:
if bot_instance and (owner := await bot_instance.fetch_user(user_id)):
await owner.send(
f"Error occurred but failed to create embed: {str(error)}\nEmbed error: {str(e)}"
)
except:
print("Complete failure in error reporting system")
async def handle_error(ctx_or_interaction, error):
user_id = 452666956353503252 # Owner user ID
# Handle missing required argument errors
# Import here to avoid circular imports
try:
from cogs.ban_system_cog import UserBannedError
if isinstance(error, UserBannedError):
# This should be handled in the main.py error handlers
# But just in case it gets here, handle it properly
message = error.message
if isinstance(ctx_or_interaction, commands.Context):
await ctx_or_interaction.send(message, ephemeral=True)
else:
if not ctx_or_interaction.response.is_done():
await ctx_or_interaction.response.send_message(
message, ephemeral=True
)
else:
await ctx_or_interaction.followup.send(message, ephemeral=True)
return
except ImportError:
# Module not loaded yet, continue with other error handling
pass
if isinstance(error, commands.NotOwner):
message = "You are not the owner of this bot."
if isinstance(ctx_or_interaction, commands.Context):
await ctx_or_interaction.send(message)
else:
if not ctx_or_interaction.response.is_done():
await ctx_or_interaction.response.send_message(message, ephemeral=True)
else:
await ctx_or_interaction.followup.send(message, ephemeral=True)
# Also send to owner in an embed
if isinstance(error, commands.MissingRequiredArgument):
missing_arg = error.param.name if hasattr(error, "param") else "an argument"
message = f"Missing required argument: `{missing_arg}`. Please provide all required arguments."
if isinstance(ctx_or_interaction, commands.Context):
await ctx_or_interaction.send(message)
else:
if not ctx_or_interaction.response.is_done():
await ctx_or_interaction.response.send_message(message, ephemeral=True)
else:
await ctx_or_interaction.followup.send(message, ephemeral=True)
# Also send to owner in an embed
await send_error_embed_to_owner(ctx_or_interaction, error)
return
# Special handling for interaction timeout errors (10062: Unknown interaction)
if (
isinstance(error, commands.CommandInvokeError)
and isinstance(error.original, discord.NotFound)
and error.original.code == 10062
):
print(f"Interaction timeout error (10062): {error}")
# This error occurs when Discord's interaction token expires (after 3 seconds)
# We can't respond to the interaction anymore, so we'll just log it and notify the owner
await send_error_embed_to_owner(ctx_or_interaction, error)
return
error_message = f"An error occurred: {error}"
# Check if this is an AI command error
is_ai_command = False
if isinstance(ctx_or_interaction, commands.Context) and hasattr(
ctx_or_interaction, "command"
):
is_ai_command = (
ctx_or_interaction.command and ctx_or_interaction.command.name == "ai"
)
elif hasattr(ctx_or_interaction, "command") and ctx_or_interaction.command:
is_ai_command = ctx_or_interaction.command.name == "ai"
# For AI command errors with HTTPException, try to handle specially
if (
is_ai_command
and isinstance(error, commands.CommandInvokeError)
and isinstance(error.original, discord.HTTPException)
):
if error.original.code == 50035 and "Must be 4000 or fewer in length" in str(
error.original
):
# Try to get the AI response from the stored content
if isinstance(ctx_or_interaction, commands.Context) and hasattr(
ctx_or_interaction, "_last_message_content"
):
content = ctx_or_interaction._last_message_content
# Save to file and send
with open("ai_response.txt", "w", encoding="utf-8") as f:
f.write(content)
await ctx_or_interaction.send(
"The AI response was too long. Here's the content as a file:",
file=discord.File("ai_response.txt"),
)
# Also notify the owner
await send_error_embed_to_owner(ctx_or_interaction, error)
return
elif hasattr(ctx_or_interaction, "_last_response_content"):
content = ctx_or_interaction._last_response_content
# Save to file and send
with open("ai_response.txt", "w", encoding="utf-8") as f:
f.write(content)
if not ctx_or_interaction.response.is_done():
await ctx_or_interaction.response.send_message(
"The AI response was too long. Here's the content as a file:",
file=discord.File("ai_response.txt"),
)
else:
await ctx_or_interaction.followup.send(
"The AI response was too long. Here's the content as a file:",
file=discord.File("ai_response.txt"),
)
# Also notify the owner
await send_error_embed_to_owner(ctx_or_interaction, error)
return
# Extract message content for logging
content = extract_message_content(ctx_or_interaction)
# Log error details to file
log_file = log_error_details(ctx_or_interaction, error, content)
# Check if the command runner is the owner
is_owner = False
if isinstance(ctx_or_interaction, commands.Context):
is_owner = ctx_or_interaction.author.id == user_id
else:
is_owner = ctx_or_interaction.user.id == user_id
# Only send detailed error DM if the command runner is the owner
if is_owner:
try:
# Get the bot instance - handle both Context and Interaction objects
bot_instance = None
if isinstance(ctx_or_interaction, commands.Context):
bot_instance = ctx_or_interaction.bot
elif hasattr(ctx_or_interaction, "bot"):
bot_instance = ctx_or_interaction.bot
elif hasattr(ctx_or_interaction, "client"):
bot_instance = ctx_or_interaction.client
# If we couldn't get the bot instance, try to get it from the global accessor
if not bot_instance:
try:
# Import here to avoid circular imports
from global_bot_accessor import get_bot_instance
bot_instance = get_bot_instance()
except ImportError:
print("Failed to import global_bot_accessor")
except Exception as e:
print(f"Error getting bot instance from global_bot_accessor: {e}")
# If we still don't have a bot instance, we can't send a DM
if not bot_instance:
print(f"Failed to send error DM to owner: No bot instance available")
return
# Now fetch the owner user
owner = await bot_instance.fetch_user(user_id)
if owner:
full_error = f"Full error details:\n```\n{str(error)}\n"
if hasattr(error, "__dict__"):
full_error += f"\nError attributes:\n{error.__dict__}\n"
if error.__cause__:
full_error += f"\nCause:\n{str(error.__cause__)}\n"
if hasattr(error.__cause__, "__dict__"):
full_error += (
f"\nCause attributes:\n{error.__cause__.__dict__}\n"
)
full_error += "```"
# Add log file path to the error message
full_error += f"\nDetailed error log saved to: `{log_file}`"
# Try to send the log file as an attachment
try:
await owner.send(
"Here's the detailed error log:", file=discord.File(log_file)
)
# Send a shorter message since we sent the file
short_error = f"Error: {str(error)}"
if error.__cause__:
short_error += f"\nCause: {str(error.__cause__)}"
await owner.send(short_error)
except discord.HTTPException:
# If sending the file fails, fall back to text messages
# Split long messages if needed
if len(full_error) > 1900:
parts = [
full_error[i : i + 1900]
for i in range(0, len(full_error), 1900)
]
for i, part in enumerate(parts):
await owner.send(f"Part {i+1}/{len(parts)}:\n{part}")
else:
await owner.send(full_error)
except Exception as e:
print(f"Failed to send error DM to owner: {e}")
# Determine the file name to use for saving content
file_name = "message.txt"
# Special handling for AI command errors
if isinstance(error, commands.CommandInvokeError) and isinstance(
error.original, discord.HTTPException
):
# Check if this is an AI command
is_ai_command = False
if isinstance(ctx_or_interaction, commands.Context) and hasattr(
ctx_or_interaction, "command"
):
is_ai_command = (
ctx_or_interaction.command and ctx_or_interaction.command.name == "ai"
)
elif hasattr(ctx_or_interaction, "command") and ctx_or_interaction.command:
is_ai_command = ctx_or_interaction.command.name == "ai"
# If it's an AI command, use a different file name
if is_ai_command:
file_name = "ai_response.txt"
# Handle message too long error (HTTP 400 - Code 50035 or 40005 for file uploads)
if (
isinstance(error, discord.HTTPException)
and (
(
error.code == 50035
and (
"Must be 4000 or fewer in length" in str(error)
or "Must be 2000 or fewer in length" in str(error)
)
)
or (error.code == 40005 and "Request entity too large" in str(error))
)
) or (
isinstance(error, commands.CommandInvokeError)
and isinstance(error.original, discord.HTTPException)
and (
(
error.original.code == 50035
and (
"Must be 4000 or fewer in length" in str(error.original)
or "Must be 2000 or fewer in length" in str(error.original)
)
)
or (
error.original.code == 40005
and "Request entity too large" in str(error.original)
)
)
):
# Try to extract the actual content from the error
content = None
# Handle CommandInvokeError specially
if isinstance(error, commands.CommandInvokeError):
# Use the original error for extraction
original_error = error.original
if isinstance(original_error, discord.HTTPException):
content = (
original_error.text if hasattr(original_error, "text") else None
)
# If it's a wrapped error, get the original error's content
elif isinstance(error.__cause__, discord.HTTPException):
content = error.__cause__.text if hasattr(error.__cause__, "text") else None
else:
content = error.text if hasattr(error, "text") else None
# If content is not available in the error, try to retrieve it from the context/interaction
if (
not content or len(content) < 10
): # If content is missing or too short to be the actual message
# Try to get the original content using our utility function
content = extract_message_content(ctx_or_interaction)
# If we still don't have content, use a generic message
if not content:
content = "The original message content could not be retrieved. This is likely due to a message that exceeded Discord's length limits."
# Try to send as a file first
try:
# Create a text file with the content
with open(file_name, "w", encoding="utf-8") as f:
f.write(content)
# Send the file instead
message = f"The message was too long. Here's the content as a file:\nError details logged to: {log_file}"
if isinstance(ctx_or_interaction, commands.Context):
await ctx_or_interaction.send(message, file=discord.File(file_name))
else:
if not ctx_or_interaction.response.is_done():
await ctx_or_interaction.response.send_message(
message, file=discord.File(file_name)
)
else:
await ctx_or_interaction.followup.send(
message, file=discord.File(file_name)
)
except discord.HTTPException as e:
# If sending as a file also fails (e.g., file too large), split into multiple messages
if e.code == 40005 or "Request entity too large" in str(e):
# Split the content into chunks of 1900 characters (Discord limit is 2000)
chunks = [content[i : i + 1900] for i in range(0, len(content), 1900)]
# Send a notification about splitting the message
intro_message = f"The message was too long to send as a file. Splitting into {len(chunks)} parts.\nError details logged to: {log_file}"
if isinstance(ctx_or_interaction, commands.Context):
await ctx_or_interaction.send(intro_message)
for i, chunk in enumerate(chunks):
await ctx_or_interaction.send(
f"Part {i+1}/{len(chunks)}:\n```\n{chunk}\n```"
)
else:
if not ctx_or_interaction.response.is_done():
await ctx_or_interaction.response.send_message(intro_message)
for i, chunk in enumerate(chunks):
await ctx_or_interaction.followup.send(
f"Part {i+1}/{len(chunks)}:\n```\n{chunk}\n```"
)
else:
await ctx_or_interaction.followup.send(intro_message)
for i, chunk in enumerate(chunks):
await ctx_or_interaction.followup.send(
f"Part {i+1}/{len(chunks)}:\n```\n{chunk}\n```"
)
else:
# If it's a different error, re-raise it
raise
return
# Send embed to owner for all errors that reach this point
await send_error_embed_to_owner(ctx_or_interaction, error)
# Original error handling logic
if isinstance(ctx_or_interaction, commands.Context):
if ctx_or_interaction.author.id == user_id:
try:
await ctx_or_interaction.send(content=error_message)
except discord.Forbidden:
await ctx_or_interaction.send(
"Unable to send you a DM with the error details."
)
else:
await ctx_or_interaction.send(
"An error occurred while processing your command."
)
else:
if not ctx_or_interaction.response.is_done():
if ctx_or_interaction.user.id == user_id:
await ctx_or_interaction.response.send_message(
content=error_message, ephemeral=True
)
else:
await ctx_or_interaction.response.send_message(
"An error occurred while processing your command.", ephemeral=True
)
else:
if ctx_or_interaction.user.id == user_id:
await ctx_or_interaction.followup.send(
content=error_message, ephemeral=True
)
else:
await ctx_or_interaction.followup.send(
"An error occurred while processing your command.", ephemeral=True
)