forked from sh-edraft.de/sh_discord_bot
		
	Reviewed-on: sh-edraft.de/kd_discord_bot#102 Reviewed-by: Ebola-Chan <nick.jungmann@gmail.com> Closes #46
This commit is contained in:
		@@ -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"
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -217,7 +217,30 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "database": {},
 | 
			
		||||
    "permission": {
 | 
			
		||||
    "permission": {},
 | 
			
		||||
    "stats": {
 | 
			
		||||
      "list": {
 | 
			
		||||
        "statistic": "Statistik",
 | 
			
		||||
        "description": "Beschreibung",
 | 
			
		||||
        "nothing_found": "Keine Statistiken gefunden."
 | 
			
		||||
      },
 | 
			
		||||
      "view": {
 | 
			
		||||
        "statistic": "Statistik",
 | 
			
		||||
        "description": "Beschreibung",
 | 
			
		||||
        "failed": "Statistik kann nicht gezeigt werden :("
 | 
			
		||||
      },
 | 
			
		||||
      "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"
 | 
			
		||||
      },
 | 
			
		||||
      "remove": {
 | 
			
		||||
        "failed": "Statistik kann nicht gelöscht werden :(",
 | 
			
		||||
        "success": "Statistik wurde gelöscht :D"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "api": {
 | 
			
		||||
 
 | 
			
		||||
@@ -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'
 | 
			
		||||
 
 | 
			
		||||
@@ -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'
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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'
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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,31 @@ 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}')
 | 
			
		||||
        try:
 | 
			
		||||
            if isinstance(message, discord.Embed):
 | 
			
		||||
                await interaction.response.send_message(embed=message)
 | 
			
		||||
            else:
 | 
			
		||||
                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(await interaction.original_response(), without_tracking)
 | 
			
		||||
 
 | 
			
		||||
@@ -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'
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										36
									
								
								kdb-bot/src/bot_data/abc/statistic_repository_abc.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								kdb-bot/src/bot_data/abc/statistic_repository_abc.py
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										36
									
								
								kdb-bot/src/bot_data/migration/stats_migration.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								kdb-bot/src/bot_data/migration/stats_migration.py
									
									
									
									
									
										Normal file
									
								
							@@ -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`;')
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										109
									
								
								kdb-bot/src/bot_data/model/statistic.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								kdb-bot/src/bot_data/model/statistic.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
 | 
			
		||||
from cpl_core.database import TableABC
 | 
			
		||||
from cpl_core.utils import CredentialManager
 | 
			
		||||
 | 
			
		||||
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 = CredentialManager.encrypt(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 CredentialManager.decrypt(self._code)
 | 
			
		||||
    
 | 
			
		||||
    @code.setter
 | 
			
		||||
    def code(self, value: str):
 | 
			
		||||
        self._code = CredentialManager.encrypt(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 `ServerId` = {s_id}
 | 
			
		||||
            AND `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};
 | 
			
		||||
        """)
 | 
			
		||||
							
								
								
									
										93
									
								
								kdb-bot/src/bot_data/service/statistic_repository_service.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								kdb-bot/src/bot_data/service/statistic_repository_service.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,93 @@
 | 
			
		||||
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
 | 
			
		||||
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:
 | 
			
		||||
        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]),
 | 
			
		||||
            code,
 | 
			
		||||
            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)
 | 
			
		||||
@@ -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'
 | 
			
		||||
 
 | 
			
		||||
@@ -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'
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								kdb-bot/src/modules/stats/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								kdb-bot/src/modules/stats/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
# imports: 
 | 
			
		||||
							
								
								
									
										1
									
								
								kdb-bot/src/modules/stats/command/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								kdb-bot/src/modules/stats/command/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
# imports
 | 
			
		||||
							
								
								
									
										216
									
								
								kdb-bot/src/modules/stats/command/stats_group.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								kdb-bot/src/modules/stats/command/stats_group.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,216 @@
 | 
			
		||||
from typing import List as TList
 | 
			
		||||
 | 
			
		||||
import discord
 | 
			
		||||
from cpl_core.database.context import DatabaseContextABC
 | 
			
		||||
from cpl_discord.command import DiscordCommandABC
 | 
			
		||||
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 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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StatsGroup(DiscordCommandABC):
 | 
			
		||||
 | 
			
		||||
    def __init__(
 | 
			
		||||
            self,
 | 
			
		||||
            logger: CommandLogger,
 | 
			
		||||
            message_service: MessageServiceABC,
 | 
			
		||||
            client_utils: ClientUtilsServiceABC,
 | 
			
		||||
            translate: TranslatePipe,
 | 
			
		||||
            permission_service: PermissionServiceABC,
 | 
			
		||||
            statistic: StatisticService,
 | 
			
		||||
            servers: ServerRepositoryABC,
 | 
			
		||||
            stats: StatisticRepositoryABC,
 | 
			
		||||
            db: DatabaseContextABC,
 | 
			
		||||
    ):
 | 
			
		||||
        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 = stats
 | 
			
		||||
        self._db = db
 | 
			
		||||
 | 
			
		||||
    @commands.hybrid_group()
 | 
			
		||||
    @commands.guild_only()
 | 
			
		||||
    async def stats(self, ctx: Context):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @stats.command()
 | 
			
		||||
    @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
 | 
			
		||||
 | 
			
		||||
        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}'
 | 
			
		||||
 | 
			
		||||
        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=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 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
 | 
			
		||||
 | 
			
		||||
        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)
 | 
			
		||||
 | 
			
		||||
            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)
 | 
			
		||||
 | 
			
		||||
            await self._message_service.send_ctx_msg(ctx, embed, 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 view command')
 | 
			
		||||
 | 
			
		||||
    @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()
 | 
			
		||||
    @commands.guild_only()
 | 
			
		||||
    async def add(self, ctx: Context, name: str):
 | 
			
		||||
        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 add')
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        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)
 | 
			
		||||
 | 
			
		||||
    @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:
 | 
			
		||||
            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(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)
 | 
			
		||||
        except Exception as 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]]:
 | 
			
		||||
        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()
 | 
			
		||||
    @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:
 | 
			
		||||
            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.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]
 | 
			
		||||
							
								
								
									
										1
									
								
								kdb-bot/src/modules/stats/model/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								kdb-bot/src/modules/stats/model/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
# imports
 | 
			
		||||
							
								
								
									
										24
									
								
								kdb-bot/src/modules/stats/model/statistic_result.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								kdb-bot/src/modules/stats/model/statistic_result.py
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
							
								
								
									
										1
									
								
								kdb-bot/src/modules/stats/service/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								kdb-bot/src/modules/stats/service/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
# imports
 | 
			
		||||
							
								
								
									
										96
									
								
								kdb-bot/src/modules/stats/service/statistic_service.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								kdb-bot/src/modules/stats/service/statistic_service.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
			
		||||
from abc import abstractmethod
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
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.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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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, code: str, server: Server) -> StatisticResult:
 | 
			
		||||
        guild = self._bot.guilds.where(lambda g: g.id == server.discord_server_id).single()
 | 
			
		||||
 | 
			
		||||
        return await self.get_data(
 | 
			
		||||
            code,
 | 
			
		||||
            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
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @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
 | 
			
		||||
							
								
								
									
										46
									
								
								kdb-bot/src/modules/stats/stats.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								kdb-bot/src/modules/stats/stats.json
									
									
									
									
									
										Normal file
									
								
							@@ -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": []
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								kdb-bot/src/modules/stats/stats_module.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								kdb-bot/src/modules/stats/stats_module.py
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
							
								
								
									
										1
									
								
								kdb-bot/src/modules/stats/ui/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								kdb-bot/src/modules/stats/ui/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
# imports
 | 
			
		||||
							
								
								
									
										76
									
								
								kdb-bot/src/modules/stats/ui/add_statistic_form.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								kdb-bot/src/modules/stats/ui/add_statistic_form.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
			
		||||
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 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):
 | 
			
		||||
 | 
			
		||||
    description = ui.TextInput(label='Beschreibung', required=True)
 | 
			
		||||
    code = ui.TextInput(label='Code', required=True, style=TextStyle.long)
 | 
			
		||||
 | 
			
		||||
    def __init__(
 | 
			
		||||
            self,
 | 
			
		||||
            server: Server,
 | 
			
		||||
            stats: StatisticRepositoryABC,
 | 
			
		||||
            db: DatabaseContextABC,
 | 
			
		||||
            name: str,
 | 
			
		||||
            message_service: MessageServiceABC,
 | 
			
		||||
            logger: CommandLogger,
 | 
			
		||||
            t: TranslatePipe,
 | 
			
		||||
            code: str = None,
 | 
			
		||||
            description: str = None,
 | 
			
		||||
    ):
 | 
			
		||||
        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
 | 
			
		||||
        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):
 | 
			
		||||
        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.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)
 | 
			
		||||
            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')
 | 
			
		||||
		Reference in New Issue
	
	Block a user