0.3 - Statistiken (#46) #102

Merged
edraft merged 28 commits from #46 into 0.3 2022-11-09 20:55:06 +01:00
29 changed files with 839 additions and 13 deletions

View File

@ -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"

View File

@ -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,

View File

@ -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

View File

@ -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": {

View File

@ -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'

View File

@ -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'

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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)

View File

@ -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'

View 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

View File

@ -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)

View 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`;')

View 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};
""")

View 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)

View File

@ -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'

View File

@ -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'

View File

@ -0,0 +1 @@
# imports:

View File

@ -0,0 +1 @@
# imports

View 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)
edraft marked this conversation as resolved Outdated

Hier wurde zwei mal value=statisticsangegeben

embed.add_field(name=self._t.transform('modules.stats.list.statistic'), value=statistics, inline=True)
embed.add_field(name=self._t.transform('modules.stats.list.description'), value=statistics, inline=True)

statt

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)
Hier wurde zwei mal ```value=statistics```angegeben ```python embed.add_field(name=self._t.transform('modules.stats.list.statistic'), value=statistics, inline=True) embed.add_field(name=self._t.transform('modules.stats.list.description'), value=statistics, inline=True) ``` statt ```python 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
edraft marked this conversation as resolved
Review

Fehlt hier nicht noch ein Permission check?

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
Fehlt hier nicht noch ein Permission check? ```python 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 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]

View File

@ -0,0 +1 @@
# imports

View 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

View File

@ -0,0 +1 @@
# imports

View 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(),
edraft marked this conversation as resolved
Review

Ich merke hier mal an dass self._known_users.get_users() kein Lamdaausdruck hat wie die anderen Parametern. Sollte dies hier nicht gebraucht werden, dann dieser Thread resolved werden.

Ich merke hier mal an dass ```self._known_users.get_users()``` kein Lamdaausdruck hat wie die anderen Parametern. Sollte dies hier nicht gebraucht werden, dann dieser Thread resolved werden.
Review

KnownUsers ist ne Liste aller bekannten discord.Member

KnownUsers ist ne Liste aller bekannten discord.Member
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

View 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": []
}
}

View 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

View File

@ -0,0 +1 @@
# imports

View 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')