From f24fd5e88001c91bd52d4844e9a1c527cc6cd7ce Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Tue, 8 Nov 2022 20:28:38 +0100 Subject: [PATCH 01/27] Added logic to call statistics #46 --- kdb-bot/src/bot/translation/de.json | 12 +- kdb-bot/src/modules/stats/__init__.py | 1 + kdb-bot/src/modules/stats/command/__init__.py | 1 + .../src/modules/stats/command/stats_group.py | 133 ++++++++++++++++++ kdb-bot/src/modules/stats/model/__init__.py | 1 + kdb-bot/src/modules/stats/model/statistic.py | 21 +++ .../modules/stats/model/statistic_result.py | 24 ++++ kdb-bot/src/modules/stats/service/__init__.py | 1 + .../stats/service/statistic_service.py | 69 +++++++++ kdb-bot/src/modules/stats/stats.json | 46 ++++++ kdb-bot/src/modules/stats/test/user_xp_asc.py | 37 +++++ .../src/modules/stats/test/user_xp_desc.py | 37 +++++ 12 files changed, 382 insertions(+), 1 deletion(-) create mode 100644 kdb-bot/src/modules/stats/__init__.py create mode 100644 kdb-bot/src/modules/stats/command/__init__.py create mode 100644 kdb-bot/src/modules/stats/command/stats_group.py create mode 100644 kdb-bot/src/modules/stats/model/__init__.py create mode 100644 kdb-bot/src/modules/stats/model/statistic.py create mode 100644 kdb-bot/src/modules/stats/model/statistic_result.py create mode 100644 kdb-bot/src/modules/stats/service/__init__.py create mode 100644 kdb-bot/src/modules/stats/service/statistic_service.py create mode 100644 kdb-bot/src/modules/stats/stats.json create mode 100644 kdb-bot/src/modules/stats/test/user_xp_asc.py create mode 100644 kdb-bot/src/modules/stats/test/user_xp_desc.py diff --git a/kdb-bot/src/bot/translation/de.json b/kdb-bot/src/bot/translation/de.json index 92874768..8ed8bd84 100644 --- a/kdb-bot/src/bot/translation/de.json +++ b/kdb-bot/src/bot/translation/de.json @@ -195,7 +195,17 @@ } }, "database": {}, - "permission": { + "permission": {}, + "stats": { + "list": { + "statistic": "Statistik", + "description": "Beschreibung" + }, + "view": { + "statistic": "Statistik", + "description": "Beschreibung", + "failed": "Statistik kann nicht gezeigt werden :(" + } } }, "api": { diff --git a/kdb-bot/src/modules/stats/__init__.py b/kdb-bot/src/modules/stats/__init__.py new file mode 100644 index 00000000..ad5eca30 --- /dev/null +++ b/kdb-bot/src/modules/stats/__init__.py @@ -0,0 +1 @@ +# imports: diff --git a/kdb-bot/src/modules/stats/command/__init__.py b/kdb-bot/src/modules/stats/command/__init__.py new file mode 100644 index 00000000..425ab6c1 --- /dev/null +++ b/kdb-bot/src/modules/stats/command/__init__.py @@ -0,0 +1 @@ +# imports diff --git a/kdb-bot/src/modules/stats/command/stats_group.py b/kdb-bot/src/modules/stats/command/stats_group.py new file mode 100644 index 00000000..32b32f61 --- /dev/null +++ b/kdb-bot/src/modules/stats/command/stats_group.py @@ -0,0 +1,133 @@ +from typing import List as TList + +import discord +from cpl_discord.command import DiscordCommandABC +from cpl_query.extension import List +from cpl_translation import TranslatePipe +from discord import app_commands +from discord.ext import commands +from discord.ext.commands import Context + +from bot_core.abc.client_utils_service_abc import ClientUtilsServiceABC +from bot_core.abc.message_service_abc import MessageServiceABC +from bot_core.logging.command_logger import CommandLogger +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 + + +class StatsGroup(DiscordCommandABC): + + def __init__( + self, + logger: CommandLogger, + message_service: MessageServiceABC, + client_utils: ClientUtilsServiceABC, + translate: TranslatePipe, + permission_service: PermissionServiceABC, + statistic: StatisticService, + servers: ServerRepositoryABC, + ): + DiscordCommandABC.__init__(self) + + self._logger = logger + self._client_utils = client_utils + self._message_service = message_service + self._t = translate + self._permissions = permission_service + 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') + @commands.guild_only() + async def list(self, ctx: Context, wait: int = None): + 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_moderator(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 + + embed = discord.Embed( + title=self._t.transform('modules.auto_role.list.title'), + description=self._t.transform('modules.auto_role.list.description'), + color=int('ef9d0d', 16) + ) + + statistics = '' + descriptions = '' + for statistic in self._stats: + statistics += f'\n{statistic["Name"]}' + descriptions += f'\n{statistic["Description"]}' + + embed.add_field(name=self._t.transform('modules.stats.list.statistic'), value=statistics, inline=True) + embed.add_field(name=self._t.transform('modules.stats.list.description'), value=statistics, inline=True) + await self._message_service.send_ctx_msg(ctx, embed, wait_before_delete=wait) + self._logger.trace(__name__, f'Finished command stats list') + + @stats.command() + @commands.guild_only() + async def view(self, ctx: Context, name: str, wait: int = None): + self._logger.debug(__name__, f'Received command stats {ctx}:{name}') + if not await self._client_utils.check_if_bot_is_ready_yet_and_respond(ctx): + return + + if ctx.guild is None: + return + + 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) + + # headers = '' + # rows = '' + # for header in result.header: + # headers += f'\n{header}' + # + # for row in result.values: + # row_str = '' + # for column in row: + # row_str += f'\n{column}' + # rows += f'\n{row_str}' + + embed = discord.Embed( + title=statistic.name, + description=statistic.description, + color=int('ef9d0d', 16) + ) + + for i in range(result.header.count()): + header = result.header[i] + value = '' + for row in result.values: + value += f'\n{row[i]}' + embed.add_field(name=header, value=value, inline=True) + + # embed.add_field(name=self._t.transform('modules.auto_role.list.message_id'), value=rows, inline=True) + await self._message_service.send_ctx_msg(ctx, embed, wait_before_delete=wait) + # await self._message_service.send_ctx_msg(ctx, name, wait_before_delete=wait) + except Exception as e: + self._logger.error(__name__, f'Cannot view statistic {name}', e) + await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.stats.view.failed')) + + self._logger.trace(__name__, f'Finished stats command') + + @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] diff --git a/kdb-bot/src/modules/stats/model/__init__.py b/kdb-bot/src/modules/stats/model/__init__.py new file mode 100644 index 00000000..425ab6c1 --- /dev/null +++ b/kdb-bot/src/modules/stats/model/__init__.py @@ -0,0 +1 @@ +# imports diff --git a/kdb-bot/src/modules/stats/model/statistic.py b/kdb-bot/src/modules/stats/model/statistic.py new file mode 100644 index 00000000..2db1b75d --- /dev/null +++ b/kdb-bot/src/modules/stats/model/statistic.py @@ -0,0 +1,21 @@ +from typing import Callable + + +class Statistic: + + def __init__(self, name: str, description: str, func: Callable): + self._name = name + self._description = description + self._func = func + + @property + def name(self) -> str: + return self._name + + @property + def description(self) -> str: + return self._description + + @property + def func(self) -> Callable: + return self._func diff --git a/kdb-bot/src/modules/stats/model/statistic_result.py b/kdb-bot/src/modules/stats/model/statistic_result.py new file mode 100644 index 00000000..c2c53742 --- /dev/null +++ b/kdb-bot/src/modules/stats/model/statistic_result.py @@ -0,0 +1,24 @@ +from cpl_query.extension import List + + +class StatisticResult: + + def __init__(self): + self._header = List(str) + self._values = List(List) + + @property + def header(self) -> List[str]: + return self._header + + @header.setter + def header(self, value: List[str]): + self._header = value + + @property + def values(self) -> List[List]: + return self._values + + @values.setter + def values(self, value: List[List]): + self._values = value diff --git a/kdb-bot/src/modules/stats/service/__init__.py b/kdb-bot/src/modules/stats/service/__init__.py new file mode 100644 index 00000000..425ab6c1 --- /dev/null +++ b/kdb-bot/src/modules/stats/service/__init__.py @@ -0,0 +1 @@ +# imports diff --git a/kdb-bot/src/modules/stats/service/statistic_service.py b/kdb-bot/src/modules/stats/service/statistic_service.py new file mode 100644 index 00000000..b0f59d62 --- /dev/null +++ b/kdb-bot/src/modules/stats/service/statistic_service.py @@ -0,0 +1,69 @@ +from abc import abstractmethod +from typing import Callable + +from cpl_discord.service import DiscordBotServiceABC + +from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC +from bot_data.abc.client_repository_abc import ClientRepositoryABC +from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC +from bot_data.abc.level_repository_abc import LevelRepositoryABC +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.server import Server +from modules.stats.model.statistic_result import StatisticResult + + +class StatisticService: + + def __init__( + self, + auto_roles: AutoRoleRepositoryABC, + clients: ClientRepositoryABC, + known_users: KnownUserRepositoryABC, + levels: LevelRepositoryABC, + servers: ServerRepositoryABC, + user_joined_servers: UserJoinedServerRepositoryABC, + user_joined_voice_channel: UserJoinedVoiceChannelRepositoryABC, + users: UserRepositoryABC, + bot: DiscordBotServiceABC, + ): + self._auto_roles = auto_roles + self._clients = clients + self._known_users = known_users + self._levels = levels + self._servers = servers + self._user_joined_servers = user_joined_servers + self._user_joined_voice_channel = user_joined_voice_channel + self._users = users + self._bot = bot + + async def execute(self, _f: Callable, server: Server) -> StatisticResult: + guild = self._bot.guilds.where(lambda g: g.id == server.discord_server_id).single() + + return await _f( + self._auto_roles + .get_auto_roles() + .where(lambda x: x.server.server_id == server.server_id), + self._clients + .get_clients() + .where(lambda x: x.server.server_id == server.server_id), + self._known_users.get_users(), + self._levels + .get_levels() + .where(lambda x: x.server.server_id == server.server_id), + self._servers + .get_servers() + .where(lambda x: x.server_id == server.server_id), + self._user_joined_servers + .get_user_joined_servers() + .where(lambda x: x.user.server.server_id == server.server_id), + self._user_joined_voice_channel + .get_user_joined_voice_channels() + .where(lambda x: x.user.server.server_id == server.server_id), + self._users + .get_users() + .where(lambda x: x.server.server_id == server.server_id), + guild + ) diff --git a/kdb-bot/src/modules/stats/stats.json b/kdb-bot/src/modules/stats/stats.json new file mode 100644 index 00000000..c4fec974 --- /dev/null +++ b/kdb-bot/src/modules/stats/stats.json @@ -0,0 +1,46 @@ +{ + "ProjectSettings": { + "Name": "stats", + "Version": { + "Major": "0", + "Minor": "0", + "Micro": "0" + }, + "Author": "", + "AuthorEmail": "", + "Description": "", + "LongDescription": "", + "URL": "", + "CopyrightDate": "", + "CopyrightName": "", + "LicenseName": "", + "LicenseDescription": "", + "Dependencies": [ + "cpl-core>=2022.10.0.post7" + ], + "DevDependencies": [ + "cpl-cli>=2022.10.1" + ], + "PythonVersion": ">=3.10.4", + "PythonPath": { + "linux": "" + }, + "Classifiers": [] + }, + "BuildSettings": { + "ProjectType": "library", + "SourcePath": "", + "OutputPath": "../../dist", + "Main": "stats.main", + "EntryPoint": "stats", + "IncludePackageData": false, + "Included": [], + "Excluded": [ + "*/__pycache__", + "*/logs", + "*/tests" + ], + "PackageData": {}, + "ProjectReferences": [] + } +} \ No newline at end of file diff --git a/kdb-bot/src/modules/stats/test/user_xp_asc.py b/kdb-bot/src/modules/stats/test/user_xp_asc.py new file mode 100644 index 00000000..9ee1219e --- /dev/null +++ b/kdb-bot/src/modules/stats/test/user_xp_asc.py @@ -0,0 +1,37 @@ +from cpl_discord.container import Guild +from cpl_query.extension import List + +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 + + +async def user_xp_asc( + 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() + 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) + + return result diff --git a/kdb-bot/src/modules/stats/test/user_xp_desc.py b/kdb-bot/src/modules/stats/test/user_xp_desc.py new file mode 100644 index 00000000..32cd0f3e --- /dev/null +++ b/kdb-bot/src/modules/stats/test/user_xp_desc.py @@ -0,0 +1,37 @@ +from cpl_discord.container import Guild +from cpl_query.extension import List + +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 + + +async def user_xp_desc( + 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() + result.header.append('Name') + result.header.append('XP') + + for user in users.order_by_descending(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) + + return result From 5dca2e5fed1bf9b7b2ec57cece49906617e2e47d Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Tue, 8 Nov 2022 20:28:44 +0100 Subject: [PATCH 02/27] Added logic to call statistics #46 --- kdb-bot/cpl-workspace.json | 3 ++- kdb-bot/src/bot/module_list.py | 2 ++ kdb-bot/src/bot_api/abc/__init__.py | 2 +- kdb-bot/src/bot_core/abc/__init__.py | 2 +- .../configuration/feature_flags_enum.py | 2 +- .../configuration/feature_flags_settings.py | 1 + kdb-bot/src/bot_data/abc/__init__.py | 2 +- kdb-bot/src/modules/base/abc/__init__.py | 2 +- .../src/modules/permission/abc/__init__.py | 2 +- kdb-bot/src/modules/stats/stats_module.py | 24 +++++++++++++++++++ kdb-bot/src/modules/stats/test/__init__.py | 0 11 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 kdb-bot/src/modules/stats/stats_module.py create mode 100644 kdb-bot/src/modules/stats/test/__init__.py diff --git a/kdb-bot/cpl-workspace.json b/kdb-bot/cpl-workspace.json index fa9a4332..6587cbf6 100644 --- a/kdb-bot/cpl-workspace.json +++ b/kdb-bot/cpl-workspace.json @@ -3,6 +3,7 @@ "DefaultProject": "bot", "Projects": { "bot": "src/bot/bot.json", + "bot-api": "src/bot_api/bot-api.json", "bot-core": "src/bot_core/bot-core.json", "bot-data": "src/bot_data/bot-data.json", "auto-role": "src/modules/auto_role/auto-role.json", @@ -11,7 +12,7 @@ "database": "src/modules/database/database.json", "level": "src/modules/level/level.json", "permission": "src/modules/permission/permission.json", - "bot-api": "src/bot_api/bot-api.json", + "stats": "src/modules/stats/stats.json", "get-version": "tools/get_version/get-version.json", "post-build": "tools/post_build/post-build.json", "set-version": "tools/set_version/set-version.json" diff --git a/kdb-bot/src/bot/module_list.py b/kdb-bot/src/bot/module_list.py index 575824f3..3eb60cd3 100644 --- a/kdb-bot/src/bot/module_list.py +++ b/kdb-bot/src/bot/module_list.py @@ -10,6 +10,7 @@ from modules.boot_log.boot_log_module import BootLogModule from modules.database.database_module import DatabaseModule from modules.level.level_module import LevelModule from modules.permission.permission_module import PermissionModule +from modules.stats.stats_module import StatsModule class ModuleList: @@ -26,6 +27,7 @@ class ModuleList: LevelModule, PermissionModule, ApiModule, + StatsModule, # has to be last! BootLogModule, CoreExtensionModule, diff --git a/kdb-bot/src/bot_api/abc/__init__.py b/kdb-bot/src/bot_api/abc/__init__.py index 20368eb6..ad4cf626 100644 --- a/kdb-bot/src/bot_api/abc/__init__.py +++ b/kdb-bot/src/bot_api/abc/__init__.py @@ -11,7 +11,7 @@ Discord bot for the Keksdose discord Server """ -__title__ = 'bot_api.abc' +__title__ = 'bot_api.service' __author__ = 'Sven Heidemann' __license__ = 'MIT' __copyright__ = 'Copyright (c) 2022 sh-edraft.de' diff --git a/kdb-bot/src/bot_core/abc/__init__.py b/kdb-bot/src/bot_core/abc/__init__.py index 4056210a..1266f4f1 100644 --- a/kdb-bot/src/bot_core/abc/__init__.py +++ b/kdb-bot/src/bot_core/abc/__init__.py @@ -11,7 +11,7 @@ Discord bot for the Keksdose discord Server """ -__title__ = 'bot_core.abc' +__title__ = 'bot_core.service' __author__ = 'Sven Heidemann' __license__ = 'MIT' __copyright__ = 'Copyright (c) 2022 sh-edraft.de' diff --git a/kdb-bot/src/bot_core/configuration/feature_flags_enum.py b/kdb-bot/src/bot_core/configuration/feature_flags_enum.py index 226ca1d8..f04e5ca7 100644 --- a/kdb-bot/src/bot_core/configuration/feature_flags_enum.py +++ b/kdb-bot/src/bot_core/configuration/feature_flags_enum.py @@ -2,7 +2,6 @@ from enum import Enum class FeatureFlagsEnum(Enum): - # modules api_module = 'ApiModule' admin_module = 'AdminModule' @@ -16,6 +15,7 @@ class FeatureFlagsEnum(Enum): level_module = 'LevelModule' moderator_module = 'ModeratorModule' permission_module = 'PermissionModule' + stats_module = 'StatsModule' # features api_only = 'ApiOnly' presence = 'Presence' diff --git a/kdb-bot/src/bot_core/configuration/feature_flags_settings.py b/kdb-bot/src/bot_core/configuration/feature_flags_settings.py index 1861f9a1..dff28c6e 100644 --- a/kdb-bot/src/bot_core/configuration/feature_flags_settings.py +++ b/kdb-bot/src/bot_core/configuration/feature_flags_settings.py @@ -25,6 +25,7 @@ class FeatureFlagsSettings(ConfigurationModelABC): FeatureFlagsEnum.database_module.value: True, # 02.10.2022 #48 FeatureFlagsEnum.moderator_module.value: False, # 02.10.2022 #48 FeatureFlagsEnum.permission_module.value: True, # 02.10.2022 #48 + FeatureFlagsEnum.stats_module.value: True, # 08.11.2022 #46 # features FeatureFlagsEnum.api_only.value: False, # 13.10.2022 #70 FeatureFlagsEnum.presence.value: True, # 03.10.2022 #56 diff --git a/kdb-bot/src/bot_data/abc/__init__.py b/kdb-bot/src/bot_data/abc/__init__.py index 7209be45..c2b4323a 100644 --- a/kdb-bot/src/bot_data/abc/__init__.py +++ b/kdb-bot/src/bot_data/abc/__init__.py @@ -11,7 +11,7 @@ Discord bot for the Keksdose discord Server """ -__title__ = 'bot_data.abc' +__title__ = 'bot_data.service' __author__ = 'Sven Heidemann' __license__ = 'MIT' __copyright__ = 'Copyright (c) 2022 sh-edraft.de' diff --git a/kdb-bot/src/modules/base/abc/__init__.py b/kdb-bot/src/modules/base/abc/__init__.py index ccc3e98a..41181517 100644 --- a/kdb-bot/src/modules/base/abc/__init__.py +++ b/kdb-bot/src/modules/base/abc/__init__.py @@ -11,7 +11,7 @@ Discord bot for the Keksdose discord Server """ -__title__ = 'modules.base.abc' +__title__ = 'modules.base.service' __author__ = 'Sven Heidemann' __license__ = 'MIT' __copyright__ = 'Copyright (c) 2022 sh-edraft.de' diff --git a/kdb-bot/src/modules/permission/abc/__init__.py b/kdb-bot/src/modules/permission/abc/__init__.py index 294d277c..930246c1 100644 --- a/kdb-bot/src/modules/permission/abc/__init__.py +++ b/kdb-bot/src/modules/permission/abc/__init__.py @@ -11,7 +11,7 @@ Discord bot for the Keksdose discord Server """ -__title__ = 'modules.permission.abc' +__title__ = 'modules.permission.service' __author__ = 'Sven Heidemann' __license__ = 'MIT' __copyright__ = 'Copyright (c) 2022 sh-edraft.de' diff --git a/kdb-bot/src/modules/stats/stats_module.py b/kdb-bot/src/modules/stats/stats_module.py new file mode 100644 index 00000000..4c60b02a --- /dev/null +++ b/kdb-bot/src/modules/stats/stats_module.py @@ -0,0 +1,24 @@ +from cpl_core.configuration import ConfigurationABC +from cpl_core.dependency_injection import ServiceCollectionABC +from cpl_core.environment import ApplicationEnvironmentABC +from cpl_discord.service.discord_collection_abc import DiscordCollectionABC + +from bot_core.abc.module_abc import ModuleABC +from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum +from modules.stats.command.stats_group import StatsGroup +from modules.stats.service.statistic_service import StatisticService + + +class StatsModule(ModuleABC): + + def __init__(self, dc: DiscordCollectionABC): + ModuleABC.__init__(self, dc, FeatureFlagsEnum.stats_module) + + def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC): + pass + + def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): + services.add_transient(StatisticService) + # commands + self._dc.add_command(StatsGroup) + # events diff --git a/kdb-bot/src/modules/stats/test/__init__.py b/kdb-bot/src/modules/stats/test/__init__.py new file mode 100644 index 00000000..e69de29b From 95e33109fef9375fc6c543c807eb96b2d68aa813 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Tue, 8 Nov 2022 20:34:47 +0100 Subject: [PATCH 03/27] Removed old code #46 --- kdb-bot/src/modules/stats/command/stats_group.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/kdb-bot/src/modules/stats/command/stats_group.py b/kdb-bot/src/modules/stats/command/stats_group.py index 32b32f61..01884c7a 100644 --- a/kdb-bot/src/modules/stats/command/stats_group.py +++ b/kdb-bot/src/modules/stats/command/stats_group.py @@ -95,17 +95,6 @@ class StatsGroup(DiscordCommandABC): statistic = self._stats.where(lambda s: s.name == name).single() result = await self._statistic.execute(statistic.func, server) - # headers = '' - # rows = '' - # for header in result.header: - # headers += f'\n{header}' - # - # for row in result.values: - # row_str = '' - # for column in row: - # row_str += f'\n{column}' - # rows += f'\n{row_str}' - embed = discord.Embed( title=statistic.name, description=statistic.description, @@ -119,9 +108,7 @@ class StatsGroup(DiscordCommandABC): value += f'\n{row[i]}' embed.add_field(name=header, value=value, inline=True) - # embed.add_field(name=self._t.transform('modules.auto_role.list.message_id'), value=rows, inline=True) await self._message_service.send_ctx_msg(ctx, embed, wait_before_delete=wait) - # await self._message_service.send_ctx_msg(ctx, name, wait_before_delete=wait) except Exception as e: self._logger.error(__name__, f'Cannot view statistic {name}', e) await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.stats.view.failed')) From e3d3d200d62a3ffb25a6e5af3ccd18a8a72b6e5b Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Tue, 8 Nov 2022 21:54:33 +0100 Subject: [PATCH 04/27] 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 8ed8bd84..0d6e7858 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 3e524d4d..614dd1df 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 6a43f32c..5d211c59 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 01884c7a..bb46b0a0 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 2db1b75d..d1b6558d 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 b0f59d62..cee56335 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 00000000..425ab6c1 --- /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 00000000..9591fb21 --- /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') From 95a64732f31e8dd8506eb8adb36e234f59cd4f14 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Tue, 8 Nov 2022 22:06:36 +0100 Subject: [PATCH 05/27] Fixed typo #46 --- kdb-bot/src/modules/stats/command/stats_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kdb-bot/src/modules/stats/command/stats_group.py b/kdb-bot/src/modules/stats/command/stats_group.py index bb46b0a0..b1051209 100644 --- a/kdb-bot/src/modules/stats/command/stats_group.py +++ b/kdb-bot/src/modules/stats/command/stats_group.py @@ -84,7 +84,7 @@ class StatsGroup(DiscordCommandABC): statistics = '' descriptions = '' - for statistic in self._stats: + for statistic in stats: statistics += f'\n{statistic["Name"]}' descriptions += f'\n{statistic["Description"]}' From 7e5706137e0114b38421bb36db915d6fadefa357 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 9 Nov 2022 17:18:14 +0100 Subject: [PATCH 06/27] Fixed level seeder #46 --- kdb-bot/src/modules/level/level_seeder.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/kdb-bot/src/modules/level/level_seeder.py b/kdb-bot/src/modules/level/level_seeder.py index 870a7b30..e3d9a8df 100644 --- a/kdb-bot/src/modules/level/level_seeder.py +++ b/kdb-bot/src/modules/level/level_seeder.py @@ -1,7 +1,6 @@ import discord -from cpl_discord.container import Guild, Role +from cpl_discord.container import Guild from cpl_discord.service import DiscordBotServiceABC -from cpl_query.extension import List from discord import Permissions, Colour from bot_core.logging.database_logger import DatabaseLogger @@ -35,7 +34,7 @@ class LevelSeeder(DataSeederABC): await guild.create_role(name=level.name, colour=Colour(int(level.color, 16)), hoist=False, mentionable=True, permissions=Permissions(level.permissions)) self._logger.info(__name__, f'Created level {level.name}') - if self._levels.find_levels_by_server_id(server.server_id).where(lambda l: l == level).first_or_default() is not None: + if self._levels.find_levels_by_server_id(server.server_id).where(lambda l: l.name == level.name).first_or_default() is not None: self._levels.add_level(level) except discord.errors.Forbidden as e: self._logger.error(__name__, f'Creating level failed', e) From c407c59c1ad0b7033f498a0c54a07390d91855c9 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 9 Nov 2022 17:59:55 +0100 Subject: [PATCH 07/27] Added stats edit command #46 --- kdb-bot/src/bot/translation/de.json | 4 +++ .../src/bot_core/service/message_service.py | 7 ++-- .../src/modules/stats/command/stats_group.py | 32 +++++++++++++++++-- kdb-bot/src/modules/stats/model/statistic.py | 8 +++++ .../modules/stats/ui/add_statistic_form.py | 27 ++++++++++++++-- 5 files changed, 69 insertions(+), 9 deletions(-) diff --git a/kdb-bot/src/bot/translation/de.json b/kdb-bot/src/bot/translation/de.json index 0d6e7858..0768197a 100644 --- a/kdb-bot/src/bot/translation/de.json +++ b/kdb-bot/src/bot/translation/de.json @@ -209,6 +209,10 @@ "add": { "failed": "Statistik kann nicht hinzugefügt werden :(", "success": "Statistik wurde hinzugefügt :D" + }, + "edit": { + "failed": "Statistik kann nicht bearbeitet werden :(", + "success": "Statistik wurde gespeichert :D" } } }, diff --git a/kdb-bot/src/bot_core/service/message_service.py b/kdb-bot/src/bot_core/service/message_service.py index 5d211c59..04f33bfb 100644 --- a/kdb-bot/src/bot_core/service/message_service.py +++ b/kdb-bot/src/bot_core/service/message_service.py @@ -126,12 +126,11 @@ class MessageService(MessageServiceABC): 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) + await interaction.response.send_message(embed=message) else: - msg = await interaction.response.send_message(message) + 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: @@ -146,4 +145,4 @@ class MessageService(MessageServiceABC): if is_persistent: return - await self.delete_message(msg, without_tracking) + await self.delete_message(await interaction.original_response(), 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 b1051209..58105c0b 100644 --- a/kdb-bot/src/modules/stats/command/stats_group.py +++ b/kdb-bot/src/modules/stats/command/stats_group.py @@ -135,16 +135,42 @@ class StatsGroup(DiscordCommandABC): @stats.command() @commands.guild_only() async def add(self, ctx: Context, name: str): - self._logger.debug(__name__, f'Received command stats list {ctx}') + self._logger.debug(__name__, f'Received command stats add {ctx}: {name}') 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') + self._logger.trace(__name__, f'Finished command stats add') 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'Finished stats add command') self._logger.trace(__name__, f'Started stats command form') await ctx.interaction.response.send_modal(form) + + @stats.command() + @commands.guild_only() + async def edit(self, ctx: Context, name: str): + self._logger.debug(__name__, f'Received command stats edit {ctx}: {name}') + 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 edit') + return + + try: + statistic = stats.where(lambda s: s.name == name).single() + form = AddStatisticForm(stats, name, self._message_service, self._logger, self._t, code=statistic.code, description=statistic.description) + self._logger.trace(__name__, f'Finished stats edit command') + self._logger.trace(__name__, f'Started stats command form') + await ctx.interaction.response.send_modal(form) + except Exception as e: + self._logger.error(__name__, f'Cannot view statistic {name}', e) + await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.stats.edit.failed')) + + @edit.autocomplete('name') + async def edit_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 stats] diff --git a/kdb-bot/src/modules/stats/model/statistic.py b/kdb-bot/src/modules/stats/model/statistic.py index d1b6558d..1ab0afe4 100644 --- a/kdb-bot/src/modules/stats/model/statistic.py +++ b/kdb-bot/src/modules/stats/model/statistic.py @@ -16,6 +16,14 @@ class Statistic: def description(self) -> str: return self._description + @description.setter + def description(self, value: str): + self._description = value + @property def code(self) -> str: return self._code + + @code.setter + def code(self, value: str): + self._code = value diff --git a/kdb-bot/src/modules/stats/ui/add_statistic_form.py b/kdb-bot/src/modules/stats/ui/add_statistic_form.py index 9591fb21..fe053241 100644 --- a/kdb-bot/src/modules/stats/ui/add_statistic_form.py +++ b/kdb-bot/src/modules/stats/ui/add_statistic_form.py @@ -20,6 +20,8 @@ class AddStatisticForm(ui.Modal): message_service: MessageServiceABC, logger: CommandLogger, t: TranslatePipe, + code: str = None, + description: str = None, ): ui.Modal.__init__(self, title=name) @@ -29,7 +31,28 @@ class AddStatisticForm(ui.Modal): self._logger = logger self._t = t + if code is not None: + self.code.default = code + + if description is not None: + self.description.default = description + 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')) + statistic = self._stats.where(lambda s: s.name == self._name).single_or_default() + try: + if statistic is None: + 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')) + return + + statistic.description = self.description.value + statistic.code = self.code.value + await self._message_service.send_interaction_msg(interaction, self._t.transform('modules.stats.edit.success')) + except Exception as e: + self._logger.error(__name__, f'Save statistic {self._name} failed', e) + if statistic is None: + await self._message_service.send_interaction_msg(interaction, self._t.transform('modules.stats.add.failed')) + else: + await self._message_service.send_interaction_msg(interaction, self._t.transform('modules.stats.edit.failed')) + self._logger.trace(__name__, f'Finished stats command form') From c9ce81701f383adabb6c8febaad2f4fefb555409 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 9 Nov 2022 18:01:14 +0100 Subject: [PATCH 08/27] Added stats remove command #46 --- .../src/modules/stats/command/stats_group.py | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/kdb-bot/src/modules/stats/command/stats_group.py b/kdb-bot/src/modules/stats/command/stats_group.py index 58105c0b..67c8469a 100644 --- a/kdb-bot/src/modules/stats/command/stats_group.py +++ b/kdb-bot/src/modules/stats/command/stats_group.py @@ -168,9 +168,32 @@ class StatsGroup(DiscordCommandABC): self._logger.trace(__name__, f'Started stats command form') await ctx.interaction.response.send_modal(form) except Exception as e: - self._logger.error(__name__, f'Cannot view statistic {name}', e) + self._logger.error(__name__, f'Cannot edit statistic {name}', e) await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.stats.edit.failed')) @edit.autocomplete('name') async def edit_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 stats] + + @stats.command() + @commands.guild_only() + async def remove(self, ctx: Context, name: str): + self._logger.debug(__name__, f'Received command stats remove {ctx}: {name}') + 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 remove') + return + + try: + stats.remove(stats.where(lambda s: s.name == name).single()) + self._logger.trace(__name__, f'Finished stats remove command') + except Exception as e: + self._logger.error(__name__, f'Cannot remove statistic {name}', e) + await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.stats.edit.failed')) + + @remove.autocomplete('name') + async def edit_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 stats] From fa7e41469b7291a98b353d220f22dc9e7bb0860d Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 9 Nov 2022 18:22:27 +0100 Subject: [PATCH 09/27] Added stats migration #46 --- .../src/bot/startup_migration_extension.py | 2 ++ .../src/bot_data/migration/stats_migration.py | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 kdb-bot/src/bot_data/migration/stats_migration.py diff --git a/kdb-bot/src/bot/startup_migration_extension.py b/kdb-bot/src/bot/startup_migration_extension.py index a5d75b6d..724db39a 100644 --- a/kdb-bot/src/bot/startup_migration_extension.py +++ b/kdb-bot/src/bot/startup_migration_extension.py @@ -8,6 +8,7 @@ from bot_data.migration.api_migration import ApiMigration from bot_data.migration.auto_role_migration import AutoRoleMigration from bot_data.migration.initial_migration import InitialMigration from bot_data.migration.level_migration import LevelMigration +from bot_data.migration.stats_migration import StatsMigration from bot_data.service.migration_service import MigrationService @@ -25,3 +26,4 @@ class StartupMigrationExtension(StartupExtensionABC): services.add_transient(MigrationABC, AutoRoleMigration) # 03.10.2022 #54 - 0.2.2 services.add_transient(MigrationABC, ApiMigration) # 15.10.2022 #70 - 0.3.0 services.add_transient(MigrationABC, LevelMigration) # 06.11.2022 #25 - 0.3.0 + services.add_transient(MigrationABC, StatsMigration) # 09.11.2022 #46 - 0.3.0 diff --git a/kdb-bot/src/bot_data/migration/stats_migration.py b/kdb-bot/src/bot_data/migration/stats_migration.py new file mode 100644 index 00000000..92ba17ab --- /dev/null +++ b/kdb-bot/src/bot_data/migration/stats_migration.py @@ -0,0 +1,36 @@ +from bot_core.logging.database_logger import DatabaseLogger +from bot_data.abc.migration_abc import MigrationABC +from bot_data.db_context import DBContext + + +class StatsMigration(MigrationABC): + name = '0.3_StatsMigration' + + def __init__(self, logger: DatabaseLogger, db: DBContext): + MigrationABC.__init__(self) + self._logger = logger + self._db = db + self._cursor = db.cursor + + def upgrade(self): + self._logger.debug(__name__, 'Running upgrade') + + self._cursor.execute( + str(f""" + CREATE TABLE IF NOT EXISTS `Statistics` ( + `Id` BIGINT NOT NULL AUTO_INCREMENT, + `Name` VARCHAR(255) NOT NULL, + `Description` VARCHAR(255) NOT NULL, + `Code` LONGTEXT NOT NULL, + `ServerId` BIGINT, + `CreatedAt` DATETIME(6), + `LastModifiedAt` DATETIME(6), + PRIMARY KEY(`Id`), + FOREIGN KEY (`ServerId`) REFERENCES `Servers`(`ServerId`) + ); + """) + ) + + def downgrade(self): + self._cursor.execute('DROP TABLE `Statistics`;') + From a43676e9bfe691051a6b0ccc082e3baabd881f8d Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 9 Nov 2022 18:36:48 +0100 Subject: [PATCH 10/27] Added stats table #46 --- kdb-bot/src/bot_data/model/statistic.py | 108 ++++++++++++++++++ .../src/modules/stats/command/stats_group.py | 2 +- kdb-bot/src/modules/stats/model/statistic.py | 29 ----- .../modules/stats/ui/add_statistic_form.py | 6 +- 4 files changed, 112 insertions(+), 33 deletions(-) create mode 100644 kdb-bot/src/bot_data/model/statistic.py delete mode 100644 kdb-bot/src/modules/stats/model/statistic.py diff --git a/kdb-bot/src/bot_data/model/statistic.py b/kdb-bot/src/bot_data/model/statistic.py new file mode 100644 index 00000000..1c34a264 --- /dev/null +++ b/kdb-bot/src/bot_data/model/statistic.py @@ -0,0 +1,108 @@ +from datetime import datetime +from typing import Optional + +from cpl_core.database import TableABC + +from bot_data.model.server import Server + + +class Statistic(TableABC): + + def __init__(self, name: str, description: str, code: str, server: Server, created_at: datetime=None, modified_at: datetime=None, id=0): + self._id = id + self._name = name + self._description = description + self._code = code + self._server = server + + TableABC.__init__(self) + self._created_at = created_at if created_at is not None else self._created_at + self._modified_at = modified_at if modified_at is not None else self._modified_at + + @property + def id(self) -> int: + return self._id + + @property + def name(self) -> str: + return self._name + + @property + def description(self) -> str: + return self._description + + @description.setter + def description(self, value: str): + self._description = value + + @property + def code(self) -> str: + return self._code + + @code.setter + def code(self, value: str): + self._code = value + + @property + def server(self) -> Server: + return self._server + + @staticmethod + def get_select_all_string() -> str: + return str(f""" + SELECT * FROM `Statistics`; + """) + + @staticmethod + def get_select_by_id_string(id: int) -> str: + return str(f""" + SELECT * FROM `Statistics` + WHERE `Id` = {id}; + """) + + @staticmethod + def get_select_by_name_string(name: str, s_id: int) -> str: + return str(f""" + SELECT * FROM `Statistics` + WHERE `Name` = {name}; + """) + + @staticmethod + def get_select_by_server_string(s_id: int) -> str: + return str(f""" + SELECT * FROM `Statistics` + WHERE `ServerId` = {s_id}; + """) + + @property + def insert_string(self) -> str: + return str(f""" + INSERT INTO `Statistics` ( + `Name`, `Description`, `Code`, `ServerId`, `CreatedAt`, `LastModifiedAt` + ) VALUES ( + '{self._name}', + '{self._description}', + '{self._code}', + {self._server.server_id}, + '{self._created_at}', + '{self._modified_at}' + ); + """) + + @property + def udpate_string(self) -> str: + return str(f""" + UPDATE `Statistics` + SET `Name` = {self._name}, + `Description` = '{self._description}' + `Code` = '{self._code}' + `LastModifiedAt` = '{self._modified_at}' + WHERE `Id` = {self._id}; + """) + + @property + def delete_string(self) -> str: + return str(f""" + DELETE FROM `Statistics` + WHERE `Id` = {self._id}; + """) diff --git a/kdb-bot/src/modules/stats/command/stats_group.py b/kdb-bot/src/modules/stats/command/stats_group.py index 67c8469a..0d61f249 100644 --- a/kdb-bot/src/modules/stats/command/stats_group.py +++ b/kdb-bot/src/modules/stats/command/stats_group.py @@ -13,8 +13,8 @@ from bot_core.abc.client_utils_service_abc import ClientUtilsServiceABC from bot_core.abc.message_service_abc import MessageServiceABC from bot_core.logging.command_logger import CommandLogger from bot_data.abc.server_repository_abc import ServerRepositoryABC +from bot_data.model.statistic import Statistic 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.ui.add_statistic_form import AddStatisticForm diff --git a/kdb-bot/src/modules/stats/model/statistic.py b/kdb-bot/src/modules/stats/model/statistic.py deleted file mode 100644 index 1ab0afe4..00000000 --- a/kdb-bot/src/modules/stats/model/statistic.py +++ /dev/null @@ -1,29 +0,0 @@ -from typing import Callable - - -class Statistic: - - def __init__(self, name: str, description: str, code: str): - self._name = name - self._description = description - self._code = code - - @property - def name(self) -> str: - return self._name - - @property - def description(self) -> str: - return self._description - - @description.setter - def description(self, value: str): - self._description = value - - @property - def code(self) -> str: - return self._code - - @code.setter - def code(self, value: str): - self._code = value diff --git a/kdb-bot/src/modules/stats/ui/add_statistic_form.py b/kdb-bot/src/modules/stats/ui/add_statistic_form.py index fe053241..073fc9d4 100644 --- a/kdb-bot/src/modules/stats/ui/add_statistic_form.py +++ b/kdb-bot/src/modules/stats/ui/add_statistic_form.py @@ -5,7 +5,7 @@ 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 +from modules.stats.model.statisticmodel import StatisticModel class AddStatisticForm(ui.Modal): @@ -15,7 +15,7 @@ class AddStatisticForm(ui.Modal): def __init__( self, - stats: List[Statistic], + stats: List[StatisticModel], name: str, message_service: MessageServiceABC, logger: CommandLogger, @@ -41,7 +41,7 @@ class AddStatisticForm(ui.Modal): statistic = self._stats.where(lambda s: s.name == self._name).single_or_default() try: if statistic is None: - self._stats.append(Statistic(self._name, self.description.value, self.code.value)) + self._stats.append(StatisticModel(self._name, self.description.value, self.code.value)) await self._message_service.send_interaction_msg(interaction, self._t.transform('modules.stats.add.success')) return From c7967c6904928c80d451ac8e248837e3325c17b1 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 9 Nov 2022 18:49:11 +0100 Subject: [PATCH 11/27] Added stats repo #46 --- .../bot_data/abc/statistic_repository_abc.py | 36 ++++++++ .../service/statistic_repository_service.py | 88 +++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 kdb-bot/src/bot_data/abc/statistic_repository_abc.py create mode 100644 kdb-bot/src/bot_data/service/statistic_repository_service.py diff --git a/kdb-bot/src/bot_data/abc/statistic_repository_abc.py b/kdb-bot/src/bot_data/abc/statistic_repository_abc.py new file mode 100644 index 00000000..bf16055d --- /dev/null +++ b/kdb-bot/src/bot_data/abc/statistic_repository_abc.py @@ -0,0 +1,36 @@ +from abc import ABC, abstractmethod +from typing import Optional + +from cpl_query.extension import List + +from bot_data.model.statistic import Statistic + + +class StatisticRepositoryABC(ABC): + + @abstractmethod + def __init__(self): pass + + @abstractmethod + def get_statistics(self) -> List[Statistic]: pass + + @abstractmethod + def get_statistics_by_server_id(self, server_id: int) -> List[Statistic]: pass + + @abstractmethod + def get_statistic_by_id(self, id: int) -> Statistic: pass + + @abstractmethod + def get_statistic_by_name(self, name: str, server_id: int) -> Statistic: pass + + @abstractmethod + def find_statistic_by_name(self, name: str, server_id: int) -> Optional[Statistic]: pass + + @abstractmethod + def add_statistic(self, statistic: Statistic): pass + + @abstractmethod + def update_statistic(self, statistic: Statistic): pass + + @abstractmethod + def delete_statistic(self, statistic: Statistic): pass diff --git a/kdb-bot/src/bot_data/service/statistic_repository_service.py b/kdb-bot/src/bot_data/service/statistic_repository_service.py new file mode 100644 index 00000000..222bdf38 --- /dev/null +++ b/kdb-bot/src/bot_data/service/statistic_repository_service.py @@ -0,0 +1,88 @@ +from typing import Optional + +from cpl_core.database.context import DatabaseContextABC +from cpl_query.extension import List + +from bot_core.logging.database_logger import DatabaseLogger +from bot_data.abc.server_repository_abc import ServerRepositoryABC +from bot_data.abc.statistic_repository_abc import StatisticRepositoryABC +from bot_data.model.statistic import Statistic + + +class StatisticRepositoryService(StatisticRepositoryABC): + + def __init__(self, logger: DatabaseLogger, db_context: DatabaseContextABC, statistics: ServerRepositoryABC): + self._logger = logger + self._context = db_context + + self._statistics = statistics + + StatisticRepositoryABC.__init__(self) + + @staticmethod + def _get_value_from_result(value: any) -> Optional[any]: + if isinstance(value, str) and 'NULL' in value: + return None + + return value + + def _statistic_from_result(self, sql_result: tuple) -> Statistic: + statistic = Statistic( + self._get_value_from_result(sql_result[1]), + self._get_value_from_result(sql_result[2]), + self._get_value_from_result(sql_result[3]), + self._statistics.get_server_by_id(sql_result[4]), + id=self._get_value_from_result(sql_result[0]) + ) + + return statistic + + def get_statistics(self) -> List[Statistic]: + statistics = List(Statistic) + self._logger.trace(__name__, f'Send SQL command: {Statistic.get_select_all_string()}') + results = self._context.select(Statistic.get_select_all_string()) + for result in results: + statistics.append(self._statistic_from_result(result)) + + return statistics + + def get_statistics_by_server_id(self, server_id: int) -> List[Statistic]: + statistics = List(Statistic) + self._logger.trace(__name__, f'Send SQL command: {Statistic.get_select_by_server_string(server_id)}') + results = self._context.select(Statistic.get_select_by_server_string(server_id)) + for result in results: + statistics.append(self._statistic_from_result(result)) + + return statistics + + def get_statistic_by_id(self, id: int) -> Statistic: + self._logger.trace(__name__, f'Send SQL command: {Statistic.get_select_by_id_string(id)}') + result = self._context.select(Statistic.get_select_by_id_string(id))[0] + return self._statistic_from_result(result) + + def get_statistic_by_name(self, name: str, server_id: int) -> Statistic: + self._logger.trace(__name__, f'Send SQL command: {Statistic.get_select_by_name_string(name, server_id)}') + result = self._context.select(Statistic.get_select_by_name_string(name, server_id))[0] + return self._statistic_from_result(result) + + def find_statistic_by_name(self, name: str, server_id: int) -> Optional[Statistic]: + self._logger.trace(__name__, f'Send SQL command: {Statistic.get_select_by_name_string(name, server_id)}') + result = self._context.select(Statistic.get_select_by_name_string(name, server_id)) + if result is None or len(result) == 0: + return None + + result = result[0] + + return self._statistic_from_result(result) + + def add_statistic(self, statistic: Statistic): + self._logger.trace(__name__, f'Send SQL command: {statistic.insert_string}') + self._context.cursor.execute(statistic.insert_string) + + def update_statistic(self, statistic: Statistic): + self._logger.trace(__name__, f'Send SQL command: {statistic.udpate_string}') + self._context.cursor.execute(statistic.udpate_string) + + def delete_statistic(self, statistic: Statistic): + self._logger.trace(__name__, f'Send SQL command: {statistic.delete_string}') + self._context.cursor.execute(statistic.delete_string) From bd7983189267ab75602cc4b45decdaeb437bf43b Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 9 Nov 2022 19:23:10 +0100 Subject: [PATCH 12/27] Added db to stats #46 --- kdb-bot/src/bot/translation/de.json | 7 +- kdb-bot/src/bot_data/data_module.py | 3 + kdb-bot/src/bot_data/model/statistic.py | 17 ++--- .../service/statistic_repository_service.py | 7 +- .../src/modules/stats/command/stats_group.py | 66 +++++++++++-------- .../modules/stats/ui/add_statistic_form.py | 26 ++++++-- 6 files changed, 85 insertions(+), 41 deletions(-) diff --git a/kdb-bot/src/bot/translation/de.json b/kdb-bot/src/bot/translation/de.json index 0768197a..58bb23d4 100644 --- a/kdb-bot/src/bot/translation/de.json +++ b/kdb-bot/src/bot/translation/de.json @@ -199,7 +199,8 @@ "stats": { "list": { "statistic": "Statistik", - "description": "Beschreibung" + "description": "Beschreibung", + "nothing_found": "Keine Statistiken gefunden." }, "view": { "statistic": "Statistik", @@ -213,6 +214,10 @@ "edit": { "failed": "Statistik kann nicht bearbeitet werden :(", "success": "Statistik wurde gespeichert :D" + }, + "remove": { + "failed": "Statistik kann nicht gelöscht werden :(", + "success": "Statistik wurde gelöscht :D" } } }, diff --git a/kdb-bot/src/bot_data/data_module.py b/kdb-bot/src/bot_data/data_module.py index 0668bffe..e32d8039 100644 --- a/kdb-bot/src/bot_data/data_module.py +++ b/kdb-bot/src/bot_data/data_module.py @@ -11,6 +11,7 @@ from bot_data.abc.client_repository_abc import ClientRepositoryABC from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC from bot_data.abc.level_repository_abc import LevelRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC +from bot_data.abc.statistic_repository_abc import StatisticRepositoryABC 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 @@ -21,6 +22,7 @@ from bot_data.service.known_user_repository_service import KnownUserRepositorySe from bot_data.service.level_repository_service import LevelRepositoryService from bot_data.service.seeder_service import SeederService from bot_data.service.server_repository_service import ServerRepositoryService +from bot_data.service.statistic_repository_service import StatisticRepositoryService from bot_data.service.user_joined_server_repository_service import UserJoinedServerRepositoryService from bot_data.service.user_joined_voice_channel_service import UserJoinedVoiceChannelRepositoryService from bot_data.service.user_repository_service import UserRepositoryService @@ -44,5 +46,6 @@ class DataModule(ModuleABC): services.add_transient(UserJoinedVoiceChannelRepositoryABC, UserJoinedVoiceChannelRepositoryService) services.add_transient(AutoRoleRepositoryABC, AutoRoleRepositoryService) services.add_transient(LevelRepositoryABC, LevelRepositoryService) + services.add_transient(StatisticRepositoryABC, StatisticRepositoryService) services.add_transient(SeederService) diff --git a/kdb-bot/src/bot_data/model/statistic.py b/kdb-bot/src/bot_data/model/statistic.py index 1c34a264..11011237 100644 --- a/kdb-bot/src/bot_data/model/statistic.py +++ b/kdb-bot/src/bot_data/model/statistic.py @@ -1,7 +1,7 @@ from datetime import datetime -from typing import Optional from cpl_core.database import TableABC +from cpl_core.utils import CredentialManager from bot_data.model.server import Server @@ -12,7 +12,7 @@ class Statistic(TableABC): self._id = id self._name = name self._description = description - self._code = code + self._code = CredentialManager.encrypt(code) self._server = server TableABC.__init__(self) @@ -37,11 +37,11 @@ class Statistic(TableABC): @property def code(self) -> str: - return self._code + return CredentialManager.decrypt(self._code) @code.setter def code(self, value: str): - self._code = value + self._code = CredentialManager.encrypt(value) @property def server(self) -> Server: @@ -64,7 +64,8 @@ class Statistic(TableABC): def get_select_by_name_string(name: str, s_id: int) -> str: return str(f""" SELECT * FROM `Statistics` - WHERE `Name` = {name}; + WHERE `ServerId` = {s_id} + AND `Name` = '{name}'; """) @staticmethod @@ -93,9 +94,9 @@ class Statistic(TableABC): def udpate_string(self) -> str: return str(f""" UPDATE `Statistics` - SET `Name` = {self._name}, - `Description` = '{self._description}' - `Code` = '{self._code}' + SET `Name` = '{self._name}', + `Description` = '{self._description}', + `Code` = '{self._code}', `LastModifiedAt` = '{self._modified_at}' WHERE `Id` = {self._id}; """) diff --git a/kdb-bot/src/bot_data/service/statistic_repository_service.py b/kdb-bot/src/bot_data/service/statistic_repository_service.py index 222bdf38..8f31dbe2 100644 --- a/kdb-bot/src/bot_data/service/statistic_repository_service.py +++ b/kdb-bot/src/bot_data/service/statistic_repository_service.py @@ -1,6 +1,7 @@ from typing import Optional from cpl_core.database.context import DatabaseContextABC +from cpl_core.utils import CredentialManager from cpl_query.extension import List from bot_core.logging.database_logger import DatabaseLogger @@ -27,10 +28,14 @@ class StatisticRepositoryService(StatisticRepositoryABC): return value def _statistic_from_result(self, sql_result: tuple) -> Statistic: + code = self._get_value_from_result(sql_result[3]) + if code is not None: + code = CredentialManager.decrypt(code) + statistic = Statistic( self._get_value_from_result(sql_result[1]), self._get_value_from_result(sql_result[2]), - self._get_value_from_result(sql_result[3]), + code, self._statistics.get_server_by_id(sql_result[4]), id=self._get_value_from_result(sql_result[0]) ) diff --git a/kdb-bot/src/modules/stats/command/stats_group.py b/kdb-bot/src/modules/stats/command/stats_group.py index 0d61f249..96734119 100644 --- a/kdb-bot/src/modules/stats/command/stats_group.py +++ b/kdb-bot/src/modules/stats/command/stats_group.py @@ -1,9 +1,8 @@ -import textwrap from typing import List as TList import discord +from cpl_core.database.context import DatabaseContextABC from cpl_discord.command import DiscordCommandABC -from cpl_query.extension import List from cpl_translation import TranslatePipe from discord import app_commands from discord.ext import commands @@ -13,29 +12,11 @@ from bot_core.abc.client_utils_service_abc import ClientUtilsServiceABC from bot_core.abc.message_service_abc import MessageServiceABC from bot_core.logging.command_logger import CommandLogger from bot_data.abc.server_repository_abc import ServerRepositoryABC -from bot_data.model.statistic import Statistic +from bot_data.abc.statistic_repository_abc import StatisticRepositoryABC from modules.permission.abc.permission_service_abc import PermissionServiceABC from modules.stats.service.statistic_service import StatisticService 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): @@ -48,6 +29,8 @@ class StatsGroup(DiscordCommandABC): permission_service: PermissionServiceABC, statistic: StatisticService, servers: ServerRepositoryABC, + stats: StatisticRepositoryABC, + db: DatabaseContextABC, ): DiscordCommandABC.__init__(self) @@ -58,6 +41,8 @@ class StatsGroup(DiscordCommandABC): self._permissions = permission_service self._statistic = statistic self._servers = servers + self._stats = stats + self._db = db @commands.hybrid_group() @commands.guild_only() @@ -76,17 +61,27 @@ class StatsGroup(DiscordCommandABC): self._logger.trace(__name__, f'Finished command stats list') return + if ctx.guild is None: + return + embed = discord.Embed( title=self._t.transform('modules.auto_role.list.title'), description=self._t.transform('modules.auto_role.list.description'), color=int('ef9d0d', 16) ) + server = self._servers.get_server_by_discord_id(ctx.guild.id) + stats = self._stats.get_statistics_by_server_id(server.server_id) + + if stats.count() == 0: + await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.stats.list.nothing_found')) + return + statistics = '' descriptions = '' for statistic in stats: - statistics += f'\n{statistic["Name"]}' - descriptions += f'\n{statistic["Description"]}' + statistics += f'\n{statistic.name}' + descriptions += f'\n{statistic.description}' embed.add_field(name=self._t.transform('modules.stats.list.statistic'), value=statistics, inline=True) embed.add_field(name=self._t.transform('modules.stats.list.description'), value=statistics, inline=True) @@ -105,6 +100,7 @@ class StatsGroup(DiscordCommandABC): try: server = self._servers.get_server_by_discord_id(ctx.guild.id) + stats = self._stats.get_statistics_by_server_id(server.server_id) statistic = stats.where(lambda s: s.name == name).single() result = await self._statistic.execute(statistic.code, server) @@ -130,6 +126,8 @@ class StatsGroup(DiscordCommandABC): @view.autocomplete('name') async def view_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[app_commands.Choice[str]]: + server = self._servers.get_server_by_discord_id(interaction.guild.id) + stats = self._stats.get_statistics_by_server_id(server.server_id) return [app_commands.Choice(name=f'{statistic.name}: {statistic.description}', value=statistic.name) for statistic in stats] @stats.command() @@ -144,7 +142,11 @@ class StatsGroup(DiscordCommandABC): self._logger.trace(__name__, f'Finished command stats add') return - form = AddStatisticForm(stats, name, self._message_service, self._logger, self._t) + if ctx.guild is None: + return + + server = self._servers.get_server_by_discord_id(ctx.guild.id) + form = AddStatisticForm(server, self._stats, self._db, name, self._message_service, self._logger, self._t) self._logger.trace(__name__, f'Finished stats add command') self._logger.trace(__name__, f'Started stats command form') await ctx.interaction.response.send_modal(form) @@ -162,8 +164,10 @@ class StatsGroup(DiscordCommandABC): return try: + server = self._servers.get_server_by_discord_id(ctx.guild.id) + stats = self._stats.get_statistics_by_server_id(server.server_id) statistic = stats.where(lambda s: s.name == name).single() - form = AddStatisticForm(stats, name, self._message_service, self._logger, self._t, code=statistic.code, description=statistic.description) + form = AddStatisticForm(server, self._stats, self._db, name, self._message_service, self._logger, self._t, code=statistic.code, description=statistic.description) self._logger.trace(__name__, f'Finished stats edit command') self._logger.trace(__name__, f'Started stats command form') await ctx.interaction.response.send_modal(form) @@ -173,6 +177,8 @@ class StatsGroup(DiscordCommandABC): @edit.autocomplete('name') async def edit_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[app_commands.Choice[str]]: + server = self._servers.get_server_by_discord_id(interaction.guild.id) + stats = self._stats.get_statistics_by_server_id(server.server_id) return [app_commands.Choice(name=f'{statistic.name}: {statistic.description}', value=statistic.name) for statistic in stats] @stats.command() @@ -188,12 +194,18 @@ class StatsGroup(DiscordCommandABC): return try: - stats.remove(stats.where(lambda s: s.name == name).single()) + server = self._servers.get_server_by_discord_id(ctx.guild.id) + statistic = self._stats.get_statistic_by_name(name, server.server_id) + self._stats.delete_statistic(statistic) + self._db.save_changes() + await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.stats.remove.success')) self._logger.trace(__name__, f'Finished stats remove command') except Exception as e: self._logger.error(__name__, f'Cannot remove statistic {name}', e) - await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.stats.edit.failed')) + await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.stats.remove.failed')) @remove.autocomplete('name') async def edit_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[app_commands.Choice[str]]: + server = self._servers.get_server_by_discord_id(interaction.guild.id) + stats = self._stats.get_statistics_by_server_id(server.server_id) return [app_commands.Choice(name=f'{statistic.name}: {statistic.description}', value=statistic.name) for statistic in stats] diff --git a/kdb-bot/src/modules/stats/ui/add_statistic_form.py b/kdb-bot/src/modules/stats/ui/add_statistic_form.py index 073fc9d4..625bac82 100644 --- a/kdb-bot/src/modules/stats/ui/add_statistic_form.py +++ b/kdb-bot/src/modules/stats/ui/add_statistic_form.py @@ -1,11 +1,14 @@ import discord +from cpl_core.database.context import DatabaseContextABC 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.statisticmodel import StatisticModel +from bot_data.abc.statistic_repository_abc import StatisticRepositoryABC +from bot_data.model.server import Server +from bot_data.model.statistic import Statistic class AddStatisticForm(ui.Modal): @@ -15,7 +18,9 @@ class AddStatisticForm(ui.Modal): def __init__( self, - stats: List[StatisticModel], + server: Server, + stats: StatisticRepositoryABC, + db: DatabaseContextABC, name: str, message_service: MessageServiceABC, logger: CommandLogger, @@ -25,7 +30,9 @@ class AddStatisticForm(ui.Modal): ): ui.Modal.__init__(self, title=name) + self._server = server self._stats = stats + self._db = db self._name = name self._message_service = message_service self._logger = logger @@ -38,15 +45,26 @@ class AddStatisticForm(ui.Modal): self.description.default = description async def on_submit(self, interaction: discord.Interaction): - statistic = self._stats.where(lambda s: s.name == self._name).single_or_default() + statistic = self._stats.get_statistics_by_server_id(self._server.server_id).where(lambda s: s.name == self._name).single_or_default() + + if interaction.guild is None: + if statistic is None: + await self._message_service.send_interaction_msg(interaction, self._t.transform('modules.stats.add.failed')) + else: + await self._message_service.send_interaction_msg(interaction, self._t.transform('modules.stats.edit.failed')) + return + try: if statistic is None: - self._stats.append(StatisticModel(self._name, self.description.value, self.code.value)) + self._stats.add_statistic(Statistic(self._name, self.description.value, self.code.value, self._server)) + self._db.save_changes() await self._message_service.send_interaction_msg(interaction, self._t.transform('modules.stats.add.success')) return statistic.description = self.description.value statistic.code = self.code.value + self._stats.update_statistic(statistic) + self._db.save_changes() await self._message_service.send_interaction_msg(interaction, self._t.transform('modules.stats.edit.success')) except Exception as e: self._logger.error(__name__, f'Save statistic {self._name} failed', e) From c6a5d49942747ca77992d872c84661b03782e04b Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Tue, 8 Nov 2022 20:28:38 +0100 Subject: [PATCH 13/27] Added logic to call statistics #46 --- kdb-bot/src/bot/translation/de.json | 12 +- kdb-bot/src/modules/stats/__init__.py | 1 + kdb-bot/src/modules/stats/command/__init__.py | 1 + .../src/modules/stats/command/stats_group.py | 133 ++++++++++++++++++ kdb-bot/src/modules/stats/model/__init__.py | 1 + kdb-bot/src/modules/stats/model/statistic.py | 21 +++ .../modules/stats/model/statistic_result.py | 24 ++++ kdb-bot/src/modules/stats/service/__init__.py | 1 + .../stats/service/statistic_service.py | 69 +++++++++ kdb-bot/src/modules/stats/stats.json | 46 ++++++ kdb-bot/src/modules/stats/test/user_xp_asc.py | 37 +++++ .../src/modules/stats/test/user_xp_desc.py | 37 +++++ 12 files changed, 382 insertions(+), 1 deletion(-) create mode 100644 kdb-bot/src/modules/stats/__init__.py create mode 100644 kdb-bot/src/modules/stats/command/__init__.py create mode 100644 kdb-bot/src/modules/stats/command/stats_group.py create mode 100644 kdb-bot/src/modules/stats/model/__init__.py create mode 100644 kdb-bot/src/modules/stats/model/statistic.py create mode 100644 kdb-bot/src/modules/stats/model/statistic_result.py create mode 100644 kdb-bot/src/modules/stats/service/__init__.py create mode 100644 kdb-bot/src/modules/stats/service/statistic_service.py create mode 100644 kdb-bot/src/modules/stats/stats.json create mode 100644 kdb-bot/src/modules/stats/test/user_xp_asc.py create mode 100644 kdb-bot/src/modules/stats/test/user_xp_desc.py diff --git a/kdb-bot/src/bot/translation/de.json b/kdb-bot/src/bot/translation/de.json index 45281393..edb2e87c 100644 --- a/kdb-bot/src/bot/translation/de.json +++ b/kdb-bot/src/bot/translation/de.json @@ -217,7 +217,17 @@ } }, "database": {}, - "permission": { + "permission": {}, + "stats": { + "list": { + "statistic": "Statistik", + "description": "Beschreibung" + }, + "view": { + "statistic": "Statistik", + "description": "Beschreibung", + "failed": "Statistik kann nicht gezeigt werden :(" + } } }, "api": { diff --git a/kdb-bot/src/modules/stats/__init__.py b/kdb-bot/src/modules/stats/__init__.py new file mode 100644 index 00000000..ad5eca30 --- /dev/null +++ b/kdb-bot/src/modules/stats/__init__.py @@ -0,0 +1 @@ +# imports: diff --git a/kdb-bot/src/modules/stats/command/__init__.py b/kdb-bot/src/modules/stats/command/__init__.py new file mode 100644 index 00000000..425ab6c1 --- /dev/null +++ b/kdb-bot/src/modules/stats/command/__init__.py @@ -0,0 +1 @@ +# imports diff --git a/kdb-bot/src/modules/stats/command/stats_group.py b/kdb-bot/src/modules/stats/command/stats_group.py new file mode 100644 index 00000000..32b32f61 --- /dev/null +++ b/kdb-bot/src/modules/stats/command/stats_group.py @@ -0,0 +1,133 @@ +from typing import List as TList + +import discord +from cpl_discord.command import DiscordCommandABC +from cpl_query.extension import List +from cpl_translation import TranslatePipe +from discord import app_commands +from discord.ext import commands +from discord.ext.commands import Context + +from bot_core.abc.client_utils_service_abc import ClientUtilsServiceABC +from bot_core.abc.message_service_abc import MessageServiceABC +from bot_core.logging.command_logger import CommandLogger +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 + + +class StatsGroup(DiscordCommandABC): + + def __init__( + self, + logger: CommandLogger, + message_service: MessageServiceABC, + client_utils: ClientUtilsServiceABC, + translate: TranslatePipe, + permission_service: PermissionServiceABC, + statistic: StatisticService, + servers: ServerRepositoryABC, + ): + DiscordCommandABC.__init__(self) + + self._logger = logger + self._client_utils = client_utils + self._message_service = message_service + self._t = translate + self._permissions = permission_service + 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') + @commands.guild_only() + async def list(self, ctx: Context, wait: int = None): + 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_moderator(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 + + embed = discord.Embed( + title=self._t.transform('modules.auto_role.list.title'), + description=self._t.transform('modules.auto_role.list.description'), + color=int('ef9d0d', 16) + ) + + statistics = '' + descriptions = '' + for statistic in self._stats: + statistics += f'\n{statistic["Name"]}' + descriptions += f'\n{statistic["Description"]}' + + embed.add_field(name=self._t.transform('modules.stats.list.statistic'), value=statistics, inline=True) + embed.add_field(name=self._t.transform('modules.stats.list.description'), value=statistics, inline=True) + await self._message_service.send_ctx_msg(ctx, embed, wait_before_delete=wait) + self._logger.trace(__name__, f'Finished command stats list') + + @stats.command() + @commands.guild_only() + async def view(self, ctx: Context, name: str, wait: int = None): + self._logger.debug(__name__, f'Received command stats {ctx}:{name}') + if not await self._client_utils.check_if_bot_is_ready_yet_and_respond(ctx): + return + + if ctx.guild is None: + return + + 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) + + # headers = '' + # rows = '' + # for header in result.header: + # headers += f'\n{header}' + # + # for row in result.values: + # row_str = '' + # for column in row: + # row_str += f'\n{column}' + # rows += f'\n{row_str}' + + embed = discord.Embed( + title=statistic.name, + description=statistic.description, + color=int('ef9d0d', 16) + ) + + for i in range(result.header.count()): + header = result.header[i] + value = '' + for row in result.values: + value += f'\n{row[i]}' + embed.add_field(name=header, value=value, inline=True) + + # embed.add_field(name=self._t.transform('modules.auto_role.list.message_id'), value=rows, inline=True) + await self._message_service.send_ctx_msg(ctx, embed, wait_before_delete=wait) + # await self._message_service.send_ctx_msg(ctx, name, wait_before_delete=wait) + except Exception as e: + self._logger.error(__name__, f'Cannot view statistic {name}', e) + await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.stats.view.failed')) + + self._logger.trace(__name__, f'Finished stats command') + + @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] diff --git a/kdb-bot/src/modules/stats/model/__init__.py b/kdb-bot/src/modules/stats/model/__init__.py new file mode 100644 index 00000000..425ab6c1 --- /dev/null +++ b/kdb-bot/src/modules/stats/model/__init__.py @@ -0,0 +1 @@ +# imports diff --git a/kdb-bot/src/modules/stats/model/statistic.py b/kdb-bot/src/modules/stats/model/statistic.py new file mode 100644 index 00000000..2db1b75d --- /dev/null +++ b/kdb-bot/src/modules/stats/model/statistic.py @@ -0,0 +1,21 @@ +from typing import Callable + + +class Statistic: + + def __init__(self, name: str, description: str, func: Callable): + self._name = name + self._description = description + self._func = func + + @property + def name(self) -> str: + return self._name + + @property + def description(self) -> str: + return self._description + + @property + def func(self) -> Callable: + return self._func diff --git a/kdb-bot/src/modules/stats/model/statistic_result.py b/kdb-bot/src/modules/stats/model/statistic_result.py new file mode 100644 index 00000000..c2c53742 --- /dev/null +++ b/kdb-bot/src/modules/stats/model/statistic_result.py @@ -0,0 +1,24 @@ +from cpl_query.extension import List + + +class StatisticResult: + + def __init__(self): + self._header = List(str) + self._values = List(List) + + @property + def header(self) -> List[str]: + return self._header + + @header.setter + def header(self, value: List[str]): + self._header = value + + @property + def values(self) -> List[List]: + return self._values + + @values.setter + def values(self, value: List[List]): + self._values = value diff --git a/kdb-bot/src/modules/stats/service/__init__.py b/kdb-bot/src/modules/stats/service/__init__.py new file mode 100644 index 00000000..425ab6c1 --- /dev/null +++ b/kdb-bot/src/modules/stats/service/__init__.py @@ -0,0 +1 @@ +# imports diff --git a/kdb-bot/src/modules/stats/service/statistic_service.py b/kdb-bot/src/modules/stats/service/statistic_service.py new file mode 100644 index 00000000..b0f59d62 --- /dev/null +++ b/kdb-bot/src/modules/stats/service/statistic_service.py @@ -0,0 +1,69 @@ +from abc import abstractmethod +from typing import Callable + +from cpl_discord.service import DiscordBotServiceABC + +from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC +from bot_data.abc.client_repository_abc import ClientRepositoryABC +from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC +from bot_data.abc.level_repository_abc import LevelRepositoryABC +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.server import Server +from modules.stats.model.statistic_result import StatisticResult + + +class StatisticService: + + def __init__( + self, + auto_roles: AutoRoleRepositoryABC, + clients: ClientRepositoryABC, + known_users: KnownUserRepositoryABC, + levels: LevelRepositoryABC, + servers: ServerRepositoryABC, + user_joined_servers: UserJoinedServerRepositoryABC, + user_joined_voice_channel: UserJoinedVoiceChannelRepositoryABC, + users: UserRepositoryABC, + bot: DiscordBotServiceABC, + ): + self._auto_roles = auto_roles + self._clients = clients + self._known_users = known_users + self._levels = levels + self._servers = servers + self._user_joined_servers = user_joined_servers + self._user_joined_voice_channel = user_joined_voice_channel + self._users = users + self._bot = bot + + async def execute(self, _f: Callable, server: Server) -> StatisticResult: + guild = self._bot.guilds.where(lambda g: g.id == server.discord_server_id).single() + + return await _f( + self._auto_roles + .get_auto_roles() + .where(lambda x: x.server.server_id == server.server_id), + self._clients + .get_clients() + .where(lambda x: x.server.server_id == server.server_id), + self._known_users.get_users(), + self._levels + .get_levels() + .where(lambda x: x.server.server_id == server.server_id), + self._servers + .get_servers() + .where(lambda x: x.server_id == server.server_id), + self._user_joined_servers + .get_user_joined_servers() + .where(lambda x: x.user.server.server_id == server.server_id), + self._user_joined_voice_channel + .get_user_joined_voice_channels() + .where(lambda x: x.user.server.server_id == server.server_id), + self._users + .get_users() + .where(lambda x: x.server.server_id == server.server_id), + guild + ) diff --git a/kdb-bot/src/modules/stats/stats.json b/kdb-bot/src/modules/stats/stats.json new file mode 100644 index 00000000..c4fec974 --- /dev/null +++ b/kdb-bot/src/modules/stats/stats.json @@ -0,0 +1,46 @@ +{ + "ProjectSettings": { + "Name": "stats", + "Version": { + "Major": "0", + "Minor": "0", + "Micro": "0" + }, + "Author": "", + "AuthorEmail": "", + "Description": "", + "LongDescription": "", + "URL": "", + "CopyrightDate": "", + "CopyrightName": "", + "LicenseName": "", + "LicenseDescription": "", + "Dependencies": [ + "cpl-core>=2022.10.0.post7" + ], + "DevDependencies": [ + "cpl-cli>=2022.10.1" + ], + "PythonVersion": ">=3.10.4", + "PythonPath": { + "linux": "" + }, + "Classifiers": [] + }, + "BuildSettings": { + "ProjectType": "library", + "SourcePath": "", + "OutputPath": "../../dist", + "Main": "stats.main", + "EntryPoint": "stats", + "IncludePackageData": false, + "Included": [], + "Excluded": [ + "*/__pycache__", + "*/logs", + "*/tests" + ], + "PackageData": {}, + "ProjectReferences": [] + } +} \ No newline at end of file diff --git a/kdb-bot/src/modules/stats/test/user_xp_asc.py b/kdb-bot/src/modules/stats/test/user_xp_asc.py new file mode 100644 index 00000000..9ee1219e --- /dev/null +++ b/kdb-bot/src/modules/stats/test/user_xp_asc.py @@ -0,0 +1,37 @@ +from cpl_discord.container import Guild +from cpl_query.extension import List + +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 + + +async def user_xp_asc( + 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() + 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) + + return result diff --git a/kdb-bot/src/modules/stats/test/user_xp_desc.py b/kdb-bot/src/modules/stats/test/user_xp_desc.py new file mode 100644 index 00000000..32cd0f3e --- /dev/null +++ b/kdb-bot/src/modules/stats/test/user_xp_desc.py @@ -0,0 +1,37 @@ +from cpl_discord.container import Guild +from cpl_query.extension import List + +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 + + +async def user_xp_desc( + 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() + result.header.append('Name') + result.header.append('XP') + + for user in users.order_by_descending(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) + + return result From a57c8da72a6a88d3b278162d9056978acbf078fb Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Tue, 8 Nov 2022 20:28:44 +0100 Subject: [PATCH 14/27] Added logic to call statistics #46 --- kdb-bot/cpl-workspace.json | 3 ++- kdb-bot/src/bot/module_list.py | 2 ++ kdb-bot/src/bot_api/abc/__init__.py | 2 +- kdb-bot/src/bot_core/abc/__init__.py | 2 +- .../configuration/feature_flags_enum.py | 2 +- .../configuration/feature_flags_settings.py | 1 + kdb-bot/src/bot_data/abc/__init__.py | 2 +- kdb-bot/src/modules/base/abc/__init__.py | 2 +- .../src/modules/permission/abc/__init__.py | 2 +- kdb-bot/src/modules/stats/stats_module.py | 24 +++++++++++++++++++ kdb-bot/src/modules/stats/test/__init__.py | 0 11 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 kdb-bot/src/modules/stats/stats_module.py create mode 100644 kdb-bot/src/modules/stats/test/__init__.py diff --git a/kdb-bot/cpl-workspace.json b/kdb-bot/cpl-workspace.json index fa9a4332..6587cbf6 100644 --- a/kdb-bot/cpl-workspace.json +++ b/kdb-bot/cpl-workspace.json @@ -3,6 +3,7 @@ "DefaultProject": "bot", "Projects": { "bot": "src/bot/bot.json", + "bot-api": "src/bot_api/bot-api.json", "bot-core": "src/bot_core/bot-core.json", "bot-data": "src/bot_data/bot-data.json", "auto-role": "src/modules/auto_role/auto-role.json", @@ -11,7 +12,7 @@ "database": "src/modules/database/database.json", "level": "src/modules/level/level.json", "permission": "src/modules/permission/permission.json", - "bot-api": "src/bot_api/bot-api.json", + "stats": "src/modules/stats/stats.json", "get-version": "tools/get_version/get-version.json", "post-build": "tools/post_build/post-build.json", "set-version": "tools/set_version/set-version.json" diff --git a/kdb-bot/src/bot/module_list.py b/kdb-bot/src/bot/module_list.py index 575824f3..3eb60cd3 100644 --- a/kdb-bot/src/bot/module_list.py +++ b/kdb-bot/src/bot/module_list.py @@ -10,6 +10,7 @@ from modules.boot_log.boot_log_module import BootLogModule from modules.database.database_module import DatabaseModule from modules.level.level_module import LevelModule from modules.permission.permission_module import PermissionModule +from modules.stats.stats_module import StatsModule class ModuleList: @@ -26,6 +27,7 @@ class ModuleList: LevelModule, PermissionModule, ApiModule, + StatsModule, # has to be last! BootLogModule, CoreExtensionModule, diff --git a/kdb-bot/src/bot_api/abc/__init__.py b/kdb-bot/src/bot_api/abc/__init__.py index 20368eb6..ad4cf626 100644 --- a/kdb-bot/src/bot_api/abc/__init__.py +++ b/kdb-bot/src/bot_api/abc/__init__.py @@ -11,7 +11,7 @@ Discord bot for the Keksdose discord Server """ -__title__ = 'bot_api.abc' +__title__ = 'bot_api.service' __author__ = 'Sven Heidemann' __license__ = 'MIT' __copyright__ = 'Copyright (c) 2022 sh-edraft.de' diff --git a/kdb-bot/src/bot_core/abc/__init__.py b/kdb-bot/src/bot_core/abc/__init__.py index 4056210a..1266f4f1 100644 --- a/kdb-bot/src/bot_core/abc/__init__.py +++ b/kdb-bot/src/bot_core/abc/__init__.py @@ -11,7 +11,7 @@ Discord bot for the Keksdose discord Server """ -__title__ = 'bot_core.abc' +__title__ = 'bot_core.service' __author__ = 'Sven Heidemann' __license__ = 'MIT' __copyright__ = 'Copyright (c) 2022 sh-edraft.de' diff --git a/kdb-bot/src/bot_core/configuration/feature_flags_enum.py b/kdb-bot/src/bot_core/configuration/feature_flags_enum.py index 226ca1d8..f04e5ca7 100644 --- a/kdb-bot/src/bot_core/configuration/feature_flags_enum.py +++ b/kdb-bot/src/bot_core/configuration/feature_flags_enum.py @@ -2,7 +2,6 @@ from enum import Enum class FeatureFlagsEnum(Enum): - # modules api_module = 'ApiModule' admin_module = 'AdminModule' @@ -16,6 +15,7 @@ class FeatureFlagsEnum(Enum): level_module = 'LevelModule' moderator_module = 'ModeratorModule' permission_module = 'PermissionModule' + stats_module = 'StatsModule' # features api_only = 'ApiOnly' presence = 'Presence' diff --git a/kdb-bot/src/bot_core/configuration/feature_flags_settings.py b/kdb-bot/src/bot_core/configuration/feature_flags_settings.py index 1861f9a1..dff28c6e 100644 --- a/kdb-bot/src/bot_core/configuration/feature_flags_settings.py +++ b/kdb-bot/src/bot_core/configuration/feature_flags_settings.py @@ -25,6 +25,7 @@ class FeatureFlagsSettings(ConfigurationModelABC): FeatureFlagsEnum.database_module.value: True, # 02.10.2022 #48 FeatureFlagsEnum.moderator_module.value: False, # 02.10.2022 #48 FeatureFlagsEnum.permission_module.value: True, # 02.10.2022 #48 + FeatureFlagsEnum.stats_module.value: True, # 08.11.2022 #46 # features FeatureFlagsEnum.api_only.value: False, # 13.10.2022 #70 FeatureFlagsEnum.presence.value: True, # 03.10.2022 #56 diff --git a/kdb-bot/src/bot_data/abc/__init__.py b/kdb-bot/src/bot_data/abc/__init__.py index 7209be45..c2b4323a 100644 --- a/kdb-bot/src/bot_data/abc/__init__.py +++ b/kdb-bot/src/bot_data/abc/__init__.py @@ -11,7 +11,7 @@ Discord bot for the Keksdose discord Server """ -__title__ = 'bot_data.abc' +__title__ = 'bot_data.service' __author__ = 'Sven Heidemann' __license__ = 'MIT' __copyright__ = 'Copyright (c) 2022 sh-edraft.de' diff --git a/kdb-bot/src/modules/base/abc/__init__.py b/kdb-bot/src/modules/base/abc/__init__.py index ccc3e98a..41181517 100644 --- a/kdb-bot/src/modules/base/abc/__init__.py +++ b/kdb-bot/src/modules/base/abc/__init__.py @@ -11,7 +11,7 @@ Discord bot for the Keksdose discord Server """ -__title__ = 'modules.base.abc' +__title__ = 'modules.base.service' __author__ = 'Sven Heidemann' __license__ = 'MIT' __copyright__ = 'Copyright (c) 2022 sh-edraft.de' diff --git a/kdb-bot/src/modules/permission/abc/__init__.py b/kdb-bot/src/modules/permission/abc/__init__.py index 294d277c..930246c1 100644 --- a/kdb-bot/src/modules/permission/abc/__init__.py +++ b/kdb-bot/src/modules/permission/abc/__init__.py @@ -11,7 +11,7 @@ Discord bot for the Keksdose discord Server """ -__title__ = 'modules.permission.abc' +__title__ = 'modules.permission.service' __author__ = 'Sven Heidemann' __license__ = 'MIT' __copyright__ = 'Copyright (c) 2022 sh-edraft.de' diff --git a/kdb-bot/src/modules/stats/stats_module.py b/kdb-bot/src/modules/stats/stats_module.py new file mode 100644 index 00000000..4c60b02a --- /dev/null +++ b/kdb-bot/src/modules/stats/stats_module.py @@ -0,0 +1,24 @@ +from cpl_core.configuration import ConfigurationABC +from cpl_core.dependency_injection import ServiceCollectionABC +from cpl_core.environment import ApplicationEnvironmentABC +from cpl_discord.service.discord_collection_abc import DiscordCollectionABC + +from bot_core.abc.module_abc import ModuleABC +from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum +from modules.stats.command.stats_group import StatsGroup +from modules.stats.service.statistic_service import StatisticService + + +class StatsModule(ModuleABC): + + def __init__(self, dc: DiscordCollectionABC): + ModuleABC.__init__(self, dc, FeatureFlagsEnum.stats_module) + + def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC): + pass + + def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): + services.add_transient(StatisticService) + # commands + self._dc.add_command(StatsGroup) + # events diff --git a/kdb-bot/src/modules/stats/test/__init__.py b/kdb-bot/src/modules/stats/test/__init__.py new file mode 100644 index 00000000..e69de29b From ddb9d0e647d0a055731d042223f31d78b7929ed5 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Tue, 8 Nov 2022 20:34:47 +0100 Subject: [PATCH 15/27] Removed old code #46 --- kdb-bot/src/modules/stats/command/stats_group.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/kdb-bot/src/modules/stats/command/stats_group.py b/kdb-bot/src/modules/stats/command/stats_group.py index 32b32f61..01884c7a 100644 --- a/kdb-bot/src/modules/stats/command/stats_group.py +++ b/kdb-bot/src/modules/stats/command/stats_group.py @@ -95,17 +95,6 @@ class StatsGroup(DiscordCommandABC): statistic = self._stats.where(lambda s: s.name == name).single() result = await self._statistic.execute(statistic.func, server) - # headers = '' - # rows = '' - # for header in result.header: - # headers += f'\n{header}' - # - # for row in result.values: - # row_str = '' - # for column in row: - # row_str += f'\n{column}' - # rows += f'\n{row_str}' - embed = discord.Embed( title=statistic.name, description=statistic.description, @@ -119,9 +108,7 @@ class StatsGroup(DiscordCommandABC): value += f'\n{row[i]}' embed.add_field(name=header, value=value, inline=True) - # embed.add_field(name=self._t.transform('modules.auto_role.list.message_id'), value=rows, inline=True) await self._message_service.send_ctx_msg(ctx, embed, wait_before_delete=wait) - # await self._message_service.send_ctx_msg(ctx, name, wait_before_delete=wait) except Exception as e: self._logger.error(__name__, f'Cannot view statistic {name}', e) await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.stats.view.failed')) From 61b2432d0bd6027722eeb8eeed2a182295ff8a21 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Tue, 8 Nov 2022 21:54:33 +0100 Subject: [PATCH 16/27] 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 edb2e87c..49f675b6 100644 --- a/kdb-bot/src/bot/translation/de.json +++ b/kdb-bot/src/bot/translation/de.json @@ -227,6 +227,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 3e524d4d..614dd1df 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 6a43f32c..5d211c59 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 01884c7a..bb46b0a0 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 2db1b75d..d1b6558d 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 b0f59d62..cee56335 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 00000000..425ab6c1 --- /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 00000000..9591fb21 --- /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') From 86433e841b6af1d73c72f71230cb5087a5945217 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Tue, 8 Nov 2022 22:06:36 +0100 Subject: [PATCH 17/27] Fixed typo #46 --- kdb-bot/src/modules/stats/command/stats_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kdb-bot/src/modules/stats/command/stats_group.py b/kdb-bot/src/modules/stats/command/stats_group.py index bb46b0a0..b1051209 100644 --- a/kdb-bot/src/modules/stats/command/stats_group.py +++ b/kdb-bot/src/modules/stats/command/stats_group.py @@ -84,7 +84,7 @@ class StatsGroup(DiscordCommandABC): statistics = '' descriptions = '' - for statistic in self._stats: + for statistic in stats: statistics += f'\n{statistic["Name"]}' descriptions += f'\n{statistic["Description"]}' From 5c7db72723fab2d7bacdc10da812dfd8851291f0 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 9 Nov 2022 17:18:14 +0100 Subject: [PATCH 18/27] Fixed level seeder #46 --- kdb-bot/src/modules/level/level_seeder.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kdb-bot/src/modules/level/level_seeder.py b/kdb-bot/src/modules/level/level_seeder.py index 8e6eb9e7..ed3d25ba 100644 --- a/kdb-bot/src/modules/level/level_seeder.py +++ b/kdb-bot/src/modules/level/level_seeder.py @@ -1,6 +1,7 @@ import discord -from cpl_discord.container import Guild +from cpl_discord.container import Guild, Role from cpl_discord.service import DiscordBotServiceABC +from cpl_query.extension import List from discord import Permissions, Colour from bot_core.logging.database_logger import DatabaseLogger From 8cd67cd68cce4c1a168aa3d3933860a7236ae5c4 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 9 Nov 2022 17:59:55 +0100 Subject: [PATCH 19/27] Added stats edit command #46 --- kdb-bot/src/bot/translation/de.json | 4 +++ .../src/bot_core/service/message_service.py | 7 ++-- .../src/modules/stats/command/stats_group.py | 32 +++++++++++++++++-- kdb-bot/src/modules/stats/model/statistic.py | 8 +++++ .../modules/stats/ui/add_statistic_form.py | 27 ++++++++++++++-- 5 files changed, 69 insertions(+), 9 deletions(-) diff --git a/kdb-bot/src/bot/translation/de.json b/kdb-bot/src/bot/translation/de.json index 49f675b6..6dd03c73 100644 --- a/kdb-bot/src/bot/translation/de.json +++ b/kdb-bot/src/bot/translation/de.json @@ -231,6 +231,10 @@ "add": { "failed": "Statistik kann nicht hinzugefügt werden :(", "success": "Statistik wurde hinzugefügt :D" + }, + "edit": { + "failed": "Statistik kann nicht bearbeitet werden :(", + "success": "Statistik wurde gespeichert :D" } } }, diff --git a/kdb-bot/src/bot_core/service/message_service.py b/kdb-bot/src/bot_core/service/message_service.py index 5d211c59..04f33bfb 100644 --- a/kdb-bot/src/bot_core/service/message_service.py +++ b/kdb-bot/src/bot_core/service/message_service.py @@ -126,12 +126,11 @@ class MessageService(MessageServiceABC): 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) + await interaction.response.send_message(embed=message) else: - msg = await interaction.response.send_message(message) + 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: @@ -146,4 +145,4 @@ class MessageService(MessageServiceABC): if is_persistent: return - await self.delete_message(msg, without_tracking) + await self.delete_message(await interaction.original_response(), 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 b1051209..58105c0b 100644 --- a/kdb-bot/src/modules/stats/command/stats_group.py +++ b/kdb-bot/src/modules/stats/command/stats_group.py @@ -135,16 +135,42 @@ class StatsGroup(DiscordCommandABC): @stats.command() @commands.guild_only() async def add(self, ctx: Context, name: str): - self._logger.debug(__name__, f'Received command stats list {ctx}') + self._logger.debug(__name__, f'Received command stats add {ctx}: {name}') 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') + self._logger.trace(__name__, f'Finished command stats add') 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'Finished stats add command') self._logger.trace(__name__, f'Started stats command form') await ctx.interaction.response.send_modal(form) + + @stats.command() + @commands.guild_only() + async def edit(self, ctx: Context, name: str): + self._logger.debug(__name__, f'Received command stats edit {ctx}: {name}') + 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 edit') + return + + try: + statistic = stats.where(lambda s: s.name == name).single() + form = AddStatisticForm(stats, name, self._message_service, self._logger, self._t, code=statistic.code, description=statistic.description) + self._logger.trace(__name__, f'Finished stats edit command') + self._logger.trace(__name__, f'Started stats command form') + await ctx.interaction.response.send_modal(form) + except Exception as e: + self._logger.error(__name__, f'Cannot view statistic {name}', e) + await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.stats.edit.failed')) + + @edit.autocomplete('name') + async def edit_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 stats] diff --git a/kdb-bot/src/modules/stats/model/statistic.py b/kdb-bot/src/modules/stats/model/statistic.py index d1b6558d..1ab0afe4 100644 --- a/kdb-bot/src/modules/stats/model/statistic.py +++ b/kdb-bot/src/modules/stats/model/statistic.py @@ -16,6 +16,14 @@ class Statistic: def description(self) -> str: return self._description + @description.setter + def description(self, value: str): + self._description = value + @property def code(self) -> str: return self._code + + @code.setter + def code(self, value: str): + self._code = value diff --git a/kdb-bot/src/modules/stats/ui/add_statistic_form.py b/kdb-bot/src/modules/stats/ui/add_statistic_form.py index 9591fb21..fe053241 100644 --- a/kdb-bot/src/modules/stats/ui/add_statistic_form.py +++ b/kdb-bot/src/modules/stats/ui/add_statistic_form.py @@ -20,6 +20,8 @@ class AddStatisticForm(ui.Modal): message_service: MessageServiceABC, logger: CommandLogger, t: TranslatePipe, + code: str = None, + description: str = None, ): ui.Modal.__init__(self, title=name) @@ -29,7 +31,28 @@ class AddStatisticForm(ui.Modal): self._logger = logger self._t = t + if code is not None: + self.code.default = code + + if description is not None: + self.description.default = description + 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')) + statistic = self._stats.where(lambda s: s.name == self._name).single_or_default() + try: + if statistic is None: + 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')) + return + + statistic.description = self.description.value + statistic.code = self.code.value + await self._message_service.send_interaction_msg(interaction, self._t.transform('modules.stats.edit.success')) + except Exception as e: + self._logger.error(__name__, f'Save statistic {self._name} failed', e) + if statistic is None: + await self._message_service.send_interaction_msg(interaction, self._t.transform('modules.stats.add.failed')) + else: + await self._message_service.send_interaction_msg(interaction, self._t.transform('modules.stats.edit.failed')) + self._logger.trace(__name__, f'Finished stats command form') From bf107f9a1894d129efc7140389757b7ca3fa6d71 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 9 Nov 2022 18:01:14 +0100 Subject: [PATCH 20/27] Added stats remove command #46 --- .../src/modules/stats/command/stats_group.py | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/kdb-bot/src/modules/stats/command/stats_group.py b/kdb-bot/src/modules/stats/command/stats_group.py index 58105c0b..67c8469a 100644 --- a/kdb-bot/src/modules/stats/command/stats_group.py +++ b/kdb-bot/src/modules/stats/command/stats_group.py @@ -168,9 +168,32 @@ class StatsGroup(DiscordCommandABC): self._logger.trace(__name__, f'Started stats command form') await ctx.interaction.response.send_modal(form) except Exception as e: - self._logger.error(__name__, f'Cannot view statistic {name}', e) + self._logger.error(__name__, f'Cannot edit statistic {name}', e) await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.stats.edit.failed')) @edit.autocomplete('name') async def edit_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 stats] + + @stats.command() + @commands.guild_only() + async def remove(self, ctx: Context, name: str): + self._logger.debug(__name__, f'Received command stats remove {ctx}: {name}') + 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 remove') + return + + try: + stats.remove(stats.where(lambda s: s.name == name).single()) + self._logger.trace(__name__, f'Finished stats remove command') + except Exception as e: + self._logger.error(__name__, f'Cannot remove statistic {name}', e) + await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.stats.edit.failed')) + + @remove.autocomplete('name') + async def edit_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 stats] From b5080d8c04c4f41acb2351e2f8db863a74726763 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 9 Nov 2022 18:22:27 +0100 Subject: [PATCH 21/27] Added stats migration #46 --- .../src/bot/startup_migration_extension.py | 2 ++ .../src/bot_data/migration/stats_migration.py | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 kdb-bot/src/bot_data/migration/stats_migration.py diff --git a/kdb-bot/src/bot/startup_migration_extension.py b/kdb-bot/src/bot/startup_migration_extension.py index a5d75b6d..724db39a 100644 --- a/kdb-bot/src/bot/startup_migration_extension.py +++ b/kdb-bot/src/bot/startup_migration_extension.py @@ -8,6 +8,7 @@ from bot_data.migration.api_migration import ApiMigration from bot_data.migration.auto_role_migration import AutoRoleMigration from bot_data.migration.initial_migration import InitialMigration from bot_data.migration.level_migration import LevelMigration +from bot_data.migration.stats_migration import StatsMigration from bot_data.service.migration_service import MigrationService @@ -25,3 +26,4 @@ class StartupMigrationExtension(StartupExtensionABC): services.add_transient(MigrationABC, AutoRoleMigration) # 03.10.2022 #54 - 0.2.2 services.add_transient(MigrationABC, ApiMigration) # 15.10.2022 #70 - 0.3.0 services.add_transient(MigrationABC, LevelMigration) # 06.11.2022 #25 - 0.3.0 + services.add_transient(MigrationABC, StatsMigration) # 09.11.2022 #46 - 0.3.0 diff --git a/kdb-bot/src/bot_data/migration/stats_migration.py b/kdb-bot/src/bot_data/migration/stats_migration.py new file mode 100644 index 00000000..92ba17ab --- /dev/null +++ b/kdb-bot/src/bot_data/migration/stats_migration.py @@ -0,0 +1,36 @@ +from bot_core.logging.database_logger import DatabaseLogger +from bot_data.abc.migration_abc import MigrationABC +from bot_data.db_context import DBContext + + +class StatsMigration(MigrationABC): + name = '0.3_StatsMigration' + + def __init__(self, logger: DatabaseLogger, db: DBContext): + MigrationABC.__init__(self) + self._logger = logger + self._db = db + self._cursor = db.cursor + + def upgrade(self): + self._logger.debug(__name__, 'Running upgrade') + + self._cursor.execute( + str(f""" + CREATE TABLE IF NOT EXISTS `Statistics` ( + `Id` BIGINT NOT NULL AUTO_INCREMENT, + `Name` VARCHAR(255) NOT NULL, + `Description` VARCHAR(255) NOT NULL, + `Code` LONGTEXT NOT NULL, + `ServerId` BIGINT, + `CreatedAt` DATETIME(6), + `LastModifiedAt` DATETIME(6), + PRIMARY KEY(`Id`), + FOREIGN KEY (`ServerId`) REFERENCES `Servers`(`ServerId`) + ); + """) + ) + + def downgrade(self): + self._cursor.execute('DROP TABLE `Statistics`;') + From 1c9c265ba82a279a367b2df4039e2bfae565cb26 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 9 Nov 2022 18:36:48 +0100 Subject: [PATCH 22/27] Added stats table #46 --- kdb-bot/src/bot_data/model/statistic.py | 108 ++++++++++++++++++ .../src/modules/stats/command/stats_group.py | 2 +- kdb-bot/src/modules/stats/model/statistic.py | 29 ----- .../modules/stats/ui/add_statistic_form.py | 6 +- 4 files changed, 112 insertions(+), 33 deletions(-) create mode 100644 kdb-bot/src/bot_data/model/statistic.py delete mode 100644 kdb-bot/src/modules/stats/model/statistic.py diff --git a/kdb-bot/src/bot_data/model/statistic.py b/kdb-bot/src/bot_data/model/statistic.py new file mode 100644 index 00000000..1c34a264 --- /dev/null +++ b/kdb-bot/src/bot_data/model/statistic.py @@ -0,0 +1,108 @@ +from datetime import datetime +from typing import Optional + +from cpl_core.database import TableABC + +from bot_data.model.server import Server + + +class Statistic(TableABC): + + def __init__(self, name: str, description: str, code: str, server: Server, created_at: datetime=None, modified_at: datetime=None, id=0): + self._id = id + self._name = name + self._description = description + self._code = code + self._server = server + + TableABC.__init__(self) + self._created_at = created_at if created_at is not None else self._created_at + self._modified_at = modified_at if modified_at is not None else self._modified_at + + @property + def id(self) -> int: + return self._id + + @property + def name(self) -> str: + return self._name + + @property + def description(self) -> str: + return self._description + + @description.setter + def description(self, value: str): + self._description = value + + @property + def code(self) -> str: + return self._code + + @code.setter + def code(self, value: str): + self._code = value + + @property + def server(self) -> Server: + return self._server + + @staticmethod + def get_select_all_string() -> str: + return str(f""" + SELECT * FROM `Statistics`; + """) + + @staticmethod + def get_select_by_id_string(id: int) -> str: + return str(f""" + SELECT * FROM `Statistics` + WHERE `Id` = {id}; + """) + + @staticmethod + def get_select_by_name_string(name: str, s_id: int) -> str: + return str(f""" + SELECT * FROM `Statistics` + WHERE `Name` = {name}; + """) + + @staticmethod + def get_select_by_server_string(s_id: int) -> str: + return str(f""" + SELECT * FROM `Statistics` + WHERE `ServerId` = {s_id}; + """) + + @property + def insert_string(self) -> str: + return str(f""" + INSERT INTO `Statistics` ( + `Name`, `Description`, `Code`, `ServerId`, `CreatedAt`, `LastModifiedAt` + ) VALUES ( + '{self._name}', + '{self._description}', + '{self._code}', + {self._server.server_id}, + '{self._created_at}', + '{self._modified_at}' + ); + """) + + @property + def udpate_string(self) -> str: + return str(f""" + UPDATE `Statistics` + SET `Name` = {self._name}, + `Description` = '{self._description}' + `Code` = '{self._code}' + `LastModifiedAt` = '{self._modified_at}' + WHERE `Id` = {self._id}; + """) + + @property + def delete_string(self) -> str: + return str(f""" + DELETE FROM `Statistics` + WHERE `Id` = {self._id}; + """) diff --git a/kdb-bot/src/modules/stats/command/stats_group.py b/kdb-bot/src/modules/stats/command/stats_group.py index 67c8469a..0d61f249 100644 --- a/kdb-bot/src/modules/stats/command/stats_group.py +++ b/kdb-bot/src/modules/stats/command/stats_group.py @@ -13,8 +13,8 @@ from bot_core.abc.client_utils_service_abc import ClientUtilsServiceABC from bot_core.abc.message_service_abc import MessageServiceABC from bot_core.logging.command_logger import CommandLogger from bot_data.abc.server_repository_abc import ServerRepositoryABC +from bot_data.model.statistic import Statistic 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.ui.add_statistic_form import AddStatisticForm diff --git a/kdb-bot/src/modules/stats/model/statistic.py b/kdb-bot/src/modules/stats/model/statistic.py deleted file mode 100644 index 1ab0afe4..00000000 --- a/kdb-bot/src/modules/stats/model/statistic.py +++ /dev/null @@ -1,29 +0,0 @@ -from typing import Callable - - -class Statistic: - - def __init__(self, name: str, description: str, code: str): - self._name = name - self._description = description - self._code = code - - @property - def name(self) -> str: - return self._name - - @property - def description(self) -> str: - return self._description - - @description.setter - def description(self, value: str): - self._description = value - - @property - def code(self) -> str: - return self._code - - @code.setter - def code(self, value: str): - self._code = value diff --git a/kdb-bot/src/modules/stats/ui/add_statistic_form.py b/kdb-bot/src/modules/stats/ui/add_statistic_form.py index fe053241..073fc9d4 100644 --- a/kdb-bot/src/modules/stats/ui/add_statistic_form.py +++ b/kdb-bot/src/modules/stats/ui/add_statistic_form.py @@ -5,7 +5,7 @@ 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 +from modules.stats.model.statisticmodel import StatisticModel class AddStatisticForm(ui.Modal): @@ -15,7 +15,7 @@ class AddStatisticForm(ui.Modal): def __init__( self, - stats: List[Statistic], + stats: List[StatisticModel], name: str, message_service: MessageServiceABC, logger: CommandLogger, @@ -41,7 +41,7 @@ class AddStatisticForm(ui.Modal): statistic = self._stats.where(lambda s: s.name == self._name).single_or_default() try: if statistic is None: - self._stats.append(Statistic(self._name, self.description.value, self.code.value)) + self._stats.append(StatisticModel(self._name, self.description.value, self.code.value)) await self._message_service.send_interaction_msg(interaction, self._t.transform('modules.stats.add.success')) return From b52cdf0f3447cb66c1bbc406a4fccf06fc6c418b Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 9 Nov 2022 18:49:11 +0100 Subject: [PATCH 23/27] Added stats repo #46 --- .../bot_data/abc/statistic_repository_abc.py | 36 ++++++++ .../service/statistic_repository_service.py | 88 +++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 kdb-bot/src/bot_data/abc/statistic_repository_abc.py create mode 100644 kdb-bot/src/bot_data/service/statistic_repository_service.py diff --git a/kdb-bot/src/bot_data/abc/statistic_repository_abc.py b/kdb-bot/src/bot_data/abc/statistic_repository_abc.py new file mode 100644 index 00000000..bf16055d --- /dev/null +++ b/kdb-bot/src/bot_data/abc/statistic_repository_abc.py @@ -0,0 +1,36 @@ +from abc import ABC, abstractmethod +from typing import Optional + +from cpl_query.extension import List + +from bot_data.model.statistic import Statistic + + +class StatisticRepositoryABC(ABC): + + @abstractmethod + def __init__(self): pass + + @abstractmethod + def get_statistics(self) -> List[Statistic]: pass + + @abstractmethod + def get_statistics_by_server_id(self, server_id: int) -> List[Statistic]: pass + + @abstractmethod + def get_statistic_by_id(self, id: int) -> Statistic: pass + + @abstractmethod + def get_statistic_by_name(self, name: str, server_id: int) -> Statistic: pass + + @abstractmethod + def find_statistic_by_name(self, name: str, server_id: int) -> Optional[Statistic]: pass + + @abstractmethod + def add_statistic(self, statistic: Statistic): pass + + @abstractmethod + def update_statistic(self, statistic: Statistic): pass + + @abstractmethod + def delete_statistic(self, statistic: Statistic): pass diff --git a/kdb-bot/src/bot_data/service/statistic_repository_service.py b/kdb-bot/src/bot_data/service/statistic_repository_service.py new file mode 100644 index 00000000..222bdf38 --- /dev/null +++ b/kdb-bot/src/bot_data/service/statistic_repository_service.py @@ -0,0 +1,88 @@ +from typing import Optional + +from cpl_core.database.context import DatabaseContextABC +from cpl_query.extension import List + +from bot_core.logging.database_logger import DatabaseLogger +from bot_data.abc.server_repository_abc import ServerRepositoryABC +from bot_data.abc.statistic_repository_abc import StatisticRepositoryABC +from bot_data.model.statistic import Statistic + + +class StatisticRepositoryService(StatisticRepositoryABC): + + def __init__(self, logger: DatabaseLogger, db_context: DatabaseContextABC, statistics: ServerRepositoryABC): + self._logger = logger + self._context = db_context + + self._statistics = statistics + + StatisticRepositoryABC.__init__(self) + + @staticmethod + def _get_value_from_result(value: any) -> Optional[any]: + if isinstance(value, str) and 'NULL' in value: + return None + + return value + + def _statistic_from_result(self, sql_result: tuple) -> Statistic: + statistic = Statistic( + self._get_value_from_result(sql_result[1]), + self._get_value_from_result(sql_result[2]), + self._get_value_from_result(sql_result[3]), + self._statistics.get_server_by_id(sql_result[4]), + id=self._get_value_from_result(sql_result[0]) + ) + + return statistic + + def get_statistics(self) -> List[Statistic]: + statistics = List(Statistic) + self._logger.trace(__name__, f'Send SQL command: {Statistic.get_select_all_string()}') + results = self._context.select(Statistic.get_select_all_string()) + for result in results: + statistics.append(self._statistic_from_result(result)) + + return statistics + + def get_statistics_by_server_id(self, server_id: int) -> List[Statistic]: + statistics = List(Statistic) + self._logger.trace(__name__, f'Send SQL command: {Statistic.get_select_by_server_string(server_id)}') + results = self._context.select(Statistic.get_select_by_server_string(server_id)) + for result in results: + statistics.append(self._statistic_from_result(result)) + + return statistics + + def get_statistic_by_id(self, id: int) -> Statistic: + self._logger.trace(__name__, f'Send SQL command: {Statistic.get_select_by_id_string(id)}') + result = self._context.select(Statistic.get_select_by_id_string(id))[0] + return self._statistic_from_result(result) + + def get_statistic_by_name(self, name: str, server_id: int) -> Statistic: + self._logger.trace(__name__, f'Send SQL command: {Statistic.get_select_by_name_string(name, server_id)}') + result = self._context.select(Statistic.get_select_by_name_string(name, server_id))[0] + return self._statistic_from_result(result) + + def find_statistic_by_name(self, name: str, server_id: int) -> Optional[Statistic]: + self._logger.trace(__name__, f'Send SQL command: {Statistic.get_select_by_name_string(name, server_id)}') + result = self._context.select(Statistic.get_select_by_name_string(name, server_id)) + if result is None or len(result) == 0: + return None + + result = result[0] + + return self._statistic_from_result(result) + + def add_statistic(self, statistic: Statistic): + self._logger.trace(__name__, f'Send SQL command: {statistic.insert_string}') + self._context.cursor.execute(statistic.insert_string) + + def update_statistic(self, statistic: Statistic): + self._logger.trace(__name__, f'Send SQL command: {statistic.udpate_string}') + self._context.cursor.execute(statistic.udpate_string) + + def delete_statistic(self, statistic: Statistic): + self._logger.trace(__name__, f'Send SQL command: {statistic.delete_string}') + self._context.cursor.execute(statistic.delete_string) From 7fe0f19adffaf7c55146eb9429c1ca2e5b6f1171 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 9 Nov 2022 19:23:10 +0100 Subject: [PATCH 24/27] Added db to stats #46 --- kdb-bot/src/bot/translation/de.json | 7 +- kdb-bot/src/bot_data/data_module.py | 3 + kdb-bot/src/bot_data/model/statistic.py | 17 ++--- .../service/statistic_repository_service.py | 7 +- .../src/modules/stats/command/stats_group.py | 66 +++++++++++-------- .../modules/stats/ui/add_statistic_form.py | 26 ++++++-- 6 files changed, 85 insertions(+), 41 deletions(-) diff --git a/kdb-bot/src/bot/translation/de.json b/kdb-bot/src/bot/translation/de.json index 6dd03c73..606e89c6 100644 --- a/kdb-bot/src/bot/translation/de.json +++ b/kdb-bot/src/bot/translation/de.json @@ -221,7 +221,8 @@ "stats": { "list": { "statistic": "Statistik", - "description": "Beschreibung" + "description": "Beschreibung", + "nothing_found": "Keine Statistiken gefunden." }, "view": { "statistic": "Statistik", @@ -235,6 +236,10 @@ "edit": { "failed": "Statistik kann nicht bearbeitet werden :(", "success": "Statistik wurde gespeichert :D" + }, + "remove": { + "failed": "Statistik kann nicht gelöscht werden :(", + "success": "Statistik wurde gelöscht :D" } } }, diff --git a/kdb-bot/src/bot_data/data_module.py b/kdb-bot/src/bot_data/data_module.py index 0668bffe..e32d8039 100644 --- a/kdb-bot/src/bot_data/data_module.py +++ b/kdb-bot/src/bot_data/data_module.py @@ -11,6 +11,7 @@ from bot_data.abc.client_repository_abc import ClientRepositoryABC from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC from bot_data.abc.level_repository_abc import LevelRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC +from bot_data.abc.statistic_repository_abc import StatisticRepositoryABC 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 @@ -21,6 +22,7 @@ from bot_data.service.known_user_repository_service import KnownUserRepositorySe from bot_data.service.level_repository_service import LevelRepositoryService from bot_data.service.seeder_service import SeederService from bot_data.service.server_repository_service import ServerRepositoryService +from bot_data.service.statistic_repository_service import StatisticRepositoryService from bot_data.service.user_joined_server_repository_service import UserJoinedServerRepositoryService from bot_data.service.user_joined_voice_channel_service import UserJoinedVoiceChannelRepositoryService from bot_data.service.user_repository_service import UserRepositoryService @@ -44,5 +46,6 @@ class DataModule(ModuleABC): services.add_transient(UserJoinedVoiceChannelRepositoryABC, UserJoinedVoiceChannelRepositoryService) services.add_transient(AutoRoleRepositoryABC, AutoRoleRepositoryService) services.add_transient(LevelRepositoryABC, LevelRepositoryService) + services.add_transient(StatisticRepositoryABC, StatisticRepositoryService) services.add_transient(SeederService) diff --git a/kdb-bot/src/bot_data/model/statistic.py b/kdb-bot/src/bot_data/model/statistic.py index 1c34a264..11011237 100644 --- a/kdb-bot/src/bot_data/model/statistic.py +++ b/kdb-bot/src/bot_data/model/statistic.py @@ -1,7 +1,7 @@ from datetime import datetime -from typing import Optional from cpl_core.database import TableABC +from cpl_core.utils import CredentialManager from bot_data.model.server import Server @@ -12,7 +12,7 @@ class Statistic(TableABC): self._id = id self._name = name self._description = description - self._code = code + self._code = CredentialManager.encrypt(code) self._server = server TableABC.__init__(self) @@ -37,11 +37,11 @@ class Statistic(TableABC): @property def code(self) -> str: - return self._code + return CredentialManager.decrypt(self._code) @code.setter def code(self, value: str): - self._code = value + self._code = CredentialManager.encrypt(value) @property def server(self) -> Server: @@ -64,7 +64,8 @@ class Statistic(TableABC): def get_select_by_name_string(name: str, s_id: int) -> str: return str(f""" SELECT * FROM `Statistics` - WHERE `Name` = {name}; + WHERE `ServerId` = {s_id} + AND `Name` = '{name}'; """) @staticmethod @@ -93,9 +94,9 @@ class Statistic(TableABC): def udpate_string(self) -> str: return str(f""" UPDATE `Statistics` - SET `Name` = {self._name}, - `Description` = '{self._description}' - `Code` = '{self._code}' + SET `Name` = '{self._name}', + `Description` = '{self._description}', + `Code` = '{self._code}', `LastModifiedAt` = '{self._modified_at}' WHERE `Id` = {self._id}; """) diff --git a/kdb-bot/src/bot_data/service/statistic_repository_service.py b/kdb-bot/src/bot_data/service/statistic_repository_service.py index 222bdf38..8f31dbe2 100644 --- a/kdb-bot/src/bot_data/service/statistic_repository_service.py +++ b/kdb-bot/src/bot_data/service/statistic_repository_service.py @@ -1,6 +1,7 @@ from typing import Optional from cpl_core.database.context import DatabaseContextABC +from cpl_core.utils import CredentialManager from cpl_query.extension import List from bot_core.logging.database_logger import DatabaseLogger @@ -27,10 +28,14 @@ class StatisticRepositoryService(StatisticRepositoryABC): return value def _statistic_from_result(self, sql_result: tuple) -> Statistic: + code = self._get_value_from_result(sql_result[3]) + if code is not None: + code = CredentialManager.decrypt(code) + statistic = Statistic( self._get_value_from_result(sql_result[1]), self._get_value_from_result(sql_result[2]), - self._get_value_from_result(sql_result[3]), + code, self._statistics.get_server_by_id(sql_result[4]), id=self._get_value_from_result(sql_result[0]) ) diff --git a/kdb-bot/src/modules/stats/command/stats_group.py b/kdb-bot/src/modules/stats/command/stats_group.py index 0d61f249..96734119 100644 --- a/kdb-bot/src/modules/stats/command/stats_group.py +++ b/kdb-bot/src/modules/stats/command/stats_group.py @@ -1,9 +1,8 @@ -import textwrap from typing import List as TList import discord +from cpl_core.database.context import DatabaseContextABC from cpl_discord.command import DiscordCommandABC -from cpl_query.extension import List from cpl_translation import TranslatePipe from discord import app_commands from discord.ext import commands @@ -13,29 +12,11 @@ from bot_core.abc.client_utils_service_abc import ClientUtilsServiceABC from bot_core.abc.message_service_abc import MessageServiceABC from bot_core.logging.command_logger import CommandLogger from bot_data.abc.server_repository_abc import ServerRepositoryABC -from bot_data.model.statistic import Statistic +from bot_data.abc.statistic_repository_abc import StatisticRepositoryABC from modules.permission.abc.permission_service_abc import PermissionServiceABC from modules.stats.service.statistic_service import StatisticService 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): @@ -48,6 +29,8 @@ class StatsGroup(DiscordCommandABC): permission_service: PermissionServiceABC, statistic: StatisticService, servers: ServerRepositoryABC, + stats: StatisticRepositoryABC, + db: DatabaseContextABC, ): DiscordCommandABC.__init__(self) @@ -58,6 +41,8 @@ class StatsGroup(DiscordCommandABC): self._permissions = permission_service self._statistic = statistic self._servers = servers + self._stats = stats + self._db = db @commands.hybrid_group() @commands.guild_only() @@ -76,17 +61,27 @@ class StatsGroup(DiscordCommandABC): self._logger.trace(__name__, f'Finished command stats list') return + if ctx.guild is None: + return + embed = discord.Embed( title=self._t.transform('modules.auto_role.list.title'), description=self._t.transform('modules.auto_role.list.description'), color=int('ef9d0d', 16) ) + server = self._servers.get_server_by_discord_id(ctx.guild.id) + stats = self._stats.get_statistics_by_server_id(server.server_id) + + if stats.count() == 0: + await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.stats.list.nothing_found')) + return + statistics = '' descriptions = '' for statistic in stats: - statistics += f'\n{statistic["Name"]}' - descriptions += f'\n{statistic["Description"]}' + statistics += f'\n{statistic.name}' + descriptions += f'\n{statistic.description}' embed.add_field(name=self._t.transform('modules.stats.list.statistic'), value=statistics, inline=True) embed.add_field(name=self._t.transform('modules.stats.list.description'), value=statistics, inline=True) @@ -105,6 +100,7 @@ class StatsGroup(DiscordCommandABC): try: server = self._servers.get_server_by_discord_id(ctx.guild.id) + stats = self._stats.get_statistics_by_server_id(server.server_id) statistic = stats.where(lambda s: s.name == name).single() result = await self._statistic.execute(statistic.code, server) @@ -130,6 +126,8 @@ class StatsGroup(DiscordCommandABC): @view.autocomplete('name') async def view_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[app_commands.Choice[str]]: + server = self._servers.get_server_by_discord_id(interaction.guild.id) + stats = self._stats.get_statistics_by_server_id(server.server_id) return [app_commands.Choice(name=f'{statistic.name}: {statistic.description}', value=statistic.name) for statistic in stats] @stats.command() @@ -144,7 +142,11 @@ class StatsGroup(DiscordCommandABC): self._logger.trace(__name__, f'Finished command stats add') return - form = AddStatisticForm(stats, name, self._message_service, self._logger, self._t) + if ctx.guild is None: + return + + server = self._servers.get_server_by_discord_id(ctx.guild.id) + form = AddStatisticForm(server, self._stats, self._db, name, self._message_service, self._logger, self._t) self._logger.trace(__name__, f'Finished stats add command') self._logger.trace(__name__, f'Started stats command form') await ctx.interaction.response.send_modal(form) @@ -162,8 +164,10 @@ class StatsGroup(DiscordCommandABC): return try: + server = self._servers.get_server_by_discord_id(ctx.guild.id) + stats = self._stats.get_statistics_by_server_id(server.server_id) statistic = stats.where(lambda s: s.name == name).single() - form = AddStatisticForm(stats, name, self._message_service, self._logger, self._t, code=statistic.code, description=statistic.description) + form = AddStatisticForm(server, self._stats, self._db, name, self._message_service, self._logger, self._t, code=statistic.code, description=statistic.description) self._logger.trace(__name__, f'Finished stats edit command') self._logger.trace(__name__, f'Started stats command form') await ctx.interaction.response.send_modal(form) @@ -173,6 +177,8 @@ class StatsGroup(DiscordCommandABC): @edit.autocomplete('name') async def edit_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[app_commands.Choice[str]]: + server = self._servers.get_server_by_discord_id(interaction.guild.id) + stats = self._stats.get_statistics_by_server_id(server.server_id) return [app_commands.Choice(name=f'{statistic.name}: {statistic.description}', value=statistic.name) for statistic in stats] @stats.command() @@ -188,12 +194,18 @@ class StatsGroup(DiscordCommandABC): return try: - stats.remove(stats.where(lambda s: s.name == name).single()) + server = self._servers.get_server_by_discord_id(ctx.guild.id) + statistic = self._stats.get_statistic_by_name(name, server.server_id) + self._stats.delete_statistic(statistic) + self._db.save_changes() + await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.stats.remove.success')) self._logger.trace(__name__, f'Finished stats remove command') except Exception as e: self._logger.error(__name__, f'Cannot remove statistic {name}', e) - await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.stats.edit.failed')) + await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.stats.remove.failed')) @remove.autocomplete('name') async def edit_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[app_commands.Choice[str]]: + server = self._servers.get_server_by_discord_id(interaction.guild.id) + stats = self._stats.get_statistics_by_server_id(server.server_id) return [app_commands.Choice(name=f'{statistic.name}: {statistic.description}', value=statistic.name) for statistic in stats] diff --git a/kdb-bot/src/modules/stats/ui/add_statistic_form.py b/kdb-bot/src/modules/stats/ui/add_statistic_form.py index 073fc9d4..625bac82 100644 --- a/kdb-bot/src/modules/stats/ui/add_statistic_form.py +++ b/kdb-bot/src/modules/stats/ui/add_statistic_form.py @@ -1,11 +1,14 @@ import discord +from cpl_core.database.context import DatabaseContextABC 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.statisticmodel import StatisticModel +from bot_data.abc.statistic_repository_abc import StatisticRepositoryABC +from bot_data.model.server import Server +from bot_data.model.statistic import Statistic class AddStatisticForm(ui.Modal): @@ -15,7 +18,9 @@ class AddStatisticForm(ui.Modal): def __init__( self, - stats: List[StatisticModel], + server: Server, + stats: StatisticRepositoryABC, + db: DatabaseContextABC, name: str, message_service: MessageServiceABC, logger: CommandLogger, @@ -25,7 +30,9 @@ class AddStatisticForm(ui.Modal): ): ui.Modal.__init__(self, title=name) + self._server = server self._stats = stats + self._db = db self._name = name self._message_service = message_service self._logger = logger @@ -38,15 +45,26 @@ class AddStatisticForm(ui.Modal): self.description.default = description async def on_submit(self, interaction: discord.Interaction): - statistic = self._stats.where(lambda s: s.name == self._name).single_or_default() + statistic = self._stats.get_statistics_by_server_id(self._server.server_id).where(lambda s: s.name == self._name).single_or_default() + + if interaction.guild is None: + if statistic is None: + await self._message_service.send_interaction_msg(interaction, self._t.transform('modules.stats.add.failed')) + else: + await self._message_service.send_interaction_msg(interaction, self._t.transform('modules.stats.edit.failed')) + return + try: if statistic is None: - self._stats.append(StatisticModel(self._name, self.description.value, self.code.value)) + self._stats.add_statistic(Statistic(self._name, self.description.value, self.code.value, self._server)) + self._db.save_changes() await self._message_service.send_interaction_msg(interaction, self._t.transform('modules.stats.add.success')) return statistic.description = self.description.value statistic.code = self.code.value + self._stats.update_statistic(statistic) + self._db.save_changes() await self._message_service.send_interaction_msg(interaction, self._t.transform('modules.stats.edit.success')) except Exception as e: self._logger.error(__name__, f'Save statistic {self._name} failed', e) From 273548411bfb9d008c8b25aaeaf62e38fe0bf1b4 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 9 Nov 2022 19:57:16 +0100 Subject: [PATCH 25/27] Removed tests #26 --- kdb-bot/src/modules/stats/test/__init__.py | 0 kdb-bot/src/modules/stats/test/user_xp_asc.py | 37 ------------------- .../src/modules/stats/test/user_xp_desc.py | 37 ------------------- 3 files changed, 74 deletions(-) delete mode 100644 kdb-bot/src/modules/stats/test/__init__.py delete mode 100644 kdb-bot/src/modules/stats/test/user_xp_asc.py delete mode 100644 kdb-bot/src/modules/stats/test/user_xp_desc.py diff --git a/kdb-bot/src/modules/stats/test/__init__.py b/kdb-bot/src/modules/stats/test/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/kdb-bot/src/modules/stats/test/user_xp_asc.py b/kdb-bot/src/modules/stats/test/user_xp_asc.py deleted file mode 100644 index 9ee1219e..00000000 --- a/kdb-bot/src/modules/stats/test/user_xp_asc.py +++ /dev/null @@ -1,37 +0,0 @@ -from cpl_discord.container import Guild -from cpl_query.extension import List - -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 - - -async def user_xp_asc( - 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() - 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) - - return result diff --git a/kdb-bot/src/modules/stats/test/user_xp_desc.py b/kdb-bot/src/modules/stats/test/user_xp_desc.py deleted file mode 100644 index 32cd0f3e..00000000 --- a/kdb-bot/src/modules/stats/test/user_xp_desc.py +++ /dev/null @@ -1,37 +0,0 @@ -from cpl_discord.container import Guild -from cpl_query.extension import List - -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 - - -async def user_xp_desc( - 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() - result.header.append('Name') - result.header.append('XP') - - for user in users.order_by_descending(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) - - return result From a29e33a3f9e134f47a154f3f552775b8b194675a Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 9 Nov 2022 20:07:37 +0100 Subject: [PATCH 26/27] Readded debug log to level seeder #26 --- kdb-bot/src/modules/level/level_seeder.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kdb-bot/src/modules/level/level_seeder.py b/kdb-bot/src/modules/level/level_seeder.py index 7f203d3c..8e6eb9e7 100644 --- a/kdb-bot/src/modules/level/level_seeder.py +++ b/kdb-bot/src/modules/level/level_seeder.py @@ -35,6 +35,7 @@ class LevelSeeder(DataSeederABC): levels = self._levels.find_levels_by_server_id(server.server_id) if levels is None or levels.where(lambda l: l.name == level.name).first_or_default() is None: self._levels.add_level(level) + self._logger.debug(__name__, f'Saved level {level.name}') except discord.errors.Forbidden as e: self._logger.error(__name__, f'Creating level failed', e) level.permissions = 0 From 82365a24ba3e693f6c934d02ef0986f67165a199 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 9 Nov 2022 20:52:58 +0100 Subject: [PATCH 27/27] Fixed stats group #46 --- kdb-bot/src/modules/stats/command/stats_group.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/kdb-bot/src/modules/stats/command/stats_group.py b/kdb-bot/src/modules/stats/command/stats_group.py index 96734119..89abcb57 100644 --- a/kdb-bot/src/modules/stats/command/stats_group.py +++ b/kdb-bot/src/modules/stats/command/stats_group.py @@ -84,17 +84,22 @@ class StatsGroup(DiscordCommandABC): descriptions += f'\n{statistic.description}' embed.add_field(name=self._t.transform('modules.stats.list.statistic'), value=statistics, inline=True) - embed.add_field(name=self._t.transform('modules.stats.list.description'), value=statistics, inline=True) + embed.add_field(name=self._t.transform('modules.stats.list.description'), value=descriptions, inline=True) await self._message_service.send_ctx_msg(ctx, embed, wait_before_delete=wait) self._logger.trace(__name__, f'Finished command stats list') @stats.command() @commands.guild_only() async def view(self, ctx: Context, name: str, wait: int = None): - self._logger.debug(__name__, f'Received command stats {ctx}:{name}') + self._logger.debug(__name__, f'Received command stats view {ctx}:{name}') if not await self._client_utils.check_if_bot_is_ready_yet_and_respond(ctx): return + if not self._permissions.is_member_moderator(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 view') + return + if ctx.guild is None: return @@ -122,7 +127,7 @@ class StatsGroup(DiscordCommandABC): self._logger.error(__name__, f'Cannot view statistic {name}', e) await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.stats.view.failed')) - self._logger.trace(__name__, f'Finished stats command') + self._logger.trace(__name__, f'Finished stats view command') @view.autocomplete('name') async def view_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[app_commands.Choice[str]]: