diff --git a/kdb-bot/src/bot_core/abc/message_service_abc.py b/kdb-bot/src/bot_core/abc/message_service_abc.py index 4af35bff..912f078b 100644 --- a/kdb-bot/src/bot_core/abc/message_service_abc.py +++ b/kdb-bot/src/bot_core/abc/message_service_abc.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Union +from typing import Union, Optional import discord from cpl_query.extension import List @@ -19,13 +19,26 @@ class MessageServiceABC(ABC): 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], without_tracking=True): 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], without_tracking=False): 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, is_public: bool = False, wait_before_delete: int = None, without_tracking=True): pass + async def send_ctx_msg( + self, ctx: Context, message: Union[str, discord.Embed], file: discord.File = None, + is_persistent: bool = False, is_public: bool = False, wait_before_delete: int = None, + without_tracking=True + ) -> Optional[discord.Message]: pass @abstractmethod - async def send_interaction_msg(self, interaction: Interaction, message: Union[str, discord.Embed], is_persistent: bool = False, is_public: bool = False, wait_before_delete: int = None, without_tracking=True, **kwargs): pass + async def send_interaction_msg( + self, interaction: Interaction, message: Union[str, discord.Embed], + is_persistent: bool = False, is_public: bool = False, wait_before_delete: int = None, + without_tracking=True, **kwargs + ): pass diff --git a/kdb-bot/src/bot_core/service/message_service.py b/kdb-bot/src/bot_core/service/message_service.py index 967d23e5..0c70b3c6 100644 --- a/kdb-bot/src/bot_core/service/message_service.py +++ b/kdb-bot/src/bot_core/service/message_service.py @@ -1,5 +1,5 @@ import asyncio -from typing import Union +from typing import Union, Optional import discord from cpl_core.configuration.configuration_abc import ConfigurationABC @@ -34,11 +34,15 @@ class MessageService(MessageServiceABC): self._logger.debug(__name__, 'Deleting messages finished') 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}') + guild_id = \ + message.guild.id if message.guild is not None else \ + message.channel.guild.id if message.channel is not None and message.channel.guild is not None else \ + message.reference.guild_id if message.reference is not None else None + + server_st: ServerSettings = self._config.get_configuration(f'ServerSettings_{guild_id}') if not mass_delete: await asyncio.sleep(server_st.message_delete_timer) self._logger.debug(__name__, f'Try to delete message: {LogMessageHelper.get_log_string(message)}') - guild_id = message.guild.id try: await message.delete() await asyncio.sleep(server_st.message_delete_timer) @@ -89,11 +93,11 @@ class MessageService(MessageServiceABC): 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, is_public: bool = False, wait_before_delete: int = None, without_tracking=False): + async def send_ctx_msg(self, ctx: Context, message: Union[str, discord.Embed], file: discord.File = None, is_persistent: bool = False, is_public: bool = False, wait_before_delete: int = None, without_tracking=False) -> Optional[discord.Message]: if ctx is None: self._logger.warn(__name__, 'Message context is empty') self._logger.debug(__name__, f'Message: {message}') - return + return None self._logger.debug(__name__, f'Try to send message\t\t{message}\n\tto: {ctx.channel}') msg = None @@ -114,11 +118,13 @@ class MessageService(MessageServiceABC): await asyncio.sleep(wait_before_delete) if is_persistent: - return + return msg if ctx.guild is not None: await self.delete_message(msg, without_tracking) + return msg + async def send_interaction_msg(self, interaction: Interaction, message: Union[str, discord.Embed], is_persistent: bool = False, is_public: bool = False, wait_before_delete: int = None, without_tracking=False, **kwargs): if interaction is None: self._logger.warn(__name__, 'Message context is empty') diff --git a/kdb-bot/src/modules/level/command/level_group.py b/kdb-bot/src/modules/level/command/level_group.py index 2e899c33..7434cdc9 100644 --- a/kdb-bot/src/modules/level/command/level_group.py +++ b/kdb-bot/src/modules/level/command/level_group.py @@ -83,20 +83,34 @@ class LevelGroup(DiscordCommandABC): self._logger.trace(__name__, f'Loaded command service: {type(self).__name__}') - async def _seed_levels(self, channel: discord.TextChannel): + async def _seed_levels(self, ctx: Context): # send message to ctx.channel because send_ctx_msg resolves ctx try: - await self._message_service.send_channel_message(channel, self._t.transform('modules.level.seeding_started')) + start = await self._message_service.send_ctx_msg( + ctx, + self._t.transform('modules.level.seeding_started'), + is_persistent=True + ) await self._level_seeder.seed() - await self._message_service.send_channel_message(channel, self._t.transform('modules.level.seeding_finished')) + + end = await self._message_service.send_ctx_msg( + ctx, + self._t.transform('modules.level.seeding_finished'), + is_persistent=True + ) + await self._message_service.delete_message(start) + await self._message_service.delete_message(end) + except Exception as e: self._logger.error(__name__, f'Level seeding failed', e) - await self._message_service.send_channel_message(channel, self._t.transform('modules.level.seeding_failed')) + await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.seeding_failed')) - async def _level_auto_complete(self, interaction: discord.Interaction, current: str) -> TList[app_commands.Choice[str]]: + async def _level_auto_complete(self, interaction: discord.Interaction, current: str) -> TList[ + app_commands.Choice[str]]: server = self._servers.get_server_by_discord_id(interaction.guild.id) levels = self._levels.get_levels_by_server_id(server.server_id).select(lambda l: l.name) - return [app_commands.Choice(name=level, value=level) for level in self._client_utils.get_auto_complete_list(levels, current)] + return [app_commands.Choice(name=level, value=level) for level in + self._client_utils.get_auto_complete_list(levels, current)] @commands.hybrid_group() @commands.guild_only() @@ -167,41 +181,53 @@ class LevelGroup(DiscordCommandABC): if levels.where(lambda l: l.name == level.name).first_or_default() is not None: self._logger.debug(__name__, f'Level with name {level.name} already exists') - await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.error.level_with_name_already_exists').format(level.name)) + await self._message_service.send_ctx_msg(ctx, self._t.transform( + 'modules.level.error.level_with_name_already_exists').format(level.name)) elif levels.where(lambda l: l.min_xp == level.min_xp).first_or_default() is not None: self._logger.debug(__name__, f'Level with min_xp {level.min_xp} already exists {level.name}') found_level = levels.where(lambda l: l.min_xp == level.min_xp).first_or_default() - await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.error.level_with_xp_already_exists').format(found_level.name, found_level.min_xp)) + await self._message_service.send_ctx_msg(ctx, self._t.transform( + 'modules.level.error.level_with_xp_already_exists').format(found_level.name, found_level.min_xp)) else: try: self._levels.add_level(level) self._db.save_changes() - self._logger.info(__name__, f'Saved level {name} with color {color}, min_xp {min_xp} and permissions {permissions}') + self._logger.info(__name__, + f'Saved level {name} with color {color}, min_xp {min_xp} and permissions {permissions}') except Exception as e: - self._logger.error(__name__, f'Could not save level {name} with color {color}, min_xp {min_xp} and permissions {permissions}', e) + self._logger.error(__name__, + f'Could not save level {name} with color {color}, min_xp {min_xp} and permissions {permissions}', + e) else: - await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.create.created').format(name, permissions)) - await self._seed_levels(ctx.channel) + await self._message_service.send_ctx_msg(ctx, + self._t.transform('modules.level.create.created').format(name, + permissions)) + await self._seed_levels(ctx) self._logger.trace(__name__, f'Finished command level create') @create.autocomplete('color') - async def create_color_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[app_commands.Choice[str]]: + async def create_color_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[ + app_commands.Choice[str]]: # value in rg format see: # https://discordpy.readthedocs.io/en/latest/api.html#discord.Colour.to_rgb - return [app_commands.Choice(name=self._t.transform(f'common.colors.{color}'), value=f'rgb({code[0]}, {code[1]}, {code[2]})') for color, code in self._colors] + return [app_commands.Choice(name=self._t.transform(f'common.colors.{color}'), + value=f'rgb({code[0]}, {code[1]}, {code[2]})') for color, code in self._colors] @level.command() @commands.guild_only() @CommandChecks.check_is_ready() @CommandChecks.check_is_member_admin() - async def edit(self, ctx: Context, level: str, name: str = None, color: str = None, min_xp: int = None, permissions: int = None): + async def edit(self, ctx: Context, level: str, name: str = None, color: str = None, min_xp: int = None, + permissions: int = None): self._logger.debug(__name__, f'Received command level edit {ctx}') server = self._servers.get_server_by_discord_id(ctx.guild.id) - level_from_db = self._levels.get_levels_by_server_id(server.server_id).where(lambda l: l.name == level).single_or_default() + level_from_db = self._levels.get_levels_by_server_id(server.server_id).where( + lambda l: l.name == level).single_or_default() if level_from_db is None: - await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.edit.not_found').format(level)) + await self._message_service.send_ctx_msg(ctx, + self._t.transform('modules.level.edit.not_found').format(level)) return guild: Guild = self._bot.guilds.where(lambda g: g == ctx.guild).single() @@ -214,7 +240,9 @@ class LevelGroup(DiscordCommandABC): try: level_from_db.color = hex(discord.Colour.from_str(color).value) except Exception as e: - await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.edit.color_invalid').format(color)) + await self._message_service.send_ctx_msg(ctx, + self._t.transform('modules.level.edit.color_invalid').format( + color)) self._logger.error(__name__, f'Error parsing color {color}', e) return @@ -225,33 +253,40 @@ class LevelGroup(DiscordCommandABC): try: level_from_db.permissions = discord.Permissions(permissions).value except Exception as e: - await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.edit.permission_invalid').format(permissions)) + await self._message_service.send_ctx_msg(ctx, self._t.transform( + 'modules.level.edit.permission_invalid').format(permissions)) self._logger.error(__name__, f'Error parsing permissions {permissions}', e) return try: self._levels.update_level(level_from_db) self._db.save_changes() - await role.edit(name=level_from_db.name, permissions=discord.Permissions(level_from_db.permissions), colour=discord.Colour(int(level_from_db.color, 16))) + await role.edit(name=level_from_db.name, permissions=discord.Permissions(level_from_db.permissions), + colour=discord.Colour(int(level_from_db.color, 16))) self._logger.info(__name__, f'Saved level {level_from_db.name} with color {level_from_db.color}, min_xp {level_from_db.min_xp} and permissions {level_from_db.permissions}') except Exception as e: - self._logger.error(__name__, f'Could not save level {level} with color {color}, min_xp {min_xp} and permissions {permissions}', e) + self._logger.error(__name__, + f'Could not save level {level} with color {color}, min_xp {min_xp} and permissions {permissions}', + e) else: await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.edit.edited').format(level)) - await self._seed_levels(ctx.channel) + await self._seed_levels(ctx) self._logger.trace(__name__, f'Finished command level edit') @edit.autocomplete('level') - async def edit_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[app_commands.Choice[str]]: + async def edit_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[ + app_commands.Choice[str]]: return await self._level_auto_complete(interaction, current) @edit.autocomplete('color') - async def edit_color_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[app_commands.Choice[str]]: + async def edit_color_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[ + app_commands.Choice[str]]: # value in rg format see: # https://discordpy.readthedocs.io/en/latest/api.html#discord.Colour.to_rgb - return [app_commands.Choice(name=self._t.transform(f'common.colors.{color}'), value=f'rgb({code[0]}, {code[1]}, {code[2]})') for color, code in self._colors] + return [app_commands.Choice(name=self._t.transform(f'common.colors.{color}'), + value=f'rgb({code[0]}, {code[1]}, {code[2]})') for color, code in self._colors] @level.command() @commands.guild_only() @@ -261,10 +296,13 @@ class LevelGroup(DiscordCommandABC): self._logger.debug(__name__, f'Received command level remove {ctx}') server = self._servers.get_server_by_discord_id(ctx.guild.id) - level_from_db = self._levels.get_levels_by_server_id(server.server_id).where(lambda l: l.name == level).first_or_default() + level_from_db = self._levels.get_levels_by_server_id(server.server_id).where( + lambda l: l.name == level).first_or_default() if level_from_db is None: self._logger.debug(__name__, f'level {level} not found') - await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.remove.error.not_found').format(level)) + await self._message_service.send_ctx_msg(ctx, + self._t.transform('modules.level.remove.error.not_found').format( + level)) self._logger.trace(__name__, f'Finished command level remove') return @@ -279,13 +317,15 @@ class LevelGroup(DiscordCommandABC): except Exception as e: self._logger.error(__name__, f'Could not remove level {level}', e) else: - await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.remove.success').format(level)) - await self._seed_levels(ctx.channel) + await self._message_service.send_ctx_msg(ctx, + self._t.transform('modules.level.remove.success').format(level)) + await self._seed_levels(ctx) self._logger.trace(__name__, f'Finished command level remove') @remove.autocomplete('level') - async def remove_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[app_commands.Choice[str]]: + async def remove_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[ + app_commands.Choice[str]]: return await self._level_auto_complete(interaction, current) @level.command() @@ -304,7 +344,8 @@ class LevelGroup(DiscordCommandABC): levels = self._levels.get_levels_by_server_id(server.server_id).order_by(lambda l: l.min_xp) if level == levels.first(): - await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.down.already_first').format(member.mention)) + await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.down.already_first').format( + member.mention)) self._logger.trace(__name__, f'Finished command level down') return @@ -313,11 +354,13 @@ class LevelGroup(DiscordCommandABC): user.xp = new_level.min_xp self._users.update_user(user) self._db.save_changes() - await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.down.success').format(member.mention, new_level.name)) + await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.down.success').format( + member.mention, new_level.name)) await self._level_service.set_level(user) except Exception as e: self._logger.error(__name__, f'Cannot level down {member.name} with level {level.name}', e) - await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.down.failed').format(member.mention)) + await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.down.failed').format( + member.mention)) self._logger.trace(__name__, f'Finished command level down') @@ -337,7 +380,8 @@ class LevelGroup(DiscordCommandABC): levels = self._levels.get_levels_by_server_id(server.server_id).order_by(lambda l: l.min_xp) if level.name == levels.last().name: - await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.up.already_last').format(member.mention)) + await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.up.already_last').format( + member.mention)) self._logger.trace(__name__, f'Finished command level up') return @@ -346,11 +390,13 @@ class LevelGroup(DiscordCommandABC): user.xp = new_level.min_xp self._users.update_user(user) self._db.save_changes() - await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.up.success').format(member.mention, new_level.name)) + await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.up.success').format( + member.mention, new_level.name)) await self._level_service.set_level(user) except Exception as e: self._logger.error(__name__, f'Cannot level up {member.name} with level {level.name}', e) - await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.up.failed').format(member.mention)) + await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.up.failed').format( + member.mention)) self._logger.trace(__name__, f'Finished command level up') @@ -367,15 +413,18 @@ class LevelGroup(DiscordCommandABC): server = self._servers.get_server_by_discord_id(ctx.guild.id) user = self._users.get_user_by_discord_id_and_server_id(member.id, server.server_id) current_level = self._level_service.get_level(user) - new_level = self._levels.get_levels_by_server_id(server.server_id).where(lambda l: l.name == level).single_or_default() + new_level = self._levels.get_levels_by_server_id(server.server_id).where( + lambda l: l.name == level).single_or_default() if new_level is None: - await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.set.not_found').format(level)) + await self._message_service.send_ctx_msg(ctx, + self._t.transform('modules.level.set.not_found').format(level)) self._logger.trace(__name__, f'Finished command level set') return if current_level.name == level: - await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.set.already_level').format(member.mention, level)) + await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.set.already_level').format( + member.mention, level)) self._logger.trace(__name__, f'Finished command level set') return @@ -383,14 +432,25 @@ class LevelGroup(DiscordCommandABC): user.xp = new_level.min_xp self._users.update_user(user) self._db.save_changes() - await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.set.success').format(member.mention, new_level.name)) + await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.set.success').format( + member.mention, new_level.name)) await self._level_service.set_level(user) except Exception as e: self._logger.error(__name__, f'Cannot set level {level} for {member.name}', e) - await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.set.failed').format(member.mention)) + await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.set.failed').format( + member.mention)) self._logger.trace(__name__, f'Finished command level set') @set.autocomplete('level') async def set_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[app_commands.Choice[str]]: return await self._level_auto_complete(interaction, current) + + @level.command() + @commands.guild_only() + @CommandChecks.check_is_ready() + @CommandChecks.check_is_member_moderator() + async def reload(self, ctx: Context): + self._logger.debug(__name__, f'Received command level reload {ctx}') + await self._seed_levels(ctx) + self._logger.trace(__name__, f'Finished command level reload')