From e3d3d200d62a3ffb25a6e5af3ccd18a8a72b6e5b Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Tue, 8 Nov 2022 21:54:33 +0100 Subject: [PATCH] Added logic to handle custom statistics #46 --- kdb-bot/src/bot/translation/de.json | 4 ++ .../src/bot_core/abc/message_service_abc.py | 14 +++-- .../src/bot_core/service/message_service.py | 30 +++++++++++ .../src/modules/stats/command/stats_group.py | 52 +++++++++++++++---- kdb-bot/src/modules/stats/model/statistic.py | 8 +-- .../stats/service/statistic_service.py | 33 ++++++++++-- kdb-bot/src/modules/stats/ui/__init__.py | 1 + .../modules/stats/ui/add_statistic_form.py | 35 +++++++++++++ 8 files changed, 154 insertions(+), 23 deletions(-) create mode 100644 kdb-bot/src/modules/stats/ui/__init__.py create mode 100644 kdb-bot/src/modules/stats/ui/add_statistic_form.py diff --git a/kdb-bot/src/bot/translation/de.json b/kdb-bot/src/bot/translation/de.json index 8ed8bd8452..0d6e785801 100644 --- a/kdb-bot/src/bot/translation/de.json +++ b/kdb-bot/src/bot/translation/de.json @@ -205,6 +205,10 @@ "statistic": "Statistik", "description": "Beschreibung", "failed": "Statistik kann nicht gezeigt werden :(" + }, + "add": { + "failed": "Statistik kann nicht hinzugefügt werden :(", + "success": "Statistik wurde hinzugefügt :D" } } }, 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 3e524d4de3..614dd1df82 100644 --- a/kdb-bot/src/bot_core/abc/message_service_abc.py +++ b/kdb-bot/src/bot_core/abc/message_service_abc.py @@ -3,6 +3,7 @@ from typing import Union import discord from cpl_query.extension import List +from discord import Interaction from discord.ext.commands import Context @@ -10,18 +11,21 @@ class MessageServiceABC(ABC): @abstractmethod def __init__(self): pass - + @abstractmethod 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, without_tracking=False): pass - + @abstractmethod 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 - + @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, without_tracking=True): pass + + @abstractmethod + async def send_interaction_msg(self, interaction: Interaction, message: Union[str, discord.Embed], is_persistent: bool = False, wait_before_delete: int = None, without_tracking=True): pass diff --git a/kdb-bot/src/bot_core/service/message_service.py b/kdb-bot/src/bot_core/service/message_service.py index 6a43f32cc6..5d211c595b 100644 --- a/kdb-bot/src/bot_core/service/message_service.py +++ b/kdb-bot/src/bot_core/service/message_service.py @@ -6,6 +6,7 @@ from cpl_core.configuration.configuration_abc import ConfigurationABC from cpl_core.database.context.database_context_abc import DatabaseContextABC from cpl_discord.service import DiscordBotServiceABC from cpl_query.extension import List +from discord import Interaction from discord.ext.commands import Context from bot_core.abc.message_service_abc import MessageServiceABC @@ -117,3 +118,32 @@ class MessageService(MessageServiceABC): if ctx.guild is not None: await self.delete_message(msg, without_tracking) + + async def send_interaction_msg(self, interaction: Interaction, message: Union[str, discord.Embed], is_persistent: bool = False, wait_before_delete: int = None, without_tracking=False): + if interaction is None: + self._logger.warn(__name__, 'Message context is empty') + self._logger.debug(__name__, f'Message: {message}') + return + + self._logger.debug(__name__, f'Try to send message\t\t{message}\n\tto: {interaction.channel}') + msg = None + try: + if isinstance(message, discord.Embed): + msg = await interaction.response.send_message(embed=message) + else: + msg = await interaction.response.send_message(message) + except Exception as e: + self._logger.error(__name__, f'Send message to channel {interaction.channel.id} failed', e) + else: + self._logger.info(__name__, f'Sent message to channel {interaction.channel.id}') + if not without_tracking and interaction.guild is not None: + self._clients.append_sent_message_count(self._bot.user.id, interaction.guild.id, 1) + self._db.save_changes() + + if wait_before_delete is not None: + await asyncio.sleep(wait_before_delete) + + if is_persistent: + return + + await self.delete_message(msg, without_tracking) diff --git a/kdb-bot/src/modules/stats/command/stats_group.py b/kdb-bot/src/modules/stats/command/stats_group.py index 01884c7a74..bb46b0a06f 100644 --- a/kdb-bot/src/modules/stats/command/stats_group.py +++ b/kdb-bot/src/modules/stats/command/stats_group.py @@ -1,3 +1,4 @@ +import textwrap from typing import List as TList import discord @@ -15,8 +16,25 @@ from bot_data.abc.server_repository_abc import ServerRepositoryABC from modules.permission.abc.permission_service_abc import PermissionServiceABC from modules.stats.model.statistic import Statistic from modules.stats.service.statistic_service import StatisticService -from modules.stats.test.user_xp_asc import user_xp_asc -from modules.stats.test.user_xp_desc import user_xp_desc +from modules.stats.ui.add_statistic_form import AddStatisticForm + +stats = List(Statistic, [ + Statistic( + 'Benutzer XP Aufsteigend', + 'Zeigt XP von jedem Benutzer, aufsteigend nach XP', + textwrap.dedent("""\ + result.header.append('Name') + result.header.append('XP') + + for user in users.order_by(lambda u: u.xp): + row = List(str) + member = guild.get_member(user.discord_id) + row.append(member.name) + row.append(str(user.xp)) + result.values.append(row) + """)), + Statistic('Benutzer XP Absteigend', 'Zeigt XP von jedem Benutzer, absteigend nach XP', '') +]) class StatsGroup(DiscordCommandABC): @@ -41,17 +59,12 @@ class StatsGroup(DiscordCommandABC): self._statistic = statistic self._servers = servers - self._stats = List(Statistic, [ - Statistic('Benutzer XP Aufsteigend', 'Zeigt XP von jedem Benutzer, aufsteigend nach XP', user_xp_asc), - Statistic('Benutzer XP Absteigend', 'Zeigt XP von jedem Benutzer, absteigend nach XP', user_xp_desc) - ]) - @commands.hybrid_group() @commands.guild_only() async def stats(self, ctx: Context): pass - @stats.command(alias='rules') + @stats.command() @commands.guild_only() async def list(self, ctx: Context, wait: int = None): self._logger.debug(__name__, f'Received command stats list {ctx}') @@ -92,8 +105,8 @@ class StatsGroup(DiscordCommandABC): try: server = self._servers.get_server_by_discord_id(ctx.guild.id) - statistic = self._stats.where(lambda s: s.name == name).single() - result = await self._statistic.execute(statistic.func, server) + statistic = stats.where(lambda s: s.name == name).single() + result = await self._statistic.execute(statistic.code, server) embed = discord.Embed( title=statistic.name, @@ -117,4 +130,21 @@ class StatsGroup(DiscordCommandABC): @view.autocomplete('name') async def view_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[app_commands.Choice[str]]: - return [app_commands.Choice(name=f'{statistic.name}: {statistic.description}', value=statistic.name) for statistic in self._stats] + return [app_commands.Choice(name=f'{statistic.name}: {statistic.description}', value=statistic.name) for statistic in stats] + + @stats.command() + @commands.guild_only() + async def add(self, ctx: Context, name: str): + self._logger.debug(__name__, f'Received command stats list {ctx}') + if not await self._client_utils.check_if_bot_is_ready_yet_and_respond(ctx): + return + + if not self._permissions.is_member_technician(ctx.author): + await self._message_service.send_ctx_msg(ctx, self._t.transform('common.no_permission_message')) + self._logger.trace(__name__, f'Finished command stats list') + return + + form = AddStatisticForm(stats, name, self._message_service, self._logger, self._t) + self._logger.trace(__name__, f'Finished stats command') + self._logger.trace(__name__, f'Started stats command form') + await ctx.interaction.response.send_modal(form) diff --git a/kdb-bot/src/modules/stats/model/statistic.py b/kdb-bot/src/modules/stats/model/statistic.py index 2db1b75d30..d1b6558dfc 100644 --- a/kdb-bot/src/modules/stats/model/statistic.py +++ b/kdb-bot/src/modules/stats/model/statistic.py @@ -3,10 +3,10 @@ from typing import Callable class Statistic: - def __init__(self, name: str, description: str, func: Callable): + def __init__(self, name: str, description: str, code: str): self._name = name self._description = description - self._func = func + self._code = code @property def name(self) -> str: @@ -17,5 +17,5 @@ class Statistic: return self._description @property - def func(self) -> Callable: - return self._func + def code(self) -> str: + return self._code diff --git a/kdb-bot/src/modules/stats/service/statistic_service.py b/kdb-bot/src/modules/stats/service/statistic_service.py index b0f59d6230..cee563351b 100644 --- a/kdb-bot/src/modules/stats/service/statistic_service.py +++ b/kdb-bot/src/modules/stats/service/statistic_service.py @@ -1,7 +1,8 @@ from abc import abstractmethod -from typing import Callable from cpl_discord.service import DiscordBotServiceABC +from cpl_query.extension import List +from discord import Guild from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC from bot_data.abc.client_repository_abc import ClientRepositoryABC @@ -11,7 +12,14 @@ from bot_data.abc.server_repository_abc import ServerRepositoryABC from bot_data.abc.user_joined_server_repository_abc import UserJoinedServerRepositoryABC from bot_data.abc.user_joined_voice_channel_abc import UserJoinedVoiceChannelRepositoryABC from bot_data.abc.user_repository_abc import UserRepositoryABC +from bot_data.model.auto_role import AutoRole +from bot_data.model.client import Client +from bot_data.model.known_user import KnownUser +from bot_data.model.level import Level from bot_data.model.server import Server +from bot_data.model.user import User +from bot_data.model.user_joined_server import UserJoinedServer +from bot_data.model.user_joined_voice_channel import UserJoinedVoiceChannel from modules.stats.model.statistic_result import StatisticResult @@ -39,10 +47,11 @@ class StatisticService: self._users = users self._bot = bot - async def execute(self, _f: Callable, server: Server) -> StatisticResult: + async def execute(self, code: str, server: Server) -> StatisticResult: guild = self._bot.guilds.where(lambda g: g.id == server.discord_server_id).single() - return await _f( + return await self.get_data( + code, self._auto_roles .get_auto_roles() .where(lambda x: x.server.server_id == server.server_id), @@ -67,3 +76,21 @@ class StatisticService: .where(lambda x: x.server.server_id == server.server_id), guild ) + + @staticmethod + async def get_data( + code: str, + auto_roles: List[AutoRole], + clients: List[Client], + known_users: List[KnownUser], + levels: List[Level], + servers: List[Server], + user_joined_servers: List[UserJoinedServer], + user_joined_voice_channel: List[UserJoinedVoiceChannel], + users: List[User], + guild: Guild + ) -> StatisticResult: + result = StatisticResult() + exec(code) + + return result diff --git a/kdb-bot/src/modules/stats/ui/__init__.py b/kdb-bot/src/modules/stats/ui/__init__.py new file mode 100644 index 0000000000..425ab6c146 --- /dev/null +++ b/kdb-bot/src/modules/stats/ui/__init__.py @@ -0,0 +1 @@ +# imports diff --git a/kdb-bot/src/modules/stats/ui/add_statistic_form.py b/kdb-bot/src/modules/stats/ui/add_statistic_form.py new file mode 100644 index 0000000000..9591fb2179 --- /dev/null +++ b/kdb-bot/src/modules/stats/ui/add_statistic_form.py @@ -0,0 +1,35 @@ +import discord +from cpl_query.extension import List +from cpl_translation import TranslatePipe +from discord import ui, TextStyle + +from bot_core.abc.message_service_abc import MessageServiceABC +from bot_core.logging.command_logger import CommandLogger +from modules.stats.model.statistic import Statistic + + +class AddStatisticForm(ui.Modal): + + description = ui.TextInput(label='Beschreibung', required=True) + code = ui.TextInput(label='Code', required=True, style=TextStyle.long) + + def __init__( + self, + stats: List[Statistic], + name: str, + message_service: MessageServiceABC, + logger: CommandLogger, + t: TranslatePipe, + ): + ui.Modal.__init__(self, title=name) + + self._stats = stats + self._name = name + self._message_service = message_service + self._logger = logger + self._t = t + + async def on_submit(self, interaction: discord.Interaction): + self._stats.append(Statistic(self._name, self.description.value, self.code.value)) + await self._message_service.send_interaction_msg(interaction, self._t.transform('modules.stats.add.success')) + self._logger.trace(__name__, f'Finished stats command form')