Reviewed-on: sh-edraft.de/kd_discord_bot#102 Reviewed-by: Ebola-Chan <nick.jungmann@gmail.com> Closes #46
This commit is contained in:
commit
2483faef01
@ -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
|
||||
|
||||
|
||||
@ -25,3 +26,6 @@ class MessageServiceABC(ABC):
|
||||
|
||||
@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')
|
Loading…
Reference in New Issue
Block a user