From 47b8af57cdff20cc9a6adf0e444acf3b3e8d5157 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Tue, 19 Jul 2022 14:03:14 +0200 Subject: [PATCH] Added basic command error handling --- src/bot/config/appsettings.development.json | 6 +- src/bot/config/appsettings.edrafts-lapi.json | 5 +- src/bot/config/appsettings.production.json | 6 +- src/bot/config/appsettings.staging.json | 6 +- src/bot/startup_discord_extension.py | 3 + src/bot/translation/de.json | 1 + src/bot_core/abc/message_service_abc.py | 10 +- src/bot_core/configuration/bot_settings.py | 9 +- src/bot_core/service/message_service.py | 40 +++-- src/modules/base/command/afk_command.py | 1 + src/modules/base/command/ping_command.py | 1 + src/modules/base/command/user_info_command.py | 1 + .../events/base_on_command_error_event.py | 141 ++++++++++++++++++ .../moderator/command/purge_command.py | 1 + 14 files changed, 205 insertions(+), 26 deletions(-) create mode 100644 src/modules/base/events/base_on_command_error_event.py diff --git a/src/bot/config/appsettings.development.json b/src/bot/config/appsettings.development.json index 837f39b5..d61a00bc 100644 --- a/src/bot/config/appsettings.development.json +++ b/src/bot/config/appsettings.development.json @@ -24,7 +24,11 @@ "Bot": { "910199451145076828": { "MessageDeleteTimer": 6 - } + }, + "Technicians": [ + 240160344557879316, + 236592458664902657 + ] }, "Base": { "910199451145076828": { diff --git a/src/bot/config/appsettings.edrafts-lapi.json b/src/bot/config/appsettings.edrafts-lapi.json index 38c82587..71cee08b 100644 --- a/src/bot/config/appsettings.edrafts-lapi.json +++ b/src/bot/config/appsettings.edrafts-lapi.json @@ -22,7 +22,10 @@ "Bot": { "910199451145076828": { "MessageDeleteTimer": 2 - } + }, + "Technicians": [ + 240160344557879316 + ] }, "Base": { "910199451145076828": { diff --git a/src/bot/config/appsettings.production.json b/src/bot/config/appsettings.production.json index 35d2bf9f..9beda2f3 100644 --- a/src/bot/config/appsettings.production.json +++ b/src/bot/config/appsettings.production.json @@ -23,7 +23,11 @@ }, "910199451145076828": { "MessageDeleteTimer": 2 - } + }, + "Technicians": [ + 240160344557879316, + 236592458664902657 + ] }, "Base": { "650366049023295514": { diff --git a/src/bot/config/appsettings.staging.json b/src/bot/config/appsettings.staging.json index ee84bded..75d992cb 100644 --- a/src/bot/config/appsettings.staging.json +++ b/src/bot/config/appsettings.staging.json @@ -20,7 +20,11 @@ "Bot": { "910199451145076828": { "MessageDeleteTimer": 4 - } + }, + "Technicians": [ + 240160344557879316, + 236592458664902657 + ] }, "Base": { "910199451145076828": { diff --git a/src/bot/startup_discord_extension.py b/src/bot/startup_discord_extension.py index b1ae518e..4d20e684 100644 --- a/src/bot/startup_discord_extension.py +++ b/src/bot/startup_discord_extension.py @@ -11,6 +11,7 @@ from modules.base.command.afk_command import AFKCommand from modules.base.command.help_command import HelpCommand from modules.base.command.info_command import InfoCommand from modules.base.command.ping_command import PingCommand +from modules.base.events.base_on_command_error_event import BaseOnCommandErrorEvent from modules.moderator.command.purge_command import PurgeCommand from modules.base.command.user_info_command import UserInfoCommand from modules.base.events.base_on_member_join_event import BaseOnMemberJoinEvent @@ -47,6 +48,8 @@ class StartupDiscordExtension(StartupExtensionABC): dc.add_command(PingCommand) dc.add_command(UserInfoCommand) """ events """ + # on_command_error + dc.add_event(DiscordEventTypesEnum.on_command_error.value, BaseOnCommandErrorEvent) # on_member_join dc.add_event(DiscordEventTypesEnum.on_member_join.value, BaseOnMemberJoinEvent) # on_member_remove diff --git a/src/bot/translation/de.json b/src/bot/translation/de.json index 36b4e773..52edc751 100644 --- a/src/bot/translation/de.json +++ b/src/bot/translation/de.json @@ -14,6 +14,7 @@ "goodbye_message": "Schade das du uns so schnell verlässt :(" }, "base": { + "technician_command_error_message": "Es gab ein Fehler mit dem Befehl: {} ausgelöst von {} -> {}\nDatum und Zeit: {}\nSchau bitte ins log für Details.", "welcome_message": "Hello There!\nIch heiße dich bei {} herzlichst willkommen!", "welcome_message_for_team": "{} hat gerade das Irrenhaus betreten.", "purge_message": "Na gut..., ich lösche alle Nachrichten wenns sein muss.", diff --git a/src/bot_core/abc/message_service_abc.py b/src/bot_core/abc/message_service_abc.py index f19e4034..3e524d4d 100644 --- a/src/bot_core/abc/message_service_abc.py +++ b/src/bot_core/abc/message_service_abc.py @@ -12,16 +12,16 @@ class MessageServiceABC(ABC): def __init__(self): pass @abstractmethod - async def delete_messages(self, messages: List[discord.Message], guild_id: int): pass + async def delete_messages(self, messages: List[discord.Message], guild_id: int, without_tracking=False): pass @abstractmethod - async def delete_message(self, message: discord.Message): pass + async def delete_message(self, message: discord.Message, without_tracking=False): pass @abstractmethod - async def send_channel_message(self, channel: discord.TextChannel, message: Union[str, discord.Embed]): pass + async def send_channel_message(self, channel: discord.TextChannel, message: Union[str, discord.Embed], without_tracking=True): pass @abstractmethod - async def send_dm_message(self, message: Union[str, discord.Embed], receiver: Union[discord.User, discord.Member]): pass + async def send_dm_message(self, message: Union[str, discord.Embed], receiver: Union[discord.User, discord.Member], without_tracking=False): pass @abstractmethod - async def send_ctx_msg(self, ctx: Context, message: Union[str, discord.Embed], file: discord.File = None, is_persistent: bool = False, wait_before_delete: int = None): pass + async def send_ctx_msg(self, ctx: Context, message: Union[str, discord.Embed], file: discord.File = None, is_persistent: bool = False, wait_before_delete: int = None, without_tracking=True): pass diff --git a/src/bot_core/configuration/bot_settings.py b/src/bot_core/configuration/bot_settings.py index 6dd20f8c..96bd61c8 100644 --- a/src/bot_core/configuration/bot_settings.py +++ b/src/bot_core/configuration/bot_settings.py @@ -13,17 +13,24 @@ class BotSettings(ConfigurationModelABC): ConfigurationModelABC.__init__(self) self._servers: List[ServerSettings] = List() + self._technicians: list[int] = [] @property def servers(self) -> List[ServerSettings]: return self._servers + @property + def technicians(self) -> list[int]: + return self._technicians + def from_dict(self, settings: dict): try: + self._technicians = settings["Technicians"] + settings.pop("Technicians") servers = List(ServerSettings) for s in settings: st = ServerSettings() - settings[s]['Id'] = s + settings[s]["Id"] = s st.from_dict(settings[s]) servers.append(st) self._servers = servers diff --git a/src/bot_core/service/message_service.py b/src/bot_core/service/message_service.py index 743c5842..c0c1ca59 100644 --- a/src/bot_core/service/message_service.py +++ b/src/bot_core/service/message_service.py @@ -23,15 +23,15 @@ class MessageService(MessageServiceABC): self._clients = clients self._db = db - async def delete_messages(self, messages: List[discord.Message], guild_id: int): + async def delete_messages(self, messages: List[discord.Message], guild_id: int, without_tracking=False): self._logger.debug(__name__, f'Try to delete {messages.count()} messages') server_st: ServerSettings = self._config.get_configuration(f'ServerSettings_{guild_id}') await asyncio.sleep(server_st.message_delete_timer) for message in messages: - await self.delete_message(message, mass_delete=True) + await self.delete_message(message, mass_delete=True, without_tracking=without_tracking) self._logger.debug(__name__, 'Deleting messages finished') - async def delete_message(self, message: discord.Message, mass_delete=False): + async def delete_message(self, message: discord.Message, mass_delete=False, without_tracking=False): server_st: ServerSettings = self._config.get_configuration(f'ServerSettings_{message.guild.id}') if not mass_delete: await asyncio.sleep(server_st.message_delete_timer) @@ -42,11 +42,12 @@ class MessageService(MessageServiceABC): except Exception as e: self._logger.error(__name__, f'Deleting message failed', e) else: - self._clients.append_deleted_message_count(self._bot.user.id, guild_id, 1) - self._db.save_changes() + if not without_tracking: + self._clients.append_deleted_message_count(self._bot.user.id, guild_id, 1) + self._db.save_changes() self._logger.info(__name__, f'Deleted message {message}') - async def send_channel_message(self, channel: discord.TextChannel, message: Union[str, discord.Embed]): + async def send_channel_message(self, channel: discord.TextChannel, message: Union[str, discord.Embed], without_tracking=False): self._logger.debug(__name__, f'Try to send message\n\t{message}\n\tto: {channel}') msg = None try: @@ -58,11 +59,12 @@ class MessageService(MessageServiceABC): self._logger.error(__name__, f'Send message to channel {channel.id} failed', e) else: self._logger.info(__name__, f'Sent message to channel {channel.id}') - self._clients.append_sent_message_count(self._bot.user.id, channel.guild.id, 1) - self._db.save_changes() - await self.delete_message(msg) + if not without_tracking: + self._clients.append_sent_message_count(self._bot.user.id, channel.guild.id, 1) + self._db.save_changes() + await self.delete_message(msg, without_tracking) - async def send_dm_message(self, message: Union[str, discord.Embed], receiver: Union[discord.User, discord.Member]): + async def send_dm_message(self, message: Union[str, discord.Embed], receiver: Union[discord.User, discord.Member], without_tracking=False): self._logger.debug(__name__, f'Try to send message\n\t{message}\n\tto: {receiver}') try: if isinstance(message, discord.Embed): @@ -72,11 +74,12 @@ class MessageService(MessageServiceABC): except Exception as e: self._logger.error(__name__, f'Send message to user {receiver.id} failed', e) else: - self._clients.append_sent_message_count(self._bot.user.id, receiver.guild.id, 1) - self._db.save_changes() + if not without_tracking: + self._clients.append_sent_message_count(self._bot.user.id, receiver.guild.id, 1) + self._db.save_changes() self._logger.info(__name__, f'Sent message to user {receiver.id}') - async def send_ctx_msg(self, ctx: Context, message: Union[str, discord.Embed], file: discord.File = None, is_persistent: bool = False, wait_before_delete: int = None): + async def send_ctx_msg(self, ctx: Context, message: Union[str, discord.Embed], file: discord.File = None, is_persistent: bool = False, wait_before_delete: int = None, without_tracking=False): if ctx is None: self._logger.warn(__name__, 'Message context is empty') self._logger.debug(__name__, f'Message: {message}') @@ -93,13 +96,18 @@ class MessageService(MessageServiceABC): self._logger.error(__name__, f'Send message to channel {ctx.channel.id} failed', e) else: self._logger.info(__name__, f'Sent message to channel {ctx.channel.id}') - self._clients.append_sent_message_count(self._bot.user.id, ctx.guild.id, 1) - self._db.save_changes() + if not without_tracking: + self._clients.append_sent_message_count(self._bot.user.id, ctx.guild.id, 1) + self._db.save_changes() + if wait_before_delete is not None: await asyncio.sleep(wait_before_delete) if is_persistent: - await self.delete_message(ctx.message) + await self.delete_message(ctx.message, without_tracking) return + if ctx.guild is None: + self._logger.error(__name__, f'Error in {__name__}.send_ctx_msg: Guild is None') + return await self.delete_messages(List(discord.Message, [msg, ctx.message]), ctx.guild.id) diff --git a/src/modules/base/command/afk_command.py b/src/modules/base/command/afk_command.py index 1f954feb..10eb6af1 100644 --- a/src/modules/base/command/afk_command.py +++ b/src/modules/base/command/afk_command.py @@ -35,6 +35,7 @@ class AFKCommand(DiscordCommandABC): self._logger.trace(__name__, f'Loaded command service: {type(self).__name__}') @commands.command() + @commands.guild_only() async def afk(self, ctx: Context): self._logger.debug(__name__, f'Received command afk {ctx}') self._client_utils.received_command(ctx.guild.id) diff --git a/src/modules/base/command/ping_command.py b/src/modules/base/command/ping_command.py index 2df5fada..60b3d0ab 100644 --- a/src/modules/base/command/ping_command.py +++ b/src/modules/base/command/ping_command.py @@ -33,5 +33,6 @@ class PingCommand(DiscordCommandABC): async def ping(self, ctx: Context): self._logger.debug(__name__, f'Received command ping {ctx}') self._client_utils.received_command(ctx.guild.id) + raise Exception('Dies ist ein test, ob ich dir eine Benachrichtung über Fehler sende') await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.base.pong')) self._logger.trace(__name__, f'Finished ping command') diff --git a/src/modules/base/command/user_info_command.py b/src/modules/base/command/user_info_command.py index 38b3d4dd..d9d646ba 100644 --- a/src/modules/base/command/user_info_command.py +++ b/src/modules/base/command/user_info_command.py @@ -51,6 +51,7 @@ class UserInfoCommand(DiscordCommandABC): self._logger.trace(__name__, f'Loaded command service: {type(self).__name__}') @commands.command(name='user-info') + @commands.guild_only() async def user_info(self, ctx: Context, member: Optional[discord.Member] = None, *, wait: int = None): self._logger.debug(__name__, f'Received command user-info {ctx}:{member},{wait}') self._client_utils.received_command(ctx.guild.id) diff --git a/src/modules/base/events/base_on_command_error_event.py b/src/modules/base/events/base_on_command_error_event.py new file mode 100644 index 00000000..107ccbdc --- /dev/null +++ b/src/modules/base/events/base_on_command_error_event.py @@ -0,0 +1,141 @@ +import datetime + +from cpl_core.time import TimeFormatSettings +from cpl_discord.service import DiscordBotServiceABC +from cpl_translation import TranslatePipe +from discord.ext import commands +from discord.ext.commands import Context, CommandError + +from cpl_core.logging import LoggerABC +from cpl_discord.events.on_command_error_abc import OnCommandErrorABC + +from bot_core.abc.message_service_abc import MessageServiceABC +from bot_core.configuration.bot_settings import BotSettings + + +class BaseOnCommandErrorEvent(OnCommandErrorABC): + + def __init__( + self, + logger: LoggerABC, + bot: DiscordBotServiceABC, + messenger: MessageServiceABC, + bot_settings: BotSettings, + time_format_settings: TimeFormatSettings, + translate: TranslatePipe + ): + OnCommandErrorABC.__init__(self) + self._logger = logger + self._bot = bot + self._messenger = messenger + self._bot_settings = bot_settings + self._time_format_settings = time_format_settings + self._t = translate + + async def on_command_error(self, ctx: Context, error: CommandError): + error = getattr(error, 'original', error) + + # Todo: translate error messages !!! + if isinstance(error, commands.MissingRequiredArgument): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Ein benötigter Parameter fehlt!', without_tracking=True) + + elif isinstance(error, commands.ArgumentParsingError): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Parameter konnte nicht gelesen werden!', without_tracking=True) + + elif isinstance(error, commands.UnexpectedQuoteError): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Unerwarteter Zitat Fehler!', without_tracking=True) + + elif isinstance(error, commands.InvalidEndOfQuotedStringError): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Ungültiges Zitatende!', without_tracking=True) + + elif isinstance(error, commands.ExpectedClosingQuoteError): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Erwarte Zitatende!', without_tracking=True) + + elif isinstance(error, commands.BadArgument): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Ungültiger Parameter!', without_tracking=True) + + elif isinstance(error, commands.BadUnionArgument): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Ungültiger Union Parameter!', without_tracking=True) + + elif isinstance(error, commands.PrivateMessageOnly): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Nur private Nachrichten sind erlaubt!', without_tracking=True) + + elif isinstance(error, commands.NoPrivateMessage): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Private Nachrichten sind nicht erlaubt!', without_tracking=True) + + elif isinstance(error, commands.CheckFailure): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Du hast nicht die benötigte Berechtigung!', without_tracking=True) + + elif isinstance(error, commands.CheckAnyFailure): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Alle checks sind Fehlgeschlagen!', without_tracking=True) + + elif isinstance(error, commands.CommandNotFound): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Befehl konnte nicht gefunden werden!', without_tracking=True) + + elif isinstance(error, commands.DisabledCommand): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Befehl wurde deaktiviert!', without_tracking=True) + + elif isinstance(error, commands.CommandInvokeError): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Befehl konnte nicht aufgerufen werden!', without_tracking=True) + + elif isinstance(error, commands.TooManyArguments): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Zu viele Parameter!', without_tracking=True) + + elif isinstance(error, commands.UserInputError): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Eingabefehler!', without_tracking=True) + + elif isinstance(error, commands.CommandOnCooldown): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Befehl befindet sich im cooldown!', without_tracking=True) + + elif isinstance(error, commands.MaxConcurrencyReached): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Maximale Parallelität erreicht!', without_tracking=True) + + elif isinstance(error, commands.NotOwner): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Du bist nicht mein besitzer!', without_tracking=True) + + elif isinstance(error, commands.MissingPermissions): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Berechtigungen fehlen!', without_tracking=True) + + elif isinstance(error, commands.BotMissingPermissions): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Mir fehlen Berechtigungen!', without_tracking=True) + + elif isinstance(error, commands.MissingRole): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Benötigte Rolle fehlt!', without_tracking=True) + + elif isinstance(error, commands.BotMissingRole): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Mir fehlt eine benötigte Rolle!', without_tracking=True) + + elif isinstance(error, commands.MissingAnyRole): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Alle benötigten Rollen fehlen!', without_tracking=True) + + elif isinstance(error, commands.BotMissingAnyRole): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Mir fehlen alle benötigten Rollen!', without_tracking=True) + + elif isinstance(error, commands.NSFWChannelRequired): + await self._messenger.send_ctx_msg(ctx, 'Fehler: NSFW Kanal benötigt!', without_tracking=True) + + elif isinstance(error, commands.ExtensionError): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Erweiterungsfehler!', without_tracking=True) + + elif isinstance(error, commands.ExtensionAlreadyLoaded): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Erweiterung wurde bereits geladen!', without_tracking=True) + + elif isinstance(error, commands.ExtensionNotLoaded): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Erweiterung wurde nicht geladen!', without_tracking=True) + + elif isinstance(error, commands.NoEntryPointError): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Kein Eintrittspunkt!', without_tracking=True) + + elif isinstance(error, commands.ExtensionFailed): + await self._messenger.send_ctx_msg(ctx, 'Fehler: Erweiterung ist fehlgeschlagen!', without_tracking=True) + + else: + message = self._t.transform('modules.base.technician_command_error_message').format( + ctx.command, + ctx.author, + error, + datetime.datetime.now().strftime(self._time_format_settings.date_time_format) + ) + for t in self._bot_settings.technicians: + member = self._bot.get_user(t) + await self._messenger.send_dm_message(message, member, without_tracking=True) diff --git a/src/modules/moderator/command/purge_command.py b/src/modules/moderator/command/purge_command.py index 871aebe4..07ecd4bf 100644 --- a/src/modules/moderator/command/purge_command.py +++ b/src/modules/moderator/command/purge_command.py @@ -36,6 +36,7 @@ class PurgeCommand(DiscordCommandABC): self._logger.trace(__name__, f'Loaded command service: {type(self).__name__}') @commands.command() + @commands.guild_only() async def purge(self, ctx: Context): self._logger.debug(__name__, f'Received command purge {ctx}') self._client_utils.received_command(ctx.guild.id)