async def on_command_error(self, ctx: commands.Context, error: Exception) -> None: """ Send message to `ctx` and log `error`. Response is matched based on `error` type. If an `original` attr is carried, it will be logged, but its message will not propagate to the user. In case we do not match `error` to anything, a generic response `FALLBACK` is used as response. """ original_exception = getattr(error, "original", None) log.debug( f"Error handler received exception of type: {type(error)}, original: {type(original_exception)}" ) # This guarantees to always return some string - a fallback is used when no match is found response = match_response(error) # Use the generic error response generator, which wraps the message in a red embed response_embed = msg_error(response) # The bot may not be able to respond, e.g. due to permissions - let's be safe try: await ctx.send(embed=response_embed) except DiscordException as response_error: log.exception("Failed to send response embed", exc_info=response_error) # The idea is to only log the full traceback if we've encountered a non-Discord exception # This may need to be revisited at some point in the future - maybe we need more information if original_exception is not None: log.exception("Error handler received non-Discord exception", exc_info=error)
async def cmd_status(self, ctx: commands.Context) -> None: """Show info about internal state.""" if self.country_map is not None: embed = msg_success( f"There are currently `{len(self.country_map)}` countries in the cache." ) else: embed = msg_error("Cache is empty, check log for errors.") await ctx.send(embed=embed)
async def cmd_refresh(self, ctx: commands.Context) -> None: """Refresh internal state.""" log.debug("Manually refreshing internal state") if await self.refresh(): resp = msg_success(f"Refreshed successfully! {Emoji.ok_hand}") else: resp = msg_error( f"Something has gone wrong, check log for details. {Emoji.weary}" ) await ctx.send(embed=resp)
async def ext_list(self, ctx: commands.Context) -> None: """Show a list of all active extensions.""" active_extensions: t.Mapping[str, types.ModuleType] = self.bot.extensions log.debug(f"Active extensions: {active_extensions}") if active_extensions: readable = "\n".join(f"{i} | {ext_name}" for i, ext_name in enumerate(active_extensions.keys())) response = f"Active extensions:\n"f"```{readable}```" await ctx.send(embed=msg_success(response)) else: await ctx.send(embed=msg_error("No extensions found!"))
async def cmd_group(self, ctx: commands.Context, *, name: t.Optional[str] = None) -> None: """If no subcommand was invoked, try to match `name` to a country.""" if None in (name, self.country_map): await ctx.invoke(self.cmd_status) return if (country := self.country_map.lookup(name)) is None: await ctx.send( embed=msg_error(f"No such country found. {Emoji.frown}")) return
async def ext_reload(self, ctx: commands.Context, *, ext_name: str) -> None: """ Attempt to reload extension named `ext_name`. This simply delegates to d.py provided utility method, so we do not need to worry about much - if anything goes wrong during the reload, d.py will automatically fallback on previous state, pretending that nothing happened. The bot will report the result in `ctx`. """ log.debug(f"Attempting to reload extension: {ext_name}") try: self.bot.reload_extension(ext_name) except Exception as exc: log.debug(f"Reload failed: {exc}") response = msg_error(f"Reload failed! {exc} {Emoji.angry}") else: response = msg_success(f"Reloaded `{ext_name}` successfully {Emoji.ok_hand}") await ctx.send(embed=response)