Ruby-Cogs/autotraceback/autotraceback.py
2025-04-02 22:56:57 -04:00

142 lines
5.6 KiB
Python

from AAA3A_utils import Cog, CogsUtils, Menu # isort:skip
from redbot.core import commands # isort:skip
from redbot.core.bot import Red # isort:skip
from redbot.core.i18n import Translator, cog_i18n # isort:skip
import discord # isort:skip
import typing # isort:skip
import traceback
from redbot.core.utils.chat_formatting import box, pagify
from .dashboard_integration import DashboardIntegration
# Credits:
# General repo credits.
_: Translator = Translator("AutoTraceback", __file__)
IGNORED_ERRORS = (
commands.UserInputError,
commands.DisabledCommand,
commands.CommandNotFound,
commands.CheckFailure,
commands.NoPrivateMessage,
commands.CommandOnCooldown,
commands.MaxConcurrencyReached,
commands.BadArgument,
commands.BadBoolArgument,
)
@cog_i18n(_)
class AutoTraceback(DashboardIntegration, Cog):
"""A cog to display the error traceback of a command automatically after the error!"""
def __init__(self, bot: Red) -> None:
super().__init__(bot=bot)
self.tracebacks: typing.List[str] = []
@commands.is_owner()
@commands.hybrid_command()
async def traceback(
self, ctx: commands.Context, public: typing.Optional[bool] = True, index: int = 0
) -> None:
"""Sends to the owner the last command exception that has occurred.
If public (yes is specified), it will be sent to the chat instead.
Warning: Sending the traceback publicly can accidentally reveal sensitive information about your computer or configuration.
**Examples:**
- `[p]traceback` - Sends the traceback to your DMs.
- `[p]traceback True` - Sends the last traceback in the current context.
**Arguments:**
- `[public]` - Whether to send the traceback to the current context. Default is `True`.
- `[index]` - The error index. `0` is the last one.
"""
if not self.tracebacks and not ctx.bot._last_exception:
raise commands.UserFeedbackCheckFailure(_("No exception has occurred yet."))
if index == 0: # Last bot exception can be set directly by cogs.
_last_exception = ctx.bot._last_exception
else:
try:
_last_exception = self.tracebacks[-(index + 1)]
except IndexError:
_last_exception = ctx.bot._last_exception
_last_exception = _last_exception.split("\n")
_last_exception[0] = _last_exception[0] + (
"" if _last_exception[0].endswith(":") else ":\n"
)
_last_exception = "\n".join(_last_exception)
_last_exception = CogsUtils.replace_var_paths(_last_exception)
if public:
try:
await Menu(pages=_last_exception, timeout=180, lang="py").start(ctx)
except discord.HTTPException:
pass
else:
return
for page in pagify(_last_exception, shorten_by=15):
try:
await ctx.author.send(box(page, lang="py"))
except discord.HTTPException:
raise commands.UserFeedbackCheckFailure(
"I couldn't send the traceback message to you in DM. "
"Either you blocked me or you disabled DMs in this server."
)
@commands.Cog.listener()
async def on_command_error(
self, ctx: commands.Context, error: commands.CommandError, unhandled_by_cog: bool = False
) -> None:
if await self.bot.cog_disabled_in_guild(cog=self, guild=ctx.guild):
return
if isinstance(error, IGNORED_ERRORS):
return
traceback_error = "".join(
traceback.format_exception(type(error), error, error.__traceback__)
)
_traceback_error = traceback_error.split("\n")
_traceback_error[0] = _traceback_error[0] + (
"" if _traceback_error[0].endswith(":") else ":\n"
)
traceback_error = "\n".join(_traceback_error)
traceback_error = CogsUtils.replace_var_paths(traceback_error)
self.tracebacks.append(traceback_error)
if ctx.author.id not in ctx.bot.owner_ids:
return
pages = [box(page, lang="py") for page in pagify(traceback_error, shorten_by=10)]
try:
await Menu(pages=pages, timeout=180, delete_after_timeout=False).start(ctx)
except discord.HTTPException:
pass
@commands.Cog.listener()
async def on_assistant_cog_add(
self, assistant_cog: typing.Optional[commands.Cog] = None
) -> None: # Vert's Assistant integration/third party.
if assistant_cog is None:
return self.get_last_command_error_traceback_for_assistant
schema = {
"name": "get_last_command_error_traceback_for_assistant",
"description": "Get the traceback of the last command error occured on the bot.",
"parameters": {"type": "object", "properties": {}, "required": []},
}
await assistant_cog.register_function(cog_name=self.qualified_name, schema=schema)
async def get_last_command_error_traceback_for_assistant(
self, user: typing.Union[discord.Member, discord.User], *args, **kwargs
):
if user.id not in self.bot.owner_ids:
return "Only bot owners can view errors tracebacks."
if not self.bot._last_exception:
return "No last command error recorded."
last_traceback = self.bot._last_exception
last_traceback = CogsUtils.replace_var_paths(last_traceback)
data = {
"Last command error traceback": f"\n{last_traceback}",
}
return [f"{key}: {value}\n" for key, value in data.items() if value is not None]