[WIP] Added basic commands & functionality of Gismo@0.4.10

This commit is contained in:
Sven Heidemann 2022-07-16 23:23:07 +02:00
parent f506cb74bb
commit 9f8dd52c82
95 changed files with 4385 additions and 58 deletions

View File

@ -4,8 +4,18 @@
"Projects": { "Projects": {
"bot": "src/bot/bot.json", "bot": "src/bot/bot.json",
"bot-core": "src/bot_core/bot-core.json", "bot-core": "src/bot_core/bot-core.json",
"bot-data": "src/bot_data/bot-data.json" "bot-data": "src/bot_data/bot-data.json",
"database": "src/modules/database/database.json",
"base": "src/modules/base/base.json",
"permission": "src/modules/permission/permission.json",
"boot-log": "src/modules/boot_log/boot-log.json"
}, },
"Scripts": {} "Scripts": {
"ba": "cpl build-all",
"build-all": "cpl build-bot; cpl build-bot-core; cpl build-bot-data;",
"build-bot": "echo 'Build bot'; cd ./src/bot; cpl build; cd ../../;",
"build-bot-core": "echo 'Build bot-core'; cd ./src/bot_core; cpl build; cd ../../;",
"build-bot-data": "echo 'Build bot-data'; cd ./src/bot_data; cpl build; cd ../../;"
}
} }
} }

View File

@ -1 +1,26 @@
# -*- coding: utf-8 -*-
"""
bot Keksdose bot
~~~~~~~~~~~~~~~~~~~
Discord bot for the Keksdose discord Server
:copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'bot'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.0.0.dev1'
from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='0', micro='0.dev1')

View File

@ -1,4 +1,6 @@
from cpl_core.configuration import ConfigurationABC from typing import Optional, Type
from cpl_core.configuration import ConfigurationABC, ConfigurationModelABC
from cpl_core.console import Console from cpl_core.console import Console
from cpl_core.dependency_injection import ServiceProviderABC from cpl_core.dependency_injection import ServiceProviderABC
from cpl_core.logging import LoggerABC from cpl_core.logging import LoggerABC
@ -7,6 +9,15 @@ from cpl_discord.configuration import DiscordBotSettings
from cpl_discord.service import DiscordBotServiceABC, DiscordBotService from cpl_discord.service import DiscordBotServiceABC, DiscordBotService
from cpl_translation import TranslatePipe, TranslationServiceABC, TranslationSettings from cpl_translation import TranslatePipe, TranslationServiceABC, TranslationSettings
from bot_core.configuration.bot_settings import BotSettings
from bot_core.configuration.server_settings import ServerSettings
from modules.base.configuration.base_server_settings import BaseServerSettings
from modules.base.configuration.base_settings import BaseSettings
from modules.boot_log.configuration.boot_log_server_settings import BootLogServerSettings
from modules.boot_log.configuration.boot_log_settings import BootLogSettings
from modules.permission.configuration.permission_server_settings import PermissionServerSettings
from modules.permission.configuration.permission_settings import PermissionSettings
class Application(DiscordBotApplicationABC): class Application(DiscordBotApplicationABC):
@ -22,8 +33,21 @@ class Application(DiscordBotApplicationABC):
self._translation: TranslationServiceABC = services.get_service(TranslationServiceABC) self._translation: TranslationServiceABC = services.get_service(TranslationServiceABC)
self._translate: TranslatePipe = services.get_service(TranslatePipe) self._translate: TranslatePipe = services.get_service(TranslatePipe)
def _configure_settings_with_servers(self, settings: Type, server_settings: Type):
settings: Optional[settings] = self._configuration.get_configuration(settings)
if settings is None:
return
for server in settings.servers:
self._logger.trace(__name__, f'Saved config: {type(server).__name__}_{server.id}')
self._configuration.add_configuration(f'{type(server).__name__}_{server.id}', server)
async def configure(self): async def configure(self):
self._translation.load_by_settings(self._configuration.get_configuration(TranslationSettings)) self._translation.load_by_settings(self._configuration.get_configuration(TranslationSettings))
self._configure_settings_with_servers(BotSettings, ServerSettings)
self._configure_settings_with_servers(BaseSettings, BaseServerSettings)
self._configure_settings_with_servers(BootLogSettings, BootLogServerSettings)
self._configure_settings_with_servers(PermissionSettings, PermissionServerSettings)
async def main(self): async def main(self):
try: try:

View File

@ -17,7 +17,7 @@
"LicenseDescription": "MIT, see LICENSE for more details.", "LicenseDescription": "MIT, see LICENSE for more details.",
"Dependencies": [ "Dependencies": [
"cpl-core>=2022.7.0.post1", "cpl-core>=2022.7.0.post1",
"cpl-translation==2022.7.0.post1", "cpl-translation==2022.7.0.post2",
"cpl-query==2022.7.0", "cpl-query==2022.7.0",
"cpl-discord==2022.7.0.post1" "cpl-discord==2022.7.0.post1"
], ],

View File

@ -20,14 +20,5 @@
"DiscordBot": { "DiscordBot": {
"Token": "OTA5ODc4NDcyNzExNzU3ODQ1.YZKsXA.8p-g1c37EBZzYYW09Fvr7egQzhE", "Token": "OTA5ODc4NDcyNzExNzU3ODQ1.YZKsXA.8p-g1c37EBZzYYW09Fvr7egQzhE",
"Prefix": "!kd " "Prefix": "!kd "
},
"Bot": {
"Servers": [
{
"Id": "910199451145076828",
"MessageDeleteTimer": 2,
"BotHasNoPermissionMessage": "Ich habe keine Berechtigungen :("
}
]
} }
} }

View File

@ -7,9 +7,9 @@
}, },
"DatabaseSettings": { "DatabaseSettings": {
"Host": "localhost", "Host": "localhost",
"User": "sh_gismo", "User": "kd_kdb",
"Password": "c2hfZ2lzbW8=", "Password": "VGpZcihrb0N2T2MyZUlURQ==",
"Database": "sh_gismo_dev", "Database": "keksdose_bot_dev",
"Charset": "utf8mb4", "Charset": "utf8mb4",
"UseUnicode": "true", "UseUnicode": "true",
"Buffered": "true", "Buffered": "true",
@ -20,12 +20,39 @@
"Prefix": "!kde " "Prefix": "!kde "
}, },
"Bot": { "Bot": {
"Servers": [ "511824600884051979": {
{ "MessageDeleteTimer": 2
"Id": "910199451145076828", },
"MessageDeleteTimer": 2, "910199451145076828": {
"BotHasNoPermissionMessage": "Ich habe keine Berechtigungen :(" "MessageDeleteTimer": 2
} }
},
"Base": {
"910199451145076828": {
"MaxVoiceStateHours": 24,
"XpPerMessage": 2,
"XpPerOntimeHour": 4,
"AFKChannelIds": [
910199452915093593,
910199452915093594
],
"AFKCommandChannelId": 910199452915093594,
"HelpCommandReferenceUrl": "https://git.sh-edraft.de/sh-edraft.de/sh_gismo/wiki/Befehle"
}
},
"BootLog": {
"910199451145076828": {
"LoginMessageChannelId": "910199452915093588"
}
},
"Permission": {
"910199451145076828": {
"AdminRoleIds": [
925072155203477584
],
"ModeratorRoleIds": [
925072209884635167
] ]
} }
}
} }

View File

@ -19,15 +19,5 @@
}, },
"Discord": { "Discord": {
"Token": "OTEwMjAxNjA1NDkwNjEwMjA3.YZPZTQ.lUj2Bs5pQAv4AJ_tb_mHMTpIRls" "Token": "OTEwMjAxNjA1NDkwNjEwMjA3.YZPZTQ.lUj2Bs5pQAv4AJ_tb_mHMTpIRls"
},
"Bot": {
"Prefix": "!kt ",
"Servers": [
{
"Id": "910199451145076828",
"MessageDeleteTimer": 2,
"BotHasNoPermissionMessage": "Ich habe keine Berechtigungen :("
}
]
} }
} }

View File

@ -5,6 +5,10 @@ from cpl_core.application import ApplicationBuilder
from bot.application import Application from bot.application import Application
from bot.startup import Startup from bot.startup import Startup
from bot.startup_discord_extension import StartupDiscordExtension
from bot.startup_migration_extension import StartupMigrationExtension
from modules.boot_log.boot_log_extension import BootLogExtension
from modules.database.database_extension import DatabaseExtension
class Main: class Main:
@ -14,6 +18,10 @@ class Main:
async def main(self): async def main(self):
app_builder = ApplicationBuilder(Application) app_builder = ApplicationBuilder(Application)
app_builder.use_extension(StartupDiscordExtension)
app_builder.use_extension(StartupMigrationExtension)
app_builder.use_extension(BootLogExtension)
app_builder.use_extension(DatabaseExtension)
app_builder.use_startup(Startup) app_builder.use_startup(Startup)
self._app: Application = await app_builder.build_async() self._app: Application = await app_builder.build_async()
await self._app.run_async() await self._app.run_async()

View File

@ -4,9 +4,31 @@ from typing import Optional
from cpl_core.application import StartupABC from cpl_core.application import StartupABC
from cpl_core.configuration import ConfigurationABC from cpl_core.configuration import ConfigurationABC
from cpl_core.database import DatabaseSettings
from cpl_core.dependency_injection import ServiceProviderABC, ServiceCollectionABC from cpl_core.dependency_injection import ServiceProviderABC, ServiceCollectionABC
from cpl_core.environment import ApplicationEnvironment from cpl_core.environment import ApplicationEnvironment
from cpl_discord import get_discord_collection
from bot_core.abc.client_utils_service_abc import ClientUtilsServiceABC
from bot_core.abc.message_service_abc import MessageServiceABC
from bot_core.service.client_utils_service import ClientUtilsService
from bot_core.service.message_service import MessageService
from bot_data.abc.client_repository_abc import ClientRepositoryABC
from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC
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.db_context import DBContext
from bot_data.service.client_repository_service import ClientRepositoryService
from bot_data.service.known_user_repository_service import KnownUserRepositoryService
from bot_data.service.server_repository_service import ServerRepositoryService
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
from modules.base.abc.base_helper_abc import BaseHelperABC
from modules.base.service.base_helper_service import BaseHelperService
from modules.permission.abc.permission_service_abc import PermissionServiceABC
from modules.permission.service.permission_service import PermissionService
class Startup(StartupABC): class Startup(StartupABC):
@ -34,7 +56,23 @@ class Startup(StartupABC):
def configure_services(self, services: ServiceCollectionABC, environment: ApplicationEnvironment) -> ServiceProviderABC: def configure_services(self, services: ServiceCollectionABC, environment: ApplicationEnvironment) -> ServiceProviderABC:
services.add_logging() services.add_logging()
services.add_translation() services.add_translation()
services.add_discord()
discord_collection = get_discord_collection(services) services.add_db_context(DBContext, self._config.get_configuration(DatabaseSettings))
# general services
services.add_transient(BaseHelperABC, BaseHelperService)
services.add_transient(MessageServiceABC, MessageService)
services.add_transient(ClientUtilsServiceABC, ClientUtilsService)
# module services
services.add_singleton(PermissionServiceABC, PermissionService)
# data services
services.add_transient(ServerRepositoryABC, ServerRepositoryService)
services.add_transient(UserRepositoryABC, UserRepositoryService)
services.add_transient(ClientRepositoryABC, ClientRepositoryService)
services.add_transient(KnownUserRepositoryABC, KnownUserRepositoryService)
services.add_transient(UserJoinedServerRepositoryABC, UserJoinedServerRepositoryService)
services.add_transient(UserJoinedVoiceChannelRepositoryABC, UserJoinedVoiceChannelRepositoryService)
return services.build_service_provider() return services.build_service_provider()

View File

@ -0,0 +1,52 @@
from cpl_core.application import StartupExtensionABC
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceCollectionABC
from cpl_core.environment import ApplicationEnvironmentABC
from cpl_discord import get_discord_collection
from cpl_discord.discord_event_types_enum import DiscordEventTypesEnum
from modules.base.command.afk_command import AFKCommand
from modules.base.command.help_command import HelpCommand
from modules.base.command.ping_command import PingCommand
from modules.base.command.purge_command import PurgeCommand
from modules.base.events.base_on_member_join_event import BaseOnMemberJoinEvent
from modules.base.events.base_on_member_remove_event import BaseOnMemberRemoveEvent
from modules.base.events.base_on_message_event import BaseOnMessageEvent
from modules.base.events.base_on_voice_state_update_event import BaseOnVoiceStateUpdateEvent
from modules.boot_log.boot_log_on_ready_event import BootLogOnReadyEvent
from modules.database.database_on_ready_event import DatabaseOnReadyEvent
from modules.permission.events.permission_on_member_update_event import PermissionOnMemberUpdateEvent
from modules.permission.events.permission_on_ready_event import PermissionOnReadyEvent
class StartupDiscordExtension(StartupExtensionABC):
def __init__(self):
pass
def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC):
pass
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
services.add_discord()
dc = get_discord_collection(services)
""" commands """
dc.add_command(AFKCommand)
dc.add_command(HelpCommand)
dc.add_command(PingCommand)
dc.add_command(PurgeCommand)
""" events """
# on_member_join
dc.add_event(DiscordEventTypesEnum.on_member_join.value, BaseOnMemberJoinEvent)
# on_member_remove
dc.add_event(DiscordEventTypesEnum.on_member_join.value, BaseOnMemberRemoveEvent)
# on_member_update
dc.add_event(DiscordEventTypesEnum.on_member_update.value, PermissionOnMemberUpdateEvent)
# on_message
dc.add_event(DiscordEventTypesEnum.on_message.value, BaseOnMessageEvent)
# on_voice_state_update
dc.add_event(DiscordEventTypesEnum.on_member_join.value, BaseOnVoiceStateUpdateEvent)
# on_ready
dc.add_event(DiscordEventTypesEnum.on_ready.value, DatabaseOnReadyEvent)
dc.add_event(DiscordEventTypesEnum.on_ready.value, PermissionOnReadyEvent)
dc.add_event(DiscordEventTypesEnum.on_ready.value, BootLogOnReadyEvent) # has to be last

View File

@ -0,0 +1,21 @@
from cpl_core.application import StartupExtensionABC
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceCollectionABC
from cpl_core.environment import ApplicationEnvironmentABC
from bot_data.abc.migration_abc import MigrationABC
from bot_data.migration.initial_migration import InitialMigration
from bot_data.service.migration_service import MigrationService
class StartupMigrationExtension(StartupExtensionABC):
def __init__(self):
pass
def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC):
pass
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
services.add_transient(MigrationService)
services.add_transient(MigrationABC, InitialMigration)

View File

@ -1,8 +1,23 @@
{ {
"common": { "common": {
"hello-world": "Hallo Welt" "hello_world": "Hallo Welt",
"bot_has_no_permission_message": "Ey!!!\nWas soll das?\nIch habe keine Berechtigungen :(\nScheiß System...",
"no_permission_message": "Nein!\nIch höre nicht auf dich ¯\\_(ツ)_/¯"
}, },
"commands": { "modules": {
"base": {
"welcome_message": "Hello There!\nIch bin Gismo und heiße dich bei {} herzlichst willkommen!",
"welcome_message_for_team": "{} hat gerade das Irrenhaus betreten.",
"goodbye_message": "Schade das du uns so schnell verlässt :(",
"purge_message": "Na gut..., ich lösche alle Nachrichten wenns sein muss.",
"afk_command_channel_missing_message": "Zu unfähig einem Sprachkanal beizutreten?",
"afk_command_move_message": "Ich verschiebe dich ja schon... (◔_◔)",
"pong": "Pong"
},
"boot_log": {
"login_message": "Ich bin on the line :D\nDer Scheiß hat {} Sekunden gedauert"
},
"database": {},
"permission": {}
} }
} }

View File

@ -1 +1,26 @@
# -*- coding: utf-8 -*-
"""
bot-core Keksdose bot - core
~~~~~~~~~~~~~~~~~~~
Discord bot for the Keksdose discord Server - core package
:copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'bot_core'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.0.0.dev1'
from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='0', micro='0.dev1')

View File

View File

@ -0,0 +1,16 @@
from abc import ABC, abstractmethod
class ClientUtilsServiceABC(ABC):
@abstractmethod
def __init__(self): pass
@abstractmethod
def received_command(self, guild_id: int): pass
@abstractmethod
def moved_user(self, guild_id: int): pass
@abstractmethod
def get_client(self, dc_ic: int, guild_id: int): pass

View File

@ -0,0 +1,27 @@
from abc import ABC, abstractmethod
from typing import Union
import discord
from cpl_query.extension import List
from discord.ext.commands import Context
class MessageServiceABC(ABC):
@abstractmethod
def __init__(self): pass
@abstractmethod
async def delete_messages(self, messages: List[discord.Message], guild_id: int): pass
@abstractmethod
async def delete_message(self, message: discord.Message): pass
@abstractmethod
async def send_channel_message(self, channel: discord.TextChannel, message: Union[str, discord.Embed]): pass
@abstractmethod
async def send_dm_message(self, message: Union[str, discord.Embed], receiver: Union[discord.User, discord.Member]): 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): pass

View File

View File

@ -0,0 +1,32 @@
import traceback
from cpl_core.configuration import ConfigurationModelABC
from cpl_core.console import Console
from cpl_query.extension import List
from bot_core.configuration.server_settings import ServerSettings
class BotSettings(ConfigurationModelABC):
def __init__(self):
ConfigurationModelABC.__init__(self)
self._servers: List[ServerSettings] = List()
@property
def servers(self) -> List[ServerSettings]:
return self._servers
def from_dict(self, settings: dict):
try:
servers = List(ServerSettings)
for s in settings:
st = ServerSettings()
settings[s]['Id'] = s
st.from_dict(settings[s])
servers.append(st)
self._servers = servers
except Exception as e:
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings')
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')

View File

@ -0,0 +1,29 @@
import traceback
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl_core.console import Console
class ServerSettings(ConfigurationModelABC):
def __init__(self):
ConfigurationModelABC.__init__(self)
self._id: int = 0
self._message_delete_timer: int = 0
@property
def id(self) -> int:
return self._id
@property
def message_delete_timer(self) -> int:
return self._message_delete_timer
def from_dict(self, settings: dict):
try:
self._id = int(settings['Id'])
self._message_delete_timer = int(settings['MessageDeleteTimer'])
except Exception as e:
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in settings')
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')

View File

View File

@ -0,0 +1,42 @@
from cpl_core.database.context import DatabaseContextABC
from cpl_discord.service import DiscordBotServiceABC
from bot_core.abc.client_utils_service_abc import ClientUtilsServiceABC
from bot_data.abc.client_repository_abc import ClientRepositoryABC
from bot_data.abc.server_repository_abc import ServerRepositoryABC
class ClientUtilsService(ClientUtilsServiceABC):
def __init__(
self,
bot: DiscordBotServiceABC,
servers: ServerRepositoryABC,
clients: ClientRepositoryABC,
db: DatabaseContextABC
):
ClientUtilsServiceABC.__init__(self)
self._bot = bot
self._servers = servers
self._clients = clients
self._db = db
def received_command(self, guild_id: int):
server = self._servers.get_server_by_discord_id(guild_id)
client = self._clients.find_client_by_discord_id_and_server_id(self._bot.user.id, server.server_id)
client.received_command_count += 1
self._clients.update_client(client)
self._db.save_changes()
def moved_user(self, guild_id: int):
server = self._servers.get_server_by_discord_id(guild_id)
client = self._clients.find_client_by_discord_id_and_server_id(self._bot.user.id, server.server_id)
client.moved_users_count += 1
self._clients.update_client(client)
self._db.save_changes()
def get_client(self, dc_ic: int, guild_id: int):
server = self._servers.get_server_by_discord_id(guild_id)
client = self._clients.find_client_by_discord_id_and_server_id(self._bot.user.id, server.server_id)
return client

View File

@ -0,0 +1,105 @@
import asyncio
from typing import Union
import discord
from cpl_core.configuration.configuration_abc import ConfigurationABC
from cpl_core.database.context.database_context_abc import DatabaseContextABC
from cpl_core.logging import LoggerABC
from cpl_discord.service import DiscordBotServiceABC
from cpl_query.extension import List
from discord.ext.commands import Context
from bot_core.abc.message_service_abc import MessageServiceABC
from bot_core.configuration.server_settings import ServerSettings
from bot_data.abc.client_repository_abc import ClientRepositoryABC
class MessageService(MessageServiceABC):
def __init__(self, config: ConfigurationABC, logger: LoggerABC, bot: DiscordBotServiceABC, clients: ClientRepositoryABC, db: DatabaseContextABC):
self._config = config
self._logger = logger
self._bot = bot
self._clients = clients
self._db = db
async def delete_messages(self, messages: List[discord.Message], guild_id: int):
self._logger.debug(__name__, f'Try to delete {messages.count()} messages')
server_st: ServerSettings = self._config.get_configuration(f'ServerSettings_{guild_id}')
await asyncio.sleep(server_st.message_delete_timer)
for message in messages:
await self.delete_message(message, mass_delete=True)
self._logger.debug(__name__, 'Deleting messages finished')
async def delete_message(self, message: discord.Message, mass_delete=False):
server_st: ServerSettings = self._config.get_configuration(f'ServerSettings_{message.guild.id}')
if not mass_delete:
await asyncio.sleep(server_st.message_delete_timer)
self._logger.debug(__name__, f'Try to delete message:\n\t{message}\n\t{message.content}')
guild_id = message.guild.id
try:
await message.delete()
except Exception as e:
self._logger.error(__name__, f'Deleting message failed', e)
else:
self._clients.append_deleted_message_count(self._bot.user.id, guild_id, 1)
self._db.save_changes()
self._logger.info(__name__, f'Deleted message {message}')
async def send_channel_message(self, channel: discord.TextChannel, message: Union[str, discord.Embed]):
self._logger.debug(__name__, f'Try to send message\n\t{message}\n\tto: {channel}')
msg = None
try:
if isinstance(message, discord.Embed):
msg = await channel.send(embed=message)
else:
msg = await channel.send(message)
except Exception as e:
self._logger.error(__name__, f'Send message to channel {channel.id} failed', e)
else:
self._logger.info(__name__, f'Sent message to channel {channel.id}')
self._clients.append_sent_message_count(self._bot.user.id, channel.guild.id, 1)
self._db.save_changes()
await self.delete_message(msg)
async def send_dm_message(self, message: Union[str, discord.Embed], receiver: Union[discord.User, discord.Member]):
self._logger.debug(__name__, f'Try to send message\n\t{message}\n\tto: {receiver}')
try:
if isinstance(message, discord.Embed):
msg = await receiver.send(embed=message)
else:
msg = await receiver.send(message)
except Exception as e:
self._logger.error(__name__, f'Send message to user {receiver.id} failed', e)
else:
self._clients.append_sent_message_count(self._bot.user.id, receiver.guild.id, 1)
self._db.save_changes()
self._logger.info(__name__, f'Sent message to user {receiver.id}')
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):
if ctx 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: {ctx.channel}')
msg = None
try:
if isinstance(message, discord.Embed):
msg = await ctx.send(embed=message, file=file)
else:
msg = await ctx.send(message, file=file)
except Exception as e:
self._logger.error(__name__, f'Send message to channel {ctx.channel.id} failed', e)
else:
self._logger.info(__name__, f'Sent message to channel {ctx.channel.id}')
self._clients.append_sent_message_count(self._bot.user.id, ctx.guild.id, 1)
self._db.save_changes()
if wait_before_delete is not None:
await asyncio.sleep(wait_before_delete)
if is_persistent:
await self.delete_message(ctx.message)
return
await self.delete_messages(List(discord.Message, [msg, ctx.message]), ctx.guild.id)

View File

@ -1 +1,26 @@
# imports: # -*- coding: utf-8 -*-
"""
bot-data Keksdose bot - data
~~~~~~~~~~~~~~~~~~~
Discord bot for the Keksdose discord Server - database package
:copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'bot_data'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.0.0.dev1'
from collections import namedtuple
# imports
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='0', micro='0.dev1')

View File

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
bot-data Keksdose bot - data
~~~~~~~~~~~~~~~~~~~
Discord bot for the Keksdose discord Server - database package
:copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'bot_data.abc'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.0.0.dev1'
from collections import namedtuple
# imports
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='0', micro='0.dev1')

View File

@ -0,0 +1,53 @@
from abc import ABC, abstractmethod
from typing import Optional
from cpl_query.extension import List
from bot_data.model.client import Client
class ClientRepositoryABC(ABC):
@abstractmethod
def __init__(self): pass
@abstractmethod
def get_clients(self) -> List[Client]: pass
@abstractmethod
def get_client_by_id(self, client_id: int) -> Client: pass
@abstractmethod
def get_client_by_discord_id(self, discord_id: int) -> Client: pass
@abstractmethod
def find_client_by_discord_id(self, discord_id: int) -> Optional[Client]: pass
@abstractmethod
def find_client_by_server_id(self, server_id: int) -> Optional[Client]: pass
@abstractmethod
def find_client_by_discord_id_and_server_id(self, discord_id: int, server_id: int) -> Optional[Client]: pass
@abstractmethod
def add_client(self, client: Client): pass
@abstractmethod
def update_client(self, client: Client): pass
@abstractmethod
def delete_client(self, client: Client): pass
@abstractmethod
def append_sent_message_count(self, client_id: int, server_id: int, value: int): pass
@abstractmethod
def append_received_message_count(self, client_id: int, server_id: int, value: int): pass
@abstractmethod
def append_deleted_message_count(self, client_id: int, server_id: int, value: int): pass
@abstractmethod
def append_received_command_count(self, client_id: int, server_id: int, value: int): pass
@abstractmethod
def append_moved_users_count(self, client_id: int, server_id: int, value: int): pass

View File

@ -0,0 +1,30 @@
from abc import ABC, abstractmethod
from typing import Optional
from cpl_query.extension import List
from bot_data.model.known_user import KnownUser
class KnownUserRepositoryABC(ABC):
@abstractmethod
def __init__(self): pass
@abstractmethod
def get_users(self) -> List[KnownUser]: pass
@abstractmethod
def get_user_by_id(self, id: int) -> KnownUser: pass
@abstractmethod
def get_user_by_discord_id(self, discord_id: int) -> KnownUser: pass
@abstractmethod
def find_user_by_discord_id(self, discord_id: int) -> Optional[KnownUser]: pass
@abstractmethod
def add_user(self, known_user: KnownUser): pass
@abstractmethod
def delete_user(self, known_user: KnownUser): pass

View File

@ -0,0 +1,13 @@
from abc import ABC, abstractmethod
class MigrationABC(ABC):
@abstractmethod
def __init__(self): pass
@abstractmethod
def upgrade(self): pass
@abstractmethod
def downgrade(self): pass

View File

@ -0,0 +1,33 @@
from abc import ABC, abstractmethod
from typing import Optional
from cpl_query.extension import List
from bot_data.model.server import Server
class ServerRepositoryABC(ABC):
@abstractmethod
def __init__(self): pass
@abstractmethod
def get_servers(self) -> List[Server]: pass
@abstractmethod
def get_server_by_id(self, id: int) -> Server: pass
@abstractmethod
def get_server_by_discord_id(self, discord_id: int) -> Server: pass
@abstractmethod
def find_server_by_discord_id(self, discord_id: int) -> Optional[Server]: pass
@abstractmethod
def add_server(self, server: Server): pass
@abstractmethod
def update_server(self, server: Server): pass
@abstractmethod
def delete_server(self, server: Server): pass

View File

@ -0,0 +1,35 @@
from abc import ABC, abstractmethod
from typing import Optional
from cpl_query.extension import List
from bot_data.model.user_joined_server import UserJoinedServer
class UserJoinedServerRepositoryABC(ABC):
@abstractmethod
def __init__(self): pass
@abstractmethod
def get_user_joined_servers(self) -> List[UserJoinedServer]: pass
@abstractmethod
def get_user_joined_server_by_id(self, id: int) -> UserJoinedServer: pass
@abstractmethod
def get_user_joined_servers_by_user_id(self, user_id: int) -> list[UserJoinedServer]: pass
@abstractmethod
def get_active_user_joined_server_by_user_id(self, user_id: int) -> UserJoinedServer: pass
@abstractmethod
def find_active_user_joined_server_by_user_id(self, user_id: int) -> Optional[UserJoinedServer]: pass
@abstractmethod
def add_user_joined_server(self, user_joined_server: UserJoinedServer): pass
@abstractmethod
def update_user_joined_server(self, user_joined_server: UserJoinedServer): pass
@abstractmethod
def delete_user_joined_server(self, user_joined_server: UserJoinedServer): pass

View File

@ -0,0 +1,37 @@
from abc import ABC, abstractmethod
from typing import Optional
from cpl_query.extension import List
from bot_data.model.user_joined_voice_channel import UserJoinedVoiceChannel
class UserJoinedVoiceChannelRepositoryABC(ABC):
@abstractmethod
def __init__(self): pass
@abstractmethod
def get_user_joined_voice_channels(self) -> List[UserJoinedVoiceChannel]: pass
@abstractmethod
def get_user_joined_voice_channel_by_id(self, id: int) -> UserJoinedVoiceChannel: pass
@abstractmethod
def get_user_joined_voice_channels_by_user_id(self, user_id: int) -> list[UserJoinedVoiceChannel]: pass
@abstractmethod
def get_active_user_joined_voice_channel_by_user_id(self, user_id: int) -> UserJoinedVoiceChannel: pass
@abstractmethod
def find_active_user_joined_voice_channel_by_user_id(self, user_id: int) -> Optional[UserJoinedVoiceChannel]: pass
@abstractmethod
def find_active_user_joined_voice_channels_by_user_id(self, user_id: int) -> List[Optional[UserJoinedVoiceChannel]]: pass
@abstractmethod
def add_user_joined_voice_channel(self, user_joined_voice_channel: UserJoinedVoiceChannel): pass
@abstractmethod
def update_user_joined_voice_channel(self, user_joined_voice_channel: UserJoinedVoiceChannel): pass
@abstractmethod
def delete_user_joined_voice_channel(self, user_joined_voice_channel: UserJoinedVoiceChannel): pass

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.user import User
class UserRepositoryABC(ABC):
@abstractmethod
def __init__(self): pass
@abstractmethod
def get_users(self) -> List[User]: pass
@abstractmethod
def get_user_by_id(self, id: int) -> User: pass
@abstractmethod
def get_users_by_discord_id(self, discord_id: int) -> List[User]: pass
@abstractmethod
def get_user_by_discord_id_and_server_id(self, discord_id: int, server_id: int) -> User: pass
@abstractmethod
def find_user_by_discord_id_and_server_id(self, discord_id: int, server_id: int) -> Optional[User]: pass
@abstractmethod
def add_user(self, user: User): pass
@abstractmethod
def update_user(self, user: User): pass
@abstractmethod
def delete_user(self, user: User): pass

View File

@ -0,0 +1,33 @@
from cpl_core.database import DatabaseSettings
from cpl_core.database.context import DatabaseContext
from cpl_core.logging import LoggerABC
class DBContext(DatabaseContext):
def __init__(self, logger: LoggerABC):
self._logger = logger
DatabaseContext.__init__(self)
def connect(self, database_settings: DatabaseSettings):
try:
self._logger.debug(__name__, "Connecting to database")
self._db.connect(database_settings)
self.save_changes()
self._logger.info(__name__, "Connected to database")
except Exception as e:
self._logger.fatal(__name__, "Connecting to database failed", e)
def save_changes(self):
try:
self._logger.trace(__name__, "Save changes")
super(DBContext, self).save_changes()
self._logger.debug(__name__, "Saved changes")
except Exception as e:
self._logger.error(__name__, "Saving changes failed", e)
def select(self, statement: str) -> list[tuple]:
return super(DBContext, self).select(statement)

View File

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
bot-data Keksdose bot - data
~~~~~~~~~~~~~~~~~~~
Discord bot for the Keksdose discord Server - database package
:copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'bot_data.migration'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.0.0.dev1'
from collections import namedtuple
# imports
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='0', micro='0.dev1')

View File

@ -0,0 +1,123 @@
from cpl_core.logging import LoggerABC
from bot_data.abc.migration_abc import MigrationABC
from bot_data.db_context import DBContext
class InitialMigration(MigrationABC):
def __init__(self, logger: LoggerABC, db: DBContext):
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 `MigrationHistory` (
`MigrationId` VARCHAR(255),
`CreatedAt` DATETIME(6),
`LastModifiedAt` DATETIME(6),
PRIMARY KEY(`MigrationId`)
);
""")
)
self._cursor.execute(
str(f"""
CREATE TABLE IF NOT EXISTS `Servers` (
`ServerId` BIGINT NOT NULL AUTO_INCREMENT,
`DiscordServerId` BIGINT NOT NULL,
`CreatedAt` DATETIME(6),
`LastModifiedAt` DATETIME(6),
PRIMARY KEY(`ServerId`)
);
""")
)
self._cursor.execute(
str(f"""
CREATE TABLE IF NOT EXISTS `Users` (
`UserId` BIGINT NOT NULL AUTO_INCREMENT,
`DiscordId` BIGINT NOT NULL,
`XP` BIGINT NOT NULL DEFAULT 0,
`ServerId` BIGINT,
`CreatedAt` DATETIME(6),
`LastModifiedAt` DATETIME(6),
FOREIGN KEY (`ServerId`) REFERENCES Servers(`ServerId`),
PRIMARY KEY(`UserId`)
);
""")
)
self._cursor.execute(
str(f"""
CREATE TABLE IF NOT EXISTS `Clients` (
`ClientId` BIGINT NOT NULL AUTO_INCREMENT,
`DiscordClientId` BIGINT NOT NULL,
`SentMessageCount` BIGINT NOT NULL DEFAULT 0,
`ReceivedMessageCount` BIGINT NOT NULL DEFAULT 0,
`DeletedMessageCount` BIGINT NOT NULL DEFAULT 0,
`ReceivedCommandsCount` BIGINT NOT NULL DEFAULT 0,
`MovedUsersCount` BIGINT NOT NULL DEFAULT 0,
`ServerId` BIGINT,
`CreatedAt` DATETIME(6),
`LastModifiedAt` DATETIME(6),
FOREIGN KEY (`ServerId`) REFERENCES Servers(`ServerId`),
PRIMARY KEY(`ClientId`)
);
""")
)
self._cursor.execute(
str(f"""
CREATE TABLE IF NOT EXISTS `KnownUsers` (
`KnownUserId` BIGINT NOT NULL AUTO_INCREMENT,
`DiscordId` BIGINT NOT NULL,
`CreatedAt` DATETIME(6),
`LastModifiedAt` DATETIME(6),
PRIMARY KEY(`KnownUserId`)
);
""")
)
self._cursor.execute(
str(f"""
CREATE TABLE IF NOT EXISTS `UserJoinedServers` (
`JoinId` BIGINT NOT NULL AUTO_INCREMENT,
`UserId` BIGINT NOT NULL,
`JoinedOn` DATETIME(6) NOT NULL,
`LeavedOn` DATETIME(6),
`CreatedAt` DATETIME(6),
`LastModifiedAt` DATETIME(6),
FOREIGN KEY (`UserId`) REFERENCES Users(`UserId`),
PRIMARY KEY(`JoinId`)
);
""")
)
self._cursor.execute(
str(f"""
CREATE TABLE IF NOT EXISTS `UserJoinedVoiceChannel` (
`JoinId` BIGINT NOT NULL AUTO_INCREMENT,
`UserId` BIGINT NOT NULL,
`DiscordChannelId` BIGINT NOT NULL,
`JoinedOn` DATETIME(6) NOT NULL,
`LeavedOn` DATETIME(6),
`CreatedAt` DATETIME(6),
`LastModifiedAt` DATETIME(6),
FOREIGN KEY (`UserId`) REFERENCES Users(`UserId`),
PRIMARY KEY(`JoinId`)
);
""")
)
def downgrade(self):
self._cursor.execute('DROP TABLE `Servers`;')
self._cursor.execute('DROP TABLE `Users`;')
self._cursor.execute('DROP TABLE `Clients`;')
self._cursor.execute('DROP TABLE `KnownUsers`;')
self._cursor.execute('DROP TABLE `UserJoinedServers`;')
self._cursor.execute('DROP TABLE `UserJoinedVoiceChannel`;')

View File

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
bot-data Keksdose bot - data
~~~~~~~~~~~~~~~~~~~
Discord bot for the Keksdose discord Server - database package
:copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'bot_data.model'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.0.0.dev1'
from collections import namedtuple
# imports
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='0', micro='0.dev1')

View File

@ -0,0 +1,170 @@
from datetime import datetime
from cpl_core.database import TableABC
from bot_data.model.server import Server
class Client(TableABC):
def __init__(self,
dc_id: int,
smc: int,
rmc: int,
dmc: int,
rcc: int,
muc: int,
server: Server,
created_at: datetime = None,
modified_at: datetime = None,
id=0
):
self._client_id = id
self._discord_client_id = dc_id
self._sent_message_count = smc
self._received_message_count = rmc
self._deleted_message_count = dmc
self._received_command_count = rcc
self._moved_users_count = muc
self._server: 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 client_id(self) -> int:
return self._client_id
@property
def discord_id(self) -> int:
return self._discord_client_id
@property
def sent_message_count(self) -> int:
return self._sent_message_count
@sent_message_count.setter
def sent_message_count(self, value: int):
self._modified_at = datetime.now().isoformat()
self._sent_message_count = value
@property
def received_message_count(self) -> int:
return self._received_message_count
@received_message_count.setter
def received_message_count(self, value: int):
self._modified_at = datetime.now().isoformat()
self._received_message_count = value
@property
def deleted_message_count(self) -> int:
return self._deleted_message_count
@deleted_message_count.setter
def deleted_message_count(self, value: int):
self._modified_at = datetime.now().isoformat()
self._deleted_message_count = value
@property
def received_command_count(self) -> int:
return self._received_command_count
@received_command_count.setter
def received_command_count(self, value: int):
self._modified_at = datetime.now().isoformat()
self._received_command_count = value
@property
def moved_users_count(self) -> int:
return self._moved_users_count
@moved_users_count.setter
def moved_users_count(self, value: int):
self._modified_at = datetime.now().isoformat()
self._moved_users_count = value
@property
def server(self) -> Server:
return self._server
@staticmethod
def get_select_all_string() -> str:
return str(f"""
SELECT * FROM `Clients`;
""")
@staticmethod
def get_select_by_id_string(id: int) -> str:
return str(f"""
SELECT * FROM `Clients`
WHERE `ClientId` = {id};
""")
@staticmethod
def get_select_by_discord_id_string(id: int) -> str:
return str(f"""
SELECT * FROM `Clients`
WHERE `DiscordClientId` = {id};
""")
@staticmethod
def get_select_by_server_id_string(id: int) -> str:
return str(f"""
SELECT * FROM `Clients`
WHERE `ServerId` = {id};
""")
@staticmethod
def get_select_by_discord_id_and_server_id_string(id: int, server_id: int) -> str:
return str(f"""
SELECT * FROM `Clients`
WHERE `DiscordClientId` = {id}
AND `ServerId` = {server_id};
""")
@property
def insert_string(self) -> str:
return str(f"""
INSERT INTO `Clients` (
`DiscordClientId`,
`SentMessageCount`,
`ReceivedMessageCount`,
`DeletedMessageCount`,
`ReceivedCommandsCount`,
`MovedUsersCount`,
`ServerId`,
`CreatedAt`,
`LastModifiedAt`
) VALUES (
{self._discord_client_id},
{self._sent_message_count},
{self._received_message_count},
{self._deleted_message_count},
{self._received_message_count},
{self._moved_users_count},
{self._server.server_id},
'{self._created_at}',
'{self._modified_at}'
);
""")
@property
def udpate_string(self) -> str:
return str(f"""
UPDATE `Clients`
SET `SentMessageCount` = {self._sent_message_count},
`ReceivedMessageCount` = {self._received_message_count},
`DeletedMessageCount` = {self._deleted_message_count},
`ReceivedCommandsCount` = {self._received_command_count},
`MovedUsersCount` = {self._moved_users_count},
`LastModifiedAt` = '{self._modified_at}'
WHERE `ClientId` = {self._client_id};
""")
@property
def delete_string(self) -> str:
return str(f"""
DELETE FROM `Clients`
WHERE `ClientId` = {self._client_id};
""")

View File

@ -0,0 +1,67 @@
from datetime import datetime
from typing import Optional
from cpl_core.database import TableABC
from bot_data.model.server import Server
class KnownUser(TableABC):
def __init__(self, dc_id: int, created_at: datetime = None, modified_at: datetime = None, id=0):
self._known_user_id = id
self._discord_id = dc_id
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 known_user_id(self) -> int:
return self._known_user_id
@property
def discord_id(self) -> int:
return self._discord_id
@staticmethod
def get_select_all_string() -> str:
return str(f"""
SELECT * FROM `KnownUsers`;
""")
@staticmethod
def get_select_by_id_string(id: int) -> str:
return str(f"""
SELECT * FROM `KnownUsers`
WHERE `KnownUserId` = {id};
""")
@staticmethod
def get_select_by_discord_id_string(id: int) -> str:
return str(f"""
SELECT * FROM `KnownUsers`
WHERE `DiscordId` = {id};
""")
@property
def insert_string(self) -> str:
return str(f"""
INSERT INTO `KnownUsers` (
`DiscordId`, `CreatedAt`, `LastModifiedAt`
) VALUES (
{self._discord_id},
'{self._created_at}',
'{self._modified_at}'
);
""")
@property
def udpate_string(self) -> str:
return ''
@property
def delete_string(self) -> str:
return str(f"""
DELETE FROM `KnownUsers`
WHERE `Id` = {self._known_user_id};
""")

View File

@ -0,0 +1,47 @@
from cpl_core.database import TableABC
class MigrationHistory(TableABC):
def __init__(self, id: str):
self._id = id
TableABC.__init__(self)
@property
def migration_id(self) -> str:
return self._id
@staticmethod
def get_select_by_id_string(id: str) -> str:
return str(f"""
SELECT * FROM `MigrationHistory`
WHERE `MigrationId` = '{id}';
""")
@property
def insert_string(self) -> str:
return str(f"""
INSERT INTO `MigrationHistory` (
`MigrationId`, `CreatedAt`, `LastModifiedAt`
) VALUES (
'{self._id}',
'{self._created_at}',
'{self._modified_at}'
);
""")
@property
def udpate_string(self) -> str:
return str(f"""
UPDATE `MigrationHistory`
SET LastModifiedAt` = '{self._modified_at}'
WHERE `MigrationId` = '{self._id}';
""")
@property
def delete_string(self) -> str:
return str(f"""
DELETE FROM `MigrationHistory`
WHERE `MigrationId` = '{self._id}';
""")

View File

@ -0,0 +1,71 @@
from datetime import datetime
from typing import Optional
from cpl_core.database import TableABC
class Server(TableABC):
def __init__(self, dc_id: int, created_at: datetime=None, modified_at: datetime=None, id=0):
self._server_id = id
self._discord_server_id = dc_id
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 server_id(self) -> int:
return self._server_id
@property
def discord_server_id(self) -> int:
return self._discord_server_id
@staticmethod
def get_select_all_string() -> str:
return str(f"""
SELECT * FROM `Servers`;
""")
@staticmethod
def get_select_by_id_string(id: int) -> str:
return str(f"""
SELECT * FROM `Servers`
WHERE `ServerId` = {id};
""")
@staticmethod
def get_select_by_discord_id_string(id: int) -> str:
return str(f"""
SELECT * FROM `Servers`
WHERE `DiscordServerId` = {id};
""")
@property
def insert_string(self) -> str:
return str(f"""
INSERT INTO `Servers` (
`DiscordServerId`, `CreatedAt`, `LastModifiedAt`
) VALUES (
{self._discord_server_id},
'{self._created_at}',
'{self._modified_at}'
);
""")
@property
def udpate_string(self) -> str:
return str(f"""
UPDATE `Servers`
SET `DiscordServerId` = {self._discord_server_id},
`LastModifiedAt` = '{self._modified_at}'
WHERE `Id` = {self._server_id};
""")
@property
def delete_string(self) -> str:
return str(f"""
DELETE FROM `Servers`
WHERE `Id` = {self._server_id};
""")

View File

@ -0,0 +1,97 @@
from datetime import datetime
from typing import Optional
from cpl_core.database import TableABC
from bot_data.model.server import Server
class User(TableABC):
def __init__(self, dc_id: int, xp: int, server: Optional[Server], created_at: datetime = None, modified_at: datetime = None, id=0):
self._user_id = id
self._discord_id = dc_id
self._xp = xp
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 user_id(self) -> int:
return self._user_id
@property
def discord_id(self) -> int:
return self._discord_id
@property
def xp(self) -> int:
return self._xp
@xp.setter
def xp(self, value: int):
self._modified_at = datetime.now().isoformat()
self._xp = value
@property
def server(self) -> Optional[Server]:
return self._server
@staticmethod
def get_select_all_string() -> str:
return str(f"""
SELECT * FROM `Users`;
""")
@staticmethod
def get_select_by_id_string(id: int) -> str:
return str(f"""
SELECT * FROM `Users`
WHERE `UserId` = {id};
""")
@staticmethod
def get_select_by_discord_id_string(id: int) -> str:
return str(f"""
SELECT * FROM `Users`
WHERE `DiscordId` = {id};
""")
@staticmethod
def get_select_by_discord_id_and_server_id_string(dc_id: int, s_id: int) -> str:
return str(f"""
SELECT * FROM `Users`
WHERE `DiscordId` = {dc_id}
AND `ServerId` = {s_id};
""")
@property
def insert_string(self) -> str:
return str(f"""
INSERT INTO `Users` (
`DiscordId`, `XP`, `ServerId`, `CreatedAt`, `LastModifiedAt`
) VALUES (
{self._discord_id},
{self._xp},
{self._server.server_id},
'{self._created_at}',
'{self._modified_at}'
);
""")
@property
def udpate_string(self) -> str:
return str(f"""
UPDATE `Users`
SET `XP` = {self._xp},
`LastModifiedAt` = '{self._modified_at}'
WHERE `UserId` = {self._user_id};
""")
@property
def delete_string(self) -> str:
return str(f"""
DELETE FROM `Users`
WHERE `UserId` = {self._user_id};
""")

View File

@ -0,0 +1,116 @@
from datetime import datetime
from typing import Optional
from cpl_core.database import TableABC
from bot_data.model.user import User
from bot_data.model.server import Server
class UserJoinedServer(TableABC):
def __init__(self, user: User, joined_on: datetime, leaved_on: datetime=None, created_at: datetime=None, modified_at: datetime=None, id=0):
self._join_id = id
self._user = user
self._joined_on = joined_on
self._leaved_on = leaved_on
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 join_id(self) -> int:
return self._join_id
@property
def user(self) -> User:
return self._user
@property
def joined_on(self) -> datetime:
return self._joined_on
@joined_on.setter
def joined_on(self, value: datetime):
self._modified_at = datetime.now()
self.joined_on = value
@property
def leaved_on(self) -> datetime:
return self._leaved_on
@leaved_on.setter
def leaved_on(self, value: datetime):
self._modified_at = datetime.now()
self._leaved_on = value
@staticmethod
def get_select_all_string() -> str:
return str(f"""
SELECT * FROM `UserJoinedServers`;
""")
@staticmethod
def get_select_by_id_string(id: int) -> str:
return str(f"""
SELECT * FROM `UserJoinedServers`
WHERE `JoinId` = {id};
""")
@staticmethod
def get_select_by_user_id_string(id: int) -> str:
return str(f"""
SELECT * FROM `UserJoinedServers`
WHERE `UserId` = {id};
""")
@staticmethod
def get_select_active_by_user_id_string(id: int) -> str:
return str(f"""
SELECT * FROM `UserJoinedServers`
WHERE `UserId` = {id}
AND `LeavedOn` IS NULL;
""")
@property
def insert_string(self) -> str:
if self._leaved_on is not None:
return str(f"""
INSERT INTO `UserJoinedServers` (
`UserId`, `JoinedOn`, `LeavedOn`, `CreatedAt`, `LastModifiedAt`
) VALUES (
{self._user.user_id},
'{self._joined_on}',
'{self._leaved_on}',
'{self._created_at}',
'{self._modified_at}'
);
""")
else:
return str(f"""
INSERT INTO `UserJoinedServers` (
`UserId`, `JoinedOn`, `CreatedAt`, `LastModifiedAt`
) VALUES (
{self._user.user_id},
'{self._joined_on}',
'{self._created_at}',
'{self._modified_at}'
);
""")
@property
def udpate_string(self) -> str:
return str(f"""
UPDATE `UserJoinedServers`
SET `LeavedOn` = '{self._leaved_on}',
`LastModifiedAt` = '{self._modified_at}'
WHERE `UserId` = {self._user.user_id};
""")
@property
def delete_string(self) -> str:
return str(f"""
DELETE FROM `UserJoinedServers`
WHERE `Id` = {self._join_id};
""")

View File

@ -0,0 +1,121 @@
from datetime import datetime
from cpl_core.database import TableABC
from bot_data.model.user import User
class UserJoinedVoiceChannel(TableABC):
def __init__(self, user: User, dc_channel_id: int, joined_on: datetime, leaved_on: datetime = None, created_at: datetime = None, modified_at: datetime = None, id=0):
self._join_id = id
self._dc_channel_id = dc_channel_id
self._user = user
self._joined_on = joined_on
self._leaved_on = leaved_on
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 join_id(self) -> int:
return self._join_id
@property
def dc_channel_id(self) -> int:
return self._dc_channel_id
@property
def user(self) -> User:
return self._user
@property
def joined_on(self) -> datetime:
return self._joined_on
@joined_on.setter
def joined_on(self, value: datetime):
self._modified_at = datetime.now()
self.joined_on = value
@property
def leaved_on(self) -> datetime:
return self._leaved_on
@leaved_on.setter
def leaved_on(self, value: datetime):
self._modified_at = datetime.now()
self._leaved_on = value
@staticmethod
def get_select_all_string() -> str:
return str(f"""
SELECT * FROM `UserJoinedVoiceChannel`;
""")
@staticmethod
def get_select_by_id_string(id: int) -> str:
return str(f"""
SELECT * FROM `UserJoinedVoiceChannel`
WHERE `JoinId` = {id};
""")
@staticmethod
def get_select_by_user_id_string(id: int) -> str:
return str(f"""
SELECT * FROM `UserJoinedVoiceChannel`
WHERE `UserId` = {id};
""")
@staticmethod
def get_select_active_by_user_id_string(id: int) -> str:
return str(f"""
SELECT * FROM `UserJoinedVoiceChannel`
WHERE `UserId` = {id}
AND `LeavedOn` IS NULL;
""")
@property
def insert_string(self) -> str:
if self._leaved_on is not None:
return str(f"""
INSERT INTO `UserJoinedVoiceChannel` (
`UserId`, `DiscordChannelId`, `JoinedOn`, `LeavedOn`, `CreatedAt`, `LastModifiedAt`
) VALUES (
{self._user.user_id},
{self._dc_channel_id},
'{self._joined_on}',
'{self._leaved_on}',
'{self._created_at}',
'{self._modified_at}'
);
""")
else:
return str(f"""
INSERT INTO `UserJoinedVoiceChannel` (
`UserId`, `DiscordChannelId`, `JoinedOn`, `CreatedAt`, `LastModifiedAt`
) VALUES (
{self._user.user_id},
{self._dc_channel_id},
'{self._joined_on}',
'{self._created_at}',
'{self._modified_at}'
);
""")
@property
def udpate_string(self) -> str:
return str(f"""
UPDATE `UserJoinedVoiceChannel`
SET `LeavedOn` = '{self._leaved_on}',
`LastModifiedAt` = '{self._modified_at}'
WHERE `JoinId` = {self._join_id};
""")
@property
def delete_string(self) -> str:
return str(f"""
DELETE FROM `UserJoinedVoiceChannel`
WHERE `JoinId` = {self._join_id};
""")

View File

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
bot-data Keksdose bot - data
~~~~~~~~~~~~~~~~~~~
Discord bot for the Keksdose discord Server - database package
:copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'bot_data.service'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.0.0.dev1'
from collections import namedtuple
# imports
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='0', micro='0.dev1')

View File

@ -0,0 +1,173 @@
from typing import Optional
from cpl_core.database.context import DatabaseContextABC
from cpl_core.logging import LoggerABC
from cpl_query.extension import List
from bot_data.abc.client_repository_abc import ClientRepositoryABC
from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.model.client import Client
class ClientRepositoryService(ClientRepositoryABC):
def __init__(self, logger: LoggerABC, db_context: DatabaseContextABC, servers: ServerRepositoryABC):
self._logger = logger
self._context = db_context
self._servers = servers
ClientRepositoryABC.__init__(self)
def get_clients(self) -> List[Client]:
clients = List(Client)
self._logger.trace(__name__, f'Send SQL command: {Client.get_select_all_string()}')
results = self._context.select(Client.get_select_all_string())
for result in results:
self._logger.trace(__name__, f'Get client with id {result[0]}')
clients.append(Client(
result[1],
result[2],
result[3],
result[4],
result[5],
result[6],
self._servers.get_server_by_id(result[7]),
id=result[0]
))
return clients
def get_client_by_id(self, client_id: int) -> Client:
self._logger.trace(__name__, f'Send SQL command: {Client.get_select_by_id_string(client_id)}')
result = self._context.select(Client.get_select_by_id_string(client_id))
return Client(
result[1],
result[2],
result[3],
result[4],
result[5],
result[6],
self._servers.get_server_by_id(result[7]),
id=result[0]
)
def get_client_by_discord_id(self, discord_id: int) -> Client:
self._logger.trace(__name__, f'Send SQL command: {Client.get_select_by_discord_id_string(discord_id)}')
result = self._context.select(Client.get_select_by_discord_id_string(discord_id))[0]
return Client(
result[1],
result[2],
result[3],
result[4],
result[5],
result[6],
self._servers.get_server_by_id(result[7]),
id=result[0]
)
def find_client_by_discord_id(self, discord_id: int) -> Optional[Client]:
self._logger.trace(__name__, f'Send SQL command: {Client.get_select_by_discord_id_string(discord_id)}')
result = self._context.select(Client.get_select_by_discord_id_string(discord_id))
if result is None or len(result) == 0:
return None
result = result[0]
return Client(
result[1],
result[2],
result[3],
result[4],
result[5],
result[6],
self._servers.get_server_by_id(result[7]),
id=result[0]
)
def find_client_by_server_id(self, discord_id: int) -> Optional[Client]:
self._logger.trace(__name__, f'Send SQL command: {Client.get_select_by_server_id_string(discord_id)}')
result = self._context.select(Client.get_select_by_server_id_string(discord_id))
if result is None or len(result) == 0:
return None
result = result[0]
return Client(
result[1],
result[2],
result[3],
result[4],
result[5],
result[6],
self._servers.get_server_by_id(result[7]),
id=result[0]
)
def find_client_by_discord_id_and_server_id(self, discord_id: int, server_id: int) -> Optional[Client]:
self._logger.trace(__name__, f'Send SQL command: {Client.get_select_by_discord_id_and_server_id_string(discord_id, server_id)}')
result = self._context.select(Client.get_select_by_discord_id_and_server_id_string(discord_id, server_id))
if result is None or len(result) == 0:
return None
result = result[0]
return Client(
result[1],
result[2],
result[3],
result[4],
result[5],
result[6],
self._servers.get_server_by_id(result[7]),
id=result[0]
)
def add_client(self, client: Client):
self._logger.trace(__name__, f'Send SQL command: {client.insert_string}')
self._context.cursor.execute(client.insert_string)
def update_client(self, client: Client):
self._logger.trace(__name__, f'Send SQL command: {client.udpate_string}')
self._context.cursor.execute(client.udpate_string)
def delete_client(self, client: Client):
self._logger.trace(__name__, f'Send SQL command: {client.delete_string}')
self._context.cursor.execute(client.delete_string)
def _get_client_and_server(self, id: int, server_id: int) -> Client:
server = self._servers.find_server_by_discord_id(server_id)
if server is None:
self._logger.warn(__name__, f'Cannot find server by id {server_id}')
raise Exception('Value not found')
client = self.find_client_by_discord_id_and_server_id(id, server.server_id)
if client is None:
self._logger.warn(__name__, f'Cannot find client by ids {id}@{server.server_id}')
raise Exception('Value not found')
return client
def append_sent_message_count(self, client_id: int, server_id: int, value: int):
client = self._get_client_and_server(client_id, server_id)
client.sent_message_count += value
self.update_client(client)
def append_received_message_count(self, client_id: int, server_id: int, value: int):
client = self._get_client_and_server(client_id, server_id)
client.received_message_count += value
self.update_client(client)
def append_deleted_message_count(self, client_id: int, server_id: int, value: int):
client = self._get_client_and_server(client_id, server_id)
client.deleted_message_count += value
self.update_client(client)
def append_received_command_count(self, client_id: int, server_id: int, value: int):
client = self._get_client_and_server(client_id, server_id)
client.received_command_count += value
self.update_client(client)
def append_moved_users_count(self, client_id: int, server_id: int, value: int):
client = self._get_client_and_server(client_id, server_id)
client.moved_users_count += value
self.update_client(client)

View File

@ -0,0 +1,77 @@
from typing import Optional
from cpl_core.database.context import DatabaseContextABC
from cpl_core.logging import LoggerABC
from cpl_query.extension import List
from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC
from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.model.known_user import KnownUser
class KnownUserRepositoryService(KnownUserRepositoryABC):
def __init__(self, logger: LoggerABC, db_context: DatabaseContextABC, servers: ServerRepositoryABC):
self._logger = logger
self._context = db_context
self._servers = servers
KnownUserRepositoryABC.__init__(self)
def get_users(self) -> List[KnownUser]:
users = List(KnownUser)
self._logger.trace(__name__, f'Send SQL command: {KnownUser.get_select_all_string()}')
results = self._context.select(KnownUser.get_select_all_string())
for result in results:
self._logger.trace(__name__, f'Get known_user with id {result[0]}')
users.append(KnownUser(
result[1],
result[2],
result[3],
id=result[0]
))
return users
def get_user_by_id(self, id: int) -> KnownUser:
self._logger.trace(__name__, f'Send SQL command: {KnownUser.get_select_by_id_string(id)}')
result = self._context.select(KnownUser.get_select_by_id_string(id))
return KnownUser(
result[1],
result[2],
result[3],
id=result[0]
)
def get_user_by_discord_id(self, discord_id: int) -> KnownUser:
self._logger.trace(__name__, f'Send SQL command: {KnownUser.get_select_by_discord_id_string(discord_id)}')
result = self._context.select(KnownUser.get_select_by_discord_id_string(discord_id))[0]
return KnownUser(
result[1],
result[2],
result[3],
id=result[0]
)
def find_user_by_discord_id(self, discord_id: int) -> Optional[KnownUser]:
self._logger.trace(__name__, f'Send SQL command: {KnownUser.get_select_by_discord_id_string(discord_id)}')
result = self._context.select(KnownUser.get_select_by_discord_id_string(discord_id))
if result is None or len(result) == 0:
return None
result = result[0]
return KnownUser(
result[1],
result[2],
result[3],
id=result[0]
)
def add_user(self, known_user: KnownUser):
self._logger.trace(__name__, f'Send SQL command: {known_user.insert_string}')
self._context.cursor.execute(known_user.insert_string)
def delete_user(self, known_user: KnownUser):
self._logger.trace(__name__, f'Send SQL command: {known_user.delete_string}')
self._context.cursor.execute(known_user.delete_string)

View File

@ -0,0 +1,45 @@
from typing import Type
from cpl_core.database.context import DatabaseContextABC
from cpl_core.dependency_injection import ServiceProviderABC
from cpl_core.logging import LoggerABC
from bot_data.abc.migration_abc import MigrationABC
from bot_data.model.migration_history import MigrationHistory
class MigrationService:
def __init__(self, logger: LoggerABC, services: ServiceProviderABC, db: DatabaseContextABC):
self._logger = logger
self._services = services
self._db = db
self._cursor = db.cursor
self._migrations: list[Type[MigrationABC]] = MigrationABC.__subclasses__()
def migrate(self):
self._logger.info(__name__, f"Running Migrations")
for migration in self._migrations:
migration_id = migration.__name__
try:
# check if table exists
self._cursor.execute("SHOW TABLES LIKE 'MigrationHistory'")
result = self._cursor.fetchone()
if result:
# there is a table named "tableName"
self._logger.trace(__name__, f"Running SQL Command: {MigrationHistory.get_select_by_id_string(migration_id)}")
migration_from_db = self._db.select(MigrationHistory.get_select_by_id_string(migration_id))
self._logger.trace(__name__, str(migration_from_db))
if migration_from_db is not None and len(migration_from_db) > 0:
continue
self._logger.debug(__name__, f"Running Migration {migration}")
migration_as_service: MigrationABC = self._services.get_service(migration)
migration_as_service.upgrade()
self._cursor.execute(MigrationHistory(migration_id).insert_string)
self._db.save_changes()
except Exception as e:
self._logger.error(__name__, f'Cannot get migration with id {migration}', e)

View File

@ -0,0 +1,71 @@
from typing import Optional
from cpl_core.database.context import DatabaseContextABC
from cpl_core.logging import LoggerABC
from cpl_query.extension import List
from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.model.server import Server
class ServerRepositoryService(ServerRepositoryABC):
def __init__(self, logger: LoggerABC, db_context: DatabaseContextABC):
self._logger = logger
self._context = db_context
ServerRepositoryABC.__init__(self)
def get_servers(self) -> List[Server]:
servers = List(Server)
self._logger.trace(__name__, f'Send SQL command: {Server.get_select_all_string()}')
results = self._context.select(Server.get_select_all_string())
for result in results:
servers.append(Server(
result[1],
id=result[0]
))
return servers
def get_server_by_id(self, server_id: int) -> Server:
self._logger.trace(__name__, f'Send SQL command: {Server.get_select_by_id_string(server_id)}')
result = self._context.select(Server.get_select_by_id_string(server_id))[0]
return Server(
result[1],
id=result[0]
)
def get_server_by_discord_id(self, discord_id: int) -> Server:
self._logger.trace(__name__, f'Send SQL command: {Server.get_select_by_discord_id_string(discord_id)}')
result = self._context.select(Server.get_select_by_discord_id_string(discord_id))[0]
return Server(
result[1],
id=result[0]
)
def find_server_by_discord_id(self, discord_id: int) -> Optional[Server]:
self._logger.trace(__name__, f'Send SQL command: {Server.get_select_by_discord_id_string(discord_id)}')
result = self._context.select(Server.get_select_by_discord_id_string(discord_id))
if result is None or len(result) == 0:
return None
result = result[0]
return Server(
result[1],
result[2],
result[3],
id=result[0]
)
def add_server(self, server: Server):
self._logger.trace(__name__, f'Send SQL command: {server.insert_string}')
self._context.cursor.execute(server.insert_string)
def update_server(self, server: Server):
self._logger.trace(__name__, f'Send SQL command: {server.udpate_string}')
self._context.cursor.execute(server.udpate_string)
def delete_server(self, server: Server):
self._logger.trace(__name__, f'Send SQL command: {server.delete_string}')
self._context.cursor.execute(server.delete_string)

View File

@ -0,0 +1,107 @@
from typing import Optional
from cpl_core.database.context import DatabaseContextABC
from cpl_core.logging import LoggerABC
from cpl_query.extension import List
from bot_data.abc.user_joined_server_repository_abc import \
UserJoinedServerRepositoryABC
from bot_data.abc.user_repository_abc import UserRepositoryABC
from bot_data.model.user import User
from bot_data.model.user_joined_server import UserJoinedServer
class UserJoinedServerRepositoryService(UserJoinedServerRepositoryABC):
def __init__(self, logger: LoggerABC, db_context: DatabaseContextABC, users: UserRepositoryABC):
self._logger = logger
self._context = db_context
self._users = users
UserJoinedServerRepositoryABC.__init__(self)
def get_user_joined_servers(self) -> List[UserJoinedServer]:
joins = List(UserJoinedServer)
self._logger.trace(__name__, f'Send SQL command: {UserJoinedServer.get_select_all_string()}')
results = self._context.select(UserJoinedServer.get_select_all_string())
for result in results:
self._logger.trace(__name__, f'Get user-joined-server with id {result[0]}')
joins.append(UserJoinedServer(
self._users.get_user_by_id(result[1]),
result[2],
result[3],
result[4],
result[5],
id=result[0]
))
return joins
def get_user_joined_server_by_id(self, id: int) -> UserJoinedServer:
self._logger.trace(__name__, f'Send SQL command: {UserJoinedServer.get_select_by_id_string(id)}')
result = self._context.select(UserJoinedServer.get_select_by_id_string(id))[0]
return UserJoinedServer(
self._users.get_user_by_id(result[1]),
result[2],
result[3],
result[4],
result[5],
id=result[0]
)
def get_user_joined_servers_by_user_id(self, user_id: int) -> List[UserJoinedServer]:
joins = List(UserJoinedServer)
self._logger.trace(__name__, f'Send SQL command: {UserJoinedServer.get_select_by_user_id_string(user_id)}')
results = self._context.select(UserJoinedServer.get_select_by_user_id_string(user_id))
for result in results:
joins.append(UserJoinedServer(
self._users.get_user_by_id(result[1]),
result[2],
result[3],
result[4],
result[5],
id=result[0]
))
return joins
def get_active_user_joined_server_by_user_id(self, user_id: int) -> UserJoinedServer:
self._logger.trace(__name__, f'Send SQL command: {UserJoinedServer.get_select_by_user_id_string(user_id)}')
result = self._context.select(UserJoinedServer.get_select_active_by_user_id_string(user_id))[0]
return UserJoinedServer(
self._users.get_user_by_id(result[1]),
result[2],
result[3],
result[4],
result[5],
id=result[0]
)
def find_active_user_joined_server_by_user_id(self, user_id: int) -> Optional[UserJoinedServer]:
self._logger.trace(__name__, f'Send SQL command: {UserJoinedServer.get_select_by_user_id_string(user_id)}')
result = self._context.select(UserJoinedServer.get_select_active_by_user_id_string(user_id))
if result is None or len(result) == 0:
return None
result = result[0]
return UserJoinedServer(
self._users.get_user_by_id(result[1]),
result[2],
result[3],
result[4],
result[5],
id=result[0]
)
def add_user_joined_server(self, user_joined_server: UserJoinedServer):
self._logger.trace(__name__, f'Send SQL command: {user_joined_server.insert_string}')
self._context.cursor.execute(user_joined_server.insert_string)
def update_user_joined_server(self, user_joined_server: UserJoinedServer):
self._logger.trace(__name__, f'Send SQL command: {user_joined_server.udpate_string}')
self._context.cursor.execute(user_joined_server.udpate_string)
def delete_user_joined_server(self, user_joined_server: UserJoinedServer):
self._logger.trace(__name__, f'Send SQL command: {user_joined_server.delete_string}')
self._context.cursor.execute(user_joined_server.delete_string)

View File

@ -0,0 +1,123 @@
from typing import Optional
from cpl_core.database.context import DatabaseContextABC
from cpl_core.logging import LoggerABC
from cpl_query.extension import List, IterableABC
from bot_data.abc.user_repository_abc import UserRepositoryABC
from bot_data.model.user_joined_voice_channel import UserJoinedVoiceChannel
from bot_data.abc.user_joined_voice_channel_abc import UserJoinedVoiceChannelRepositoryABC
class UserJoinedVoiceChannelRepositoryService(UserJoinedVoiceChannelRepositoryABC):
def __init__(self, logger: LoggerABC, db_context: DatabaseContextABC, users: UserRepositoryABC):
self._logger = logger
self._context = db_context
self._users = users
UserJoinedVoiceChannelRepositoryABC.__init__(self)
def get_user_joined_voice_channels(self) -> List[UserJoinedVoiceChannel]:
joins = List(UserJoinedVoiceChannel)
self._logger.trace(__name__, f'Send SQL command: {UserJoinedVoiceChannel.get_select_all_string()}')
results = self._context.select(UserJoinedVoiceChannel.get_select_all_string())
for result in results:
self._logger.trace(__name__, f'Get user-joined-voice-channel with id {result[0]}')
joins.append(UserJoinedVoiceChannel(
self._users.get_user_by_id(result[1]),
result[2],
result[3],
result[4],
result[5],
id=result[0]
))
return joins
def get_user_joined_voice_channel_by_id(self, id: int) -> UserJoinedVoiceChannel:
self._logger.trace(__name__, f'Send SQL command: {UserJoinedVoiceChannel.get_select_by_id_string(id)}')
result = self._context.select(UserJoinedVoiceChannel.get_select_by_id_string(id))[0]
return UserJoinedVoiceChannel(
self._users.get_user_by_id(result[1]),
result[2],
result[3],
result[4],
result[5],
id=result[0]
)
def get_user_joined_voice_channels_by_user_id(self, user_id: int) -> List[UserJoinedVoiceChannel]:
joins = List(UserJoinedVoiceChannel)
self._logger.trace(__name__, f'Send SQL command: {UserJoinedVoiceChannel.get_select_by_user_id_string(user_id)}')
results = self._context.select(UserJoinedVoiceChannel.get_select_by_user_id_string(user_id))
for result in results:
joins.append(UserJoinedVoiceChannel(
self._users.get_user_by_id(result[1]),
result[2],
result[3],
result[4],
result[5],
id=result[0]
))
return joins
def get_active_user_joined_voice_channel_by_user_id(self, user_id: int) -> UserJoinedVoiceChannel:
self._logger.trace(__name__, f'Send SQL command: {UserJoinedVoiceChannel.get_select_by_user_id_string(user_id)}')
result = self._context.select(UserJoinedVoiceChannel.get_select_active_by_user_id_string(user_id))[0]
return UserJoinedVoiceChannel(
self._users.get_user_by_id(result[1]),
result[2],
result[3],
result[4],
result[5],
id=result[0]
)
def find_active_user_joined_voice_channel_by_user_id(self, user_id: int) -> Optional[UserJoinedVoiceChannel]:
self._logger.trace(__name__, f'Send SQL command: {UserJoinedVoiceChannel.get_select_by_user_id_string(user_id)}')
result = self._context.select(UserJoinedVoiceChannel.get_select_active_by_user_id_string(user_id))
if result is None or len(result) == 0:
return None
result = result[0]
return UserJoinedVoiceChannel(
self._users.get_user_by_id(result[1]),
result[2],
result[3],
result[4],
result[5],
id=result[0]
)
def find_active_user_joined_voice_channels_by_user_id(self, user_id: int) -> List[Optional[UserJoinedVoiceChannel]]:
self._logger.trace(__name__, f'Send SQL command: {UserJoinedVoiceChannel.get_select_by_user_id_string(user_id)}')
result = List(UserJoinedVoiceChannel)
db_results = self._context.select(UserJoinedVoiceChannel.get_select_active_by_user_id_string(user_id))
for db_result in db_results:
result.append(UserJoinedVoiceChannel(
self._users.get_user_by_id(db_result[1]),
db_result[2],
db_result[3],
db_result[4],
db_result[5],
id=db_result[0]
))
return result
def add_user_joined_voice_channel(self, user_joined_voice_channel: UserJoinedVoiceChannel):
self._logger.trace(__name__, f'Send SQL command: {user_joined_voice_channel.insert_string}')
self._context.cursor.execute(user_joined_voice_channel.insert_string)
def update_user_joined_voice_channel(self, user_joined_voice_channel: UserJoinedVoiceChannel):
self._logger.trace(__name__, f'Send SQL command: {user_joined_voice_channel.udpate_string}')
self._context.cursor.execute(user_joined_voice_channel.udpate_string)
def delete_user_joined_voice_channel(self, user_joined_voice_channel: UserJoinedVoiceChannel):
self._logger.trace(__name__, f'Send SQL command: {user_joined_voice_channel.delete_string}')
self._context.cursor.execute(user_joined_voice_channel.delete_string)

View File

@ -0,0 +1,99 @@
from typing import Optional
from cpl_core.database.context import DatabaseContextABC
from cpl_core.logging import LoggerABC
from cpl_query.extension import List
from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.abc.user_repository_abc import UserRepositoryABC
from bot_data.model.user import User
class UserRepositoryService(UserRepositoryABC):
def __init__(self, logger: LoggerABC, db_context: DatabaseContextABC, servers: ServerRepositoryABC):
self._logger = logger
self._context = db_context
self._servers = servers
UserRepositoryABC.__init__(self)
def get_users(self) -> List[User]:
users = List(User)
self._logger.trace(__name__, f'Send SQL command: {User.get_select_all_string()}')
results = self._context.select(User.get_select_all_string())
for result in results:
self._logger.trace(__name__, f'Get user with id {result[0]}')
users.append(User(
result[1],
result[2],
self._servers.get_server_by_id(result[3]),
id=result[0]
))
return users
def get_user_by_id(self, id: int) -> User:
self._logger.trace(__name__, f'Send SQL command: {User.get_select_by_id_string(id)}')
result = self._context.select(User.get_select_by_id_string(id))[0]
return User(
result[1],
result[2],
self._servers.get_server_by_id(result[3]),
id=result[0]
)
def get_users_by_discord_id(self, discord_id: int) -> List[User]:
users = List(User)
self._logger.trace(__name__, f'Send SQL command: {User.get_select_by_discord_id_string(discord_id)}')
results = self._context.select(User.get_select_by_discord_id_string(discord_id))
for result in results:
users.append(User(
result[1],
result[2],
self._servers.get_server_by_id(result[3]),
id=result[0]
))
return users
def get_user_by_discord_id_and_server_id(self, discord_id: int, server_id: int) -> User:
self._logger.trace(__name__, f'Send SQL command: {User.get_select_by_discord_id_and_server_id_string(discord_id, server_id)}')
result = self._context.select(User.get_select_by_discord_id_and_server_id_string(discord_id, server_id))[0]
return User(
result[1],
result[2],
self._servers.get_server_by_id(result[3]),
id=result[0]
)
def find_user_by_discord_id_and_server_id(self, discord_id: int, server_id: int) -> Optional[User]:
self._logger.trace(__name__, f'Send SQL command: {User.get_select_by_discord_id_and_server_id_string(discord_id, server_id)}')
result = self._context.select(User.get_select_by_discord_id_and_server_id_string(discord_id, server_id))
if result is None or len(result) == 0:
return None
result = result[0]
return User(
result[1],
result[2],
self._servers.get_server_by_id(result[3]),
result[4],
result[5],
id=result[0]
)
def add_user(self, user: User):
self._logger.trace(__name__, f'Send SQL command: {user.insert_string}')
self._context.cursor.execute(user.insert_string)
def update_user(self, user: User):
self._logger.trace(__name__, f'Send SQL command: {user.udpate_string}')
self._context.cursor.execute(user.udpate_string)
def delete_user(self, user: User):
self._logger.trace(__name__, f'Send SQL command: {user.delete_string}')
self._context.cursor.execute(user.delete_string)

1
src/modules/__init__.py Normal file
View File

@ -0,0 +1 @@
# imports

View File

@ -0,0 +1 @@
# imports

View File

View File

@ -0,0 +1,12 @@
from abc import abstractmethod, ABC
from modules.base.configuration.base_server_settings import BaseServerSettings
class BaseHelperABC(ABC):
def __init__(self):
ABC.__init__(self)
@abstractmethod
def get_config(self, g_id: int) -> BaseServerSettings: pass

View File

@ -0,0 +1,46 @@
{
"ProjectSettings": {
"Name": "base",
"Version": {
"Major": "0",
"Minor": "0",
"Micro": "0"
},
"Author": "",
"AuthorEmail": "",
"Description": "",
"LongDescription": "",
"URL": "",
"CopyrightDate": "",
"CopyrightName": "",
"LicenseName": "",
"LicenseDescription": "",
"Dependencies": [
"cpl-core>=2022.7.0.post2"
],
"DevDependencies": [
"cpl-cli>=2022.7.0.post2"
],
"PythonVersion": ">=3.10.4",
"PythonPath": {
"linux": ""
},
"Classifiers": []
},
"BuildSettings": {
"ProjectType": "library",
"SourcePath": "",
"OutputPath": "../../dist",
"Main": "base.main",
"EntryPoint": "base",
"IncludePackageData": false,
"Included": [],
"Excluded": [
"*/__pycache__",
"*/logs",
"*/tests"
],
"PackageData": {},
"ProjectReferences": []
}
}

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
"""
gismo sh-edraft Gismo
~~~~~~~~~~~~~~~~~~~
sh-edraft Dicord bot Gismo
:copyright: (c) 2021 - 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'modules.base.service'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2021 - 2022 sh-edraft.de'
__version__ = '0.4.2'
from collections import namedtuple
# imports
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='4', micro='2')

View File

@ -0,0 +1,65 @@
from cpl_core.configuration import ConfigurationABC
from cpl_core.database.context import DatabaseContext
from cpl_core.logging import LoggerABC
from cpl_discord.command import DiscordCommandABC
from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslatePipe
from discord import VoiceChannel
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.configuration.server_settings import ServerSettings
from bot_data.abc.client_repository_abc import ClientRepositoryABC
from modules.base.configuration.base_server_settings import BaseServerSettings
class AFKCommand(DiscordCommandABC):
def __init__(
self,
logger: LoggerABC,
config: ConfigurationABC,
message_service: MessageServiceABC,
clients: ClientRepositoryABC,
db: DatabaseContext,
bot: DiscordBotServiceABC,
client_utils: ClientUtilsServiceABC,
translate: TranslatePipe
):
DiscordCommandABC.__init__(self)
self._logger = logger
self._config = config
self._message_service = message_service
self._clients = clients
self._db = db
self._bot = bot
self._client_utils = client_utils
self._t = translate
self._logger.trace(__name__, f'Loaded command service: {type(self).__name__}')
@commands.command()
async def afk(self, ctx: Context):
self._logger.debug(__name__, f'Received command afk {ctx}')
self._client_utils.received_command(ctx.guild.id)
settings: BaseServerSettings = self._config.get_configuration(f'BaseServerSettings_{ctx.guild.id}')
server_settings: ServerSettings = self._config.get_configuration(f'ServerSettings_{ctx.guild.id}')
if ctx.author.voice is None or ctx.author.voice.channel is None:
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.base.afk_command_channel_missing_message'))
self._logger.trace(__name__, f'Finished afk command')
return
self._bot.loop.create_task(self._message_service.send_ctx_msg(ctx, self._t.transform('modules.base.afk_command_move_message')))
channel: VoiceChannel = ctx.guild.get_channel(settings.afk_command_channel_id)
try:
await ctx.author.move_to(channel)
self._client_utils.moved_user(ctx.guild.id)
except Exception as e:
self._logger.error(__name__, f'Cannot move user {ctx.author.id} to channel {ctx.channel.id}', e)
await self._message_service.send_ctx_msg(ctx, self._t.transform('common.no_permission_message'))
self._logger.trace(__name__, f'Finished afk command')

View File

@ -0,0 +1,40 @@
from cpl_core.configuration import ConfigurationABC
from cpl_core.logging import LoggerABC
from cpl_discord.command import DiscordCommandABC
from cpl_discord.service import DiscordBotServiceABC
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 modules.base.configuration.base_server_settings import BaseServerSettings
class HelpCommand(DiscordCommandABC):
def __init__(
self,
config: ConfigurationABC,
logger: LoggerABC,
message_service: MessageServiceABC,
bot: DiscordBotServiceABC,
client_utils: ClientUtilsServiceABC
):
DiscordCommandABC.__init__(self)
self._config = config
self._logger = logger
self._message_service = message_service
self._bot = bot
self._client_utils = client_utils
self._logger.trace(__name__, f'Loaded command service: {type(self).__name__}')
@commands.command()
async def help(self, ctx: Context, persistent_flag: str = None):
self._logger.debug(__name__, f'Received command help {ctx}:{persistent_flag}')
self._client_utils.received_command(ctx.guild.id)
settings: BaseServerSettings = self._config.get_configuration(f'BaseServerSettings_{ctx.guild.id}')
is_persistent = persistent_flag == '--stay'
await self._message_service.send_ctx_msg(ctx, settings.help_command_reference_url, is_persistent=is_persistent)
self._logger.trace(__name__, f'Finished help command')

View File

@ -0,0 +1,94 @@
from datetime import datetime
from cpl_core.configuration import ConfigurationABC
from cpl_core.logging import LoggerABC
from cpl_discord.command import DiscordCommandABC
from cpl_discord.service import DiscordBotServiceABC
from cpl_query.extension import List
from discord.ext import commands
from discord.ext.commands import Context
import bot
from bot_core.abc.client_utils_service_abc import ClientUtilsServiceABC
from bot_core.abc.message_service_abc import MessageServiceABC
from modules.base.configuration.base_server_settings import BaseServerSettings
class InfoCommandService(DiscordCommandABC):
def __init__(
self,
config: ConfigurationABC,
logger: LoggerABC,
message_service: MessageServiceABC,
bot: DiscordBotServiceABC,
client_utils: ClientUtilsServiceABC
):
DiscordCommandABC.__init__(self)
self._config = config
self._logger = logger
self._message_service = message_service
self._bot = bot
self._client_utils = client_utils
self._logger.trace(__name__, f'Loaded command service: {type(self).__name__}')
@commands.command()
async def info(self, ctx: Context):
#
# Todo: Use native embeds!!!
#
self._logger.debug(__name__, f'Received command info {ctx}')
self._client_utils.received_command(ctx.guild.id)
client = self._client_utils.get_client(self._bot.user.id, ctx.guild.id)
settings: BaseServerSettings = self._config.get_configuration(f'BaseServerSettings_{ctx.guild.id}')
embed_description = EmbedDescription(
settings.info_command_message.embed_description.title,
settings.info_command_message.embed_description.description,
settings.info_command_message.embed_description.url,
settings.info_command_message.embed_description.color,
List(EmbedDescriptionField),
settings.info_command_message.embed_description.footer
)
for i in range(len(settings.info_command_message.embed_description.fields)):
settings_field = settings.info_command_message.embed_description.fields[i]
field = EmbedDescriptionField(settings_field.name, settings_field.value, settings_field.inline)
if settings_field.value == '$version':
field.value = bot.__version__
elif settings_field.value == '$ontime':
start_time = self._config.get_configuration('Bot_StartTime')
ontime = round((datetime.now() - datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S.%f')).total_seconds()/3600, 2)
field.value = f'{ontime}h'
elif settings_field.value == '$sent_message_count':
field.value = client.sent_message_count
elif settings_field.value == '$received_message_count':
field.value = client.received_message_count
elif settings_field.value == '$deleted_message_count':
field.value = client.deleted_message_count
elif settings_field.value == '$received_command_count':
field.value = client.received_command_count
elif settings_field.value == '$moved_users_count':
field.value = client.moved_users_count
elif settings_field.value == '$modules':
field.value = ''
for module in ModuleABC.__subclasses__():
field.value += f'{module.__name__}\n'
embed_description.fields.append(field)
await self._message_service.send_ctx_msg(
ctx,
EmbedService.get_embed(embed_description)
)
self._logger.trace(__name__, f'Finished info command')

View File

@ -0,0 +1,37 @@
from cpl_core.logging import LoggerABC
from cpl_discord.command import DiscordCommandABC
from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslatePipe
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
class PingCommand(DiscordCommandABC):
def __init__(
self,
logger: LoggerABC,
message_service: MessageServiceABC,
bot: DiscordBotServiceABC,
client_utils: ClientUtilsServiceABC,
translate: TranslatePipe
):
DiscordCommandABC.__init__(self)
self._logger = logger
self._message_service = message_service
self._bot = bot
self._client_utils = client_utils
self._t = translate
self._logger.trace(__name__, f'Loaded command service: {type(self).__name__}')
@commands.command()
async def ping(self, ctx: Context):
self._logger.debug(__name__, f'Received command ping {ctx}')
self._client_utils.received_command(ctx.guild.id)
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.base.pong'))
self._logger.trace(__name__, f'Finished ping command')

View File

@ -0,0 +1,58 @@
import asyncio
from cpl_core.configuration import ConfigurationABC
from cpl_core.logging import LoggerABC
from cpl_discord.command import DiscordCommandABC
from cpl_translation import TranslatePipe
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.configuration.server_settings import ServerSettings
from modules.permission.abc.permission_service_abc import PermissionServiceABC
class PurgeCommand(DiscordCommandABC):
def __init__(
self,
logger: LoggerABC,
config: ConfigurationABC,
message_service: MessageServiceABC,
permissions: PermissionServiceABC,
client_utils: ClientUtilsServiceABC,
translate: TranslatePipe
):
DiscordCommandABC.__init__(self)
self._logger = logger
self._config = config
self._message_service = message_service
self._permissions = permissions
self._client_utils = client_utils
self._t = translate
self._logger.trace(__name__, f'Loaded command service: {type(self).__name__}')
@commands.command()
async def purge(self, ctx: Context):
self._logger.debug(__name__, f'Received command purge {ctx}')
self._client_utils.received_command(ctx.guild.id)
server_settings: ServerSettings = self._config.get_configuration(f'ServerSettings_{ctx.guild.id}')
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 purge command')
return
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.base.purge_message'))
await asyncio.sleep(server_settings.message_delete_timer)
try:
await ctx.channel.purge()
except Exception as e:
self._logger.error(__name__, f'Cannot purge channel {ctx.channel.id}', e)
await self._message_service.send_ctx_msg(ctx, self._t.transform('common.bot_has_no_permission_message'))
self._logger.trace(__name__, f'Finished purge command')

View File

@ -0,0 +1,130 @@
from typing import Optional
import discord
from cpl_core.configuration import ConfigurationABC
from cpl_core.logging import LoggerABC
from cpl_discord.command import DiscordCommandABC
from cpl_discord.service import DiscordBotServiceABC
from cpl_query.extension import List
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_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.abc.user_joined_server_repository_abc import UserJoinedServerRepositoryABC
from bot_data.abc.user_repository_abc import UserRepositoryABC
from modules.base.configuration.base_server_settings import BaseServerSettings
from modules.permission.abc.permission_service_abc import PermissionServiceABC
class UserInfoCommandService(DiscordCommandABC):
def __init__(
self,
config: ConfigurationABC,
logger: LoggerABC,
message_service: MessageServiceABC,
bot: DiscordBotServiceABC,
client_utils: ClientUtilsServiceABC,
permissions: PermissionServiceABC,
servers: ServerRepositoryABC,
users: UserRepositoryABC,
user_joined_servers: UserJoinedServerRepositoryABC
):
DiscordCommandABC.__init__(self)
self._config = config
self._logger = logger
self._message_service = message_service
self._bot = bot
self._client_utils = client_utils
self._permissions = permissions
self._servers = servers
self._users = users
self._user_joined_servers = user_joined_servers
self._logger.trace(__name__, f'Loaded command service: {type(self).__name__}')
@commands.command(name='user-info')
async def user_info(self, ctx: Context, member: Optional[discord.Member] = None, *, wait: int = None):
#
# Todo: Use native embeds!!!
#
self._logger.debug(__name__, f'Received command user-info {ctx}:{member},{wait}')
self._client_utils.received_command(ctx.guild.id)
settings: BaseServerSettings = self._config.get_configuration(f'BaseServerSettings_{ctx.guild.id}')
if not self._permissions.is_member_moderator(ctx.author):
await self._message_service.send_ctx_msg(ctx, settings.no_permission_message)
self._logger.trace(__name__, f'Finished purge command')
return
if member is None or not isinstance(member, discord.Member):
member = ctx.author
server = self._servers.find_server_by_discord_id(ctx.guild.id)
user = self._users.find_user_by_discord_id_and_server_id(member.id, server.server_id)
joins = self._user_joined_servers.get_user_joined_servers_by_user_id(user.user_id)
embed_description = EmbedDescription(
settings.user_info_command_message.embed_description.title.format(member.name),
settings.user_info_command_message.embed_description.description.format(member.name),
settings.user_info_command_message.embed_description.url,
settings.user_info_command_message.embed_description.color,
List(EmbedDescriptionField),
settings.user_info_command_message.embed_description.footer
)
for i in range(len(settings.user_info_command_message.embed_description.fields)):
settings_field = settings.user_info_command_message.embed_description.fields[i]
field = EmbedDescriptionField(settings_field.name, settings_field.value, settings_field.inline)
if settings_field.value == '$id':
field.value = settings_field.value.replace('$id', str(member.id))
elif settings_field.value == '$name':
field.value = settings_field.value.replace('$name', member.name)
elif settings_field.value == '$discord_join':
field.value = settings_field.value.replace('$discord_join', str(member.created_at))
elif settings_field.value == '$last_join':
field.value = settings_field.value.replace('$last_join', str(member.joined_at))
elif settings_field.value == '$xp':
field.value = settings_field.value.replace('$xp', str(user.xp))
elif settings_field.value == '$roles':
roles = ''
for role in member.roles:
roles += f'{role.name}\n'
field.value = settings_field.value.replace('$roles', roles)
elif settings_field.value == '$joins':
joins_string = ''
for join in joins:
joins_string += f'{join.joined_on}\n'
field.value = settings_field.value.replace('$joins', joins_string)
elif settings_field.value == '$leavings':
leavings_string = ''
for join in joins:
if join.leaved_on is None:
if leavings_string == '':
leavings_string = '/'
continue
leavings_string += f'{join.leaved_on}\n'
field.value = settings_field.value.replace('$leavings', leavings_string)
elif settings_field.value == '$warnings':
field.value = 'Not Implemented yet'
embed_description.fields.append(field)
await self._message_service.send_ctx_msg(
ctx,
EmbedService.get_embed(embed_description),
wait_before_delete=wait
)
self._logger.trace(__name__, f'Finished user-info command')

View File

@ -0,0 +1,58 @@
import traceback
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl_core.console import Console
class BaseServerSettings(ConfigurationModelABC):
def __init__(self):
ConfigurationModelABC.__init__(self)
self._id: int = 0
self._max_voice_state_hours: int = 0
self._xp_per_message: int = 0
self._xp_per_ontime_hour: int = 0
self._afk_channel_ids: list[int] = []
@property
def id(self) -> int:
return self._id
@property
def max_voice_state_hours(self) -> int:
return self._max_voice_state_hours
@property
def xp_per_message(self) -> int:
return self._xp_per_message
@property
def xp_per_ontime_hour(self) -> int:
return self._xp_per_ontime_hour
@property
def afk_channel_ids(self) -> list[int]:
return self._afk_channel_ids
@property
def afk_command_channel_id(self) -> int:
return self._afk_command_channel_id
@property
def help_command_reference_url(self) -> str:
return self._help_command_reference_url
def from_dict(self, settings: dict):
try:
self._id = int(settings['Id'])
self._max_voice_state_hours = int(settings['MaxVoiceStateHours'])
self._xp_per_message = int(settings['XpPerMessage'])
self._xp_per_ontime_hour = int(settings['XpPerOntimeHour'])
for index in settings['AFKChannelIds']:
self._afk_channel_ids.append(int(index))
self._afk_command_channel_id = settings['AFKCommandChannelId']
self._help_command_reference_url = settings['HelpCommandReferenceUrl']
except Exception as e:
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings')
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')

View File

@ -0,0 +1,32 @@
import traceback
from cpl_core.configuration import ConfigurationModelABC
from cpl_core.console import Console
from cpl_query.extension import List
from modules.base.configuration.base_server_settings import BaseServerSettings
class BaseSettings(ConfigurationModelABC):
def __init__(self):
ConfigurationModelABC.__init__(self)
self._servers: List[BaseServerSettings] = List()
@property
def servers(self) -> List[BaseServerSettings]:
return self._servers
def from_dict(self, settings: dict):
try:
servers = List(BaseServerSettings)
for s in settings:
st = BaseServerSettings()
settings[s]['Id'] = s
st.from_dict(settings[s])
servers.append(st)
self._servers = servers
except Exception as e:
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings')
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')

View File

View File

@ -0,0 +1,97 @@
from datetime import datetime
from typing import Union
import discord
from cpl_core.configuration import ConfigurationABC
from cpl_core.database.context import DatabaseContextABC
from cpl_core.logging import LoggerABC
from cpl_discord.events import OnMemberJoinABC
from cpl_translation import TranslatePipe
from bot_core.abc.message_service_abc import MessageServiceABC
from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC
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_repository_abc import UserRepositoryABC
from bot_data.model.known_user import KnownUser
from bot_data.model.user import User
from bot_data.model.user_joined_server import UserJoinedServer
from modules.base.abc.base_helper_abc import BaseHelperABC
from modules.base.configuration.base_server_settings import BaseServerSettings
from modules.permission.abc.permission_service_abc import PermissionServiceABC
class BaseOnMemberJoinEvent(OnMemberJoinABC):
def __init__(
self,
config: ConfigurationABC,
logger: LoggerABC,
base_helper: BaseHelperABC,
messenger: MessageServiceABC,
permissions: PermissionServiceABC,
db: DatabaseContextABC,
known_users: KnownUserRepositoryABC,
users: UserRepositoryABC,
servers: ServerRepositoryABC,
user_joins: UserJoinedServerRepositoryABC,
translate: TranslatePipe
):
OnMemberJoinABC.__init__(self)
self._config = config
self._logger = logger
self._base_helper = base_helper
self._messenger = messenger
self._permission_service = permissions
self._db = db
self._known_users = known_users
self._users = users
self._servers = servers
self._user_joins = user_joins
self._t = translate
def _check_for_known_user(self, member: Union[discord.User, discord.Member]):
self._logger.debug(__name__, f'Check if user is already known {member}')
try:
user = self._known_users.find_user_by_discord_id(member.id)
if user is not None:
return
self._logger.debug(__name__, f'Add user: {member.id}')
self._known_users.add_user(KnownUser(member.id))
self._db.save_changes()
except Exception as e:
self._logger.error(__name__, f'Cannot get user {member.id}', e)
async def _add_if_not_exists_user_async(self, member: Union[discord.User, discord.Member]):
self._logger.debug(__name__, f'Check if user exists {member}')
settings: BaseServerSettings = self._base_helper.get_config(member.guild.id)
await self._messenger.send_dm_message(self._t.transform('modules.base.welcome_message').format(member.guild.name), member)
for admin in self._permission_service.get_admins(member.guild.id):
await self._messenger.send_dm_message(self._t.transform('modules.base.welcome_message_for_team').format(member.name), admin)
for moderator in self._permission_service.get_moderators(member.guild.id):
await self._messenger.send_dm_message(self._t.transform('modules.base.welcome_message_for_team').format(member.name), moderator)
try:
server = self._servers.get_server_by_discord_id(member.guild.id)
user = self._users.find_user_by_discord_id_and_server_id(member.id, server.server_id)
if user is not None:
self._user_joins.add_user_joined_server(UserJoinedServer(user, datetime.now()))
return
self._logger.debug(__name__, f'Add user: {member.id}')
self._users.add_user(User(member.id, 0, server))
self._db.save_changes()
user = self._users.get_user_by_discord_id_and_server_id(member.id, server.server_id)
self._user_joins.add_user_joined_server(UserJoinedServer(user, datetime.now()))
self._db.save_changes()
except Exception as e:
self._logger.error(__name__, f'Cannot get user {member.id}', e)
async def on_member_join(self, member: discord.Member):
self._logger.debug(__name__, f'Module {type(self)} started')
self._check_for_known_user(member)
await self._add_if_not_exists_user_async(member)

View File

@ -0,0 +1,64 @@
from datetime import datetime
from typing import Union
import discord
from cpl_core.database.context import DatabaseContextABC
from cpl_core.logging import LoggerABC
from cpl_discord.events import OnMemberRemoveABC
from cpl_translation import TranslatePipe
from bot_core.abc.message_service_abc import MessageServiceABC
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_repository_abc import UserRepositoryABC
from modules.base.abc.base_helper_abc import BaseHelperABC
from modules.base.configuration.base_server_settings import BaseServerSettings
class BaseOnMemberRemoveEvent(OnMemberRemoveABC):
def __init__(
self,
logger: LoggerABC,
base_helper: BaseHelperABC,
db: DatabaseContextABC,
messenger: MessageServiceABC,
users: UserRepositoryABC,
servers: ServerRepositoryABC,
user_joins: UserJoinedServerRepositoryABC,
translate: TranslatePipe
):
OnMemberRemoveABC.__init__(self)
self._logger = logger
self._base_helper = base_helper
self._db = db
self._messenger = messenger
self._users = users
self._servers = servers
self._user_joins = user_joins
self._t = translate
async def _remove_user(self, member: Union[discord.User, discord.Member]):
self._logger.debug(__name__, f'Remove user {member}')
settings: BaseServerSettings = self._base_helper.get_config(member.guild.id)
await self._messenger.send_dm_message(self._t.transform('modules.base.goodbye_message'), member)
try:
server = self._servers.get_server_by_discord_id(member.guild.id)
user = self._users.find_user_by_discord_id_and_server_id(member.id, server.server_id)
if user is None:
self._logger.error(__name__, f'Cannot find user {member}')
return
join = self._user_joins.get_active_user_joined_server_by_user_id(user.user_id)
join.leaved_on = datetime.now()
self._user_joins.update_user_joined_server(join)
self._db.save_changes()
except Exception as e:
self._logger.error(__name__, f'Cannot get user {member.id}', e)
async def on_member_remove(self, member: discord.Member):
self._logger.debug(__name__, f'Module {type(self)} started')
await self._remove_user(member)

View File

@ -0,0 +1,79 @@
from typing import Optional
import discord
from cpl_core.database.context import DatabaseContextABC
from cpl_core.logging import LoggerABC
from cpl_discord.events import OnMessageABC
from cpl_discord.service import DiscordBotServiceABC
from bot_data.abc.client_repository_abc import ClientRepositoryABC
from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.abc.user_repository_abc import UserRepositoryABC
from bot_data.model.user import User
from modules.base.abc.base_helper_abc import BaseHelperABC
from modules.base.configuration.base_server_settings import BaseServerSettings
class BaseOnMessageEvent(OnMessageABC):
def __init__(
self,
logger: LoggerABC,
bhs: BaseHelperABC,
db: DatabaseContextABC,
bot: DiscordBotServiceABC,
users: UserRepositoryABC,
clients: ClientRepositoryABC,
servers: ServerRepositoryABC,
):
OnMessageABC.__init__(self)
self._logger = logger
self._base_helper = bhs
self._db = db
self._bot = bot
self._users = users
self._clients = clients
self._servers = servers
def _append_received_message_count(self, g_id: int):
try:
self._clients.append_received_message_count(self._bot.user.id, g_id, 1)
self._db.save_changes()
except Exception as e:
self._logger.error(__name__, f'Cannot edit client {self._bot.user.id}@{g_id}', e)
def _handle_message_for_xp(self, message: discord.Message):
dc_user_id = message.author.id
try:
server = self._servers.get_server_by_discord_id(message.guild.id)
except Exception as e:
self._logger.error(__name__, f'Cannot get server {message.guild.id}', e)
return
user: Optional[User] = None
try:
user = self._users.get_user_by_discord_id_and_server_id(dc_user_id, server.server_id)
except Exception as e:
self._logger.error(__name__, f'Cannot get user {dc_user_id}', e)
return
if user is None:
self._logger.error(__name__, f'User not found {dc_user_id}')
return
settings: BaseServerSettings = self._base_helper.get_config(message.guild.id)
old_xp = user.xp
user.xp += settings.xp_per_message
self._users.update_user(user)
self._db.save_changes()
self._logger.debug(__name__, f'User {user} sent message. xp: from {old_xp} to {user.xp}')
async def on_message(self, message: discord.Message):
self._logger.debug(__name__, f'Module {type(self)} started')
if message is None or message.guild is None:
return
self._append_received_message_count(message.guild.id)
if not message.author.bot:
self._handle_message_for_xp(message)

View File

@ -0,0 +1,118 @@
from datetime import datetime
from typing import Optional
import discord
from cpl_core.configuration import ConfigurationABC
from cpl_core.database.context import DatabaseContextABC
from cpl_core.logging import LoggerABC
from cpl_discord.events import OnVoiceStateUpdateABC
from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC
from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.abc.user_joined_server_repository_abc import UserJoinedServerRepositoryABC
from bot_data.abc.user_joined_voice_channel_abc import UserJoinedVoiceChannelRepositoryABC
from bot_data.abc.user_repository_abc import UserRepositoryABC
from bot_data.model.server import Server
from bot_data.model.user import User
from bot_data.model.user_joined_voice_channel import UserJoinedVoiceChannel
from modules.base.abc.base_helper_abc import BaseHelperABC
from modules.base.configuration.base_server_settings import BaseServerSettings
class BaseOnVoiceStateUpdateEvent(OnVoiceStateUpdateABC):
def __init__(
self,
config: ConfigurationABC,
logger: LoggerABC,
base_helper: BaseHelperABC,
servers: ServerRepositoryABC,
known_users: KnownUserRepositoryABC,
users: UserRepositoryABC,
user_joins: UserJoinedServerRepositoryABC,
user_joins_vc: UserJoinedVoiceChannelRepositoryABC,
db: DatabaseContextABC,
):
OnVoiceStateUpdateABC.__init__(self)
self._config = config
self._logger = logger
self._base_helper = base_helper
self._servers = servers
self._known_users = known_users
self._users = users
self._user_joins = user_joins
self._user_joins_vc = user_joins_vc
self._db = db
self._logger.info(__name__, f'Module {type(self)} loaded')
def _update_voice_state(self, joined: bool, dc_user_id: int, dc_channel_id: int, server: Server):
user: Optional[User] = None
try:
user = self._users.get_user_by_discord_id_and_server_id(dc_user_id, server.server_id)
except Exception as e:
self._logger.error(__name__, f'Cannot get user {dc_user_id}', e)
return
if user is None:
self._logger.error(__name__, f'User not found {dc_user_id}')
return
try:
if joined:
join = UserJoinedVoiceChannel(user, dc_channel_id, datetime.now())
self._user_joins_vc.add_user_joined_voice_channel(join)
self._db.save_changes()
return
settings: BaseServerSettings = self._get_config(server.discord_server_id)
join = self._user_joins_vc.get_active_user_joined_voice_channel_by_user_id(user.user_id)
join.leaved_on = datetime.now()
# ontime as hours
ontime = round((join.leaved_on - join.joined_on).total_seconds() / 3600, 2)
old_xp = user.xp
user.xp += round(ontime * settings.xp_per_ontime_hour)
self._user_joins_vc.update_user_joined_voice_channel(join)
self._users.update_user(user)
self._db.save_changes()
self._logger.debug(__name__, f'User {user} leaved_on {join.leaved_on}. Ontime: {ontime}h | xp: from {old_xp} to {user.xp}')
except Exception as e:
self._logger.error(__name__, f'Ontime validation failed', e)
async def on_voice_state_update(self, member: discord.Member, before: discord.VoiceState, after: discord.VoiceState):
self._logger.debug(__name__, f'Module {type(self)} started')
self._logger.trace(__name__, f'Detected on_voice_state_update {member.id} from {before} to {after}')
settings: BaseServerSettings = self._base_helper.get_config(member.guild.id)
server = self._servers.get_server_by_discord_id(member.guild.id)
try:
# join
if before.channel is None and after.channel is not None and after.channel.id not in settings.afk_channel_ids:
self._logger.trace(__name__, f'User {member.id} joined {after.channel}')
self._update_voice_state(True, member.id, after.channel.id, server)
# leave
elif before.channel is not None and after.channel is None and before.channel.id not in settings.afk_channel_ids:
self._logger.trace(__name__, f'User {member.id} left {before.channel}')
self._update_voice_state(False, member.id, before.channel.id, server)
# channel to channel
elif before.channel is not None and after.channel is not None:
# joined
if before.channel.id in settings.afk_channel_ids and after.channel.id not in settings.afk_channel_ids:
self._logger.trace(__name__, f'User {member.id} joined {after.channel}')
self._update_voice_state(True, member.id, after.channel.id, server)
# left
elif after.channel.id in settings.afk_channel_ids and before.channel.id not in settings.afk_channel_ids:
self._logger.trace(__name__, f'User {member.id} left {before.channel}')
self._update_voice_state(False, member.id, before.channel.id, server)
else:
self._logger.trace(__name__, f'User {member.id} switched to {after.channel}')
except Exception as e:
self._logger.error(__name__, f'Cannot handle voice state for user {member.id}', e)

View File

@ -0,0 +1 @@
# imports

View File

@ -0,0 +1,14 @@
from cpl_core.configuration import ConfigurationABC
from modules.base.abc.base_helper_abc import BaseHelperABC
from modules.base.configuration.base_server_settings import BaseServerSettings
class BaseHelperService(BaseHelperABC):
def __init__(self, config: ConfigurationABC):
BaseHelperABC.__init__(self)
self._config = config
def get_config(self, g_id: int) -> BaseServerSettings:
return self._config.get_configuration(f'BaseServerSettings_{g_id}')

View File

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

View File

@ -0,0 +1,46 @@
{
"ProjectSettings": {
"Name": "boot-log",
"Version": {
"Major": "0",
"Minor": "0",
"Micro": "0"
},
"Author": "",
"AuthorEmail": "",
"Description": "",
"LongDescription": "",
"URL": "",
"CopyrightDate": "",
"CopyrightName": "",
"LicenseName": "",
"LicenseDescription": "",
"Dependencies": [
"cpl-core>=2022.7.0.post2"
],
"DevDependencies": [
"cpl-cli>=2022.7.0.post2"
],
"PythonVersion": ">=3.10.4",
"PythonPath": {
"linux": ""
},
"Classifiers": []
},
"BuildSettings": {
"ProjectType": "library",
"SourcePath": "",
"OutputPath": "../../dist",
"Main": "boot_log.main",
"EntryPoint": "boot-log",
"IncludePackageData": false,
"Included": [],
"Excluded": [
"*/__pycache__",
"*/logs",
"*/tests"
],
"PackageData": {},
"ProjectReferences": []
}
}

View File

@ -0,0 +1,17 @@
from datetime import datetime
from cpl_core.application.application_extension_abc import ApplicationExtensionABC
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceProviderABC
from cpl_core.logging import LoggerABC
class BootLogExtension(ApplicationExtensionABC):
def __init__(self):
pass
async def run(self, config: ConfigurationABC, services: ServiceProviderABC):
logger: LoggerABC = services.get_service(LoggerABC)
logger.debug(__name__, 'BootLog extension started')
config.add_configuration('Bot_StartTime', str(datetime.now()))

View File

@ -0,0 +1,77 @@
from datetime import datetime
from cpl_core.configuration import ConfigurationABC
from cpl_core.logging import LoggerABC
from cpl_discord.events import OnReadyABC
from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslatePipe
from discord import guild
from bot_core.abc.message_service_abc import MessageServiceABC
from bot_core.configuration.server_settings import ServerSettings
from modules.boot_log.configuration.boot_log_server_settings import BootLogServerSettings
class BootLogOnReadyEvent(OnReadyABC):
def __init__(
self,
config: ConfigurationABC,
logger: LoggerABC,
bot: DiscordBotServiceABC,
message_service: MessageServiceABC,
translate: TranslatePipe
):
OnReadyABC.__init__(self)
self._config = config
self._logger = logger
self._bot = bot
self._message_service = message_service
self._t = translate
self._logger.info(__name__, f'Module {type(self)} loaded')
async def on_ready(self):
self._logger.debug(__name__, f'Module {type(self)} started')
try:
start_time = self._config.get_configuration('Bot_StartTime')
init_time = round((datetime.now() - datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S.%f')).total_seconds(), 2)
self._config.add_configuration('InitTime', str(init_time))
self._logger.debug(__name__, f'Bot Init time: {init_time}s')
# print warning if initialisation took too long
if init_time >= 30:
self._logger.warn(
__name__, 'It takes long time to start the bot!')
# print error if initialisation took way too long
elif init_time >= 90:
self._logger.error(
__name__, 'It takes very long time to start the bot!!!')
except Exception as e:
self._logger.error(__name__, 'Init time calculation failed', e)
return
for g in self._bot.guilds:
g: guild = g
self._logger.debug(__name__, f'Server detected: {g.id}')
server_settings: ServerSettings = self._config.get_configuration(f'ServerSettings_{g.id}')
if server_settings is None:
self._logger.error(__name__, f'BootLog settings for server {g.id} not found!')
return
module_settings: BootLogServerSettings = self._config.get_configuration(f'BootLogServerSettings_{g.id}')
if module_settings is None:
self._logger.error(__name__, f'Config {type(self).__name__}_{g.id} not found!')
return
await self._message_service.send_channel_message(
self._bot.get_channel(
module_settings.login_message_channel_id
),
self._t.transform('modules.boot_log.login_message').format(init_time)
)
self._logger.info(__name__, 'Bot is ready')
self._logger.trace(__name__, f'Module {type(self)} stopped')

View File

@ -0,0 +1,29 @@
import traceback
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl_core.console import Console
class BootLogServerSettings(ConfigurationModelABC):
def __init__(self):
ConfigurationModelABC.__init__(self)
self._id: int = 0
self._login_message_channel_id: int = 0
@property
def id(self) -> int:
return self._id
@property
def login_message_channel_id(self) -> int:
return self._login_message_channel_id
def from_dict(self, settings: dict):
try:
self._id = int(settings['Id'])
self._login_message_channel_id = int(settings['LoginMessageChannelId'])
except Exception as e:
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings')
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')

View File

@ -0,0 +1,32 @@
import traceback
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl_core.console import Console
from cpl_query.extension import List
from modules.boot_log.configuration.boot_log_server_settings import BootLogServerSettings
class BootLogSettings(ConfigurationModelABC):
def __init__(self):
ConfigurationModelABC.__init__(self)
self._servers: List[BootLogServerSettings] = List()
@property
def servers(self) -> List[BootLogServerSettings]:
return self._servers
def from_dict(self, settings: dict):
try:
servers = List(BootLogServerSettings)
for s in settings:
st = BootLogServerSettings()
settings[s]['Id'] = s
st.from_dict(settings[s])
servers.append(st)
self._servers = servers
except Exception as e:
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings')
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')

View File

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

View File

@ -0,0 +1,46 @@
{
"ProjectSettings": {
"Name": "database",
"Version": {
"Major": "1",
"Minor": "0",
"Micro": "0.dev1"
},
"Author": "Sven Heidemann",
"AuthorEmail": "sven.heidemann@sh-edraft.de",
"Description": "Keksdose bot - db module",
"LongDescription": "Discord bot for the Keksdose discord Server - database module",
"URL": "https://www.sh-edraft.de",
"CopyrightDate": "2022",
"CopyrightName": "sh-edraft.de",
"LicenseName": "MIT",
"LicenseDescription": "MIT, see LICENSE for more details.",
"Dependencies": [
"cpl-core>=2022.7.0.post2"
],
"DevDependencies": [
"cpl-cli>=2022.7.0.post2"
],
"PythonVersion": ">=3.10.4",
"PythonPath": {
"linux": ""
},
"Classifiers": []
},
"BuildSettings": {
"ProjectType": "library",
"SourcePath": "",
"OutputPath": "../../dist",
"Main": "database.main",
"EntryPoint": "database",
"IncludePackageData": false,
"Included": [],
"Excluded": [
"*/__pycache__",
"*/logs",
"*/tests"
],
"PackageData": {},
"ProjectReferences": []
}
}

View File

@ -0,0 +1,21 @@
from datetime import datetime
from cpl_core.application.application_extension_abc import ApplicationExtensionABC
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceProviderABC
from cpl_core.logging import LoggerABC
from bot_data.service.migration_service import MigrationService
class DatabaseExtension(ApplicationExtensionABC):
def __init__(self):
pass
async def run(self, config: ConfigurationABC, services: ServiceProviderABC):
logger: LoggerABC = services.get_service(LoggerABC)
logger.debug(__name__, 'Database extension started')
config.add_configuration('Database_StartTime', str(datetime.now()))
migrations: MigrationService = services.get_service(MigrationService)
migrations.migrate()

View File

@ -0,0 +1,310 @@
from ctypes import Union
from datetime import datetime, timedelta
import discord
from cpl_core.configuration import ConfigurationABC
from cpl_core.database.context import DatabaseContextABC
from cpl_core.logging import LoggerABC
from cpl_discord.events import OnReadyABC
from cpl_discord.service import DiscordBotServiceABC
from bot_data.abc.client_repository_abc import ClientRepositoryABC
from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC
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.client import Client
from bot_data.model.known_user import KnownUser
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 bot_data.service.user_repository_service import ServerRepositoryABC
class DatabaseOnReadyEvent(OnReadyABC):
def __init__(
self,
config: ConfigurationABC,
logger: LoggerABC,
bot: DiscordBotServiceABC,
db_context: DatabaseContextABC,
server_repo: ServerRepositoryABC,
user_repo: UserRepositoryABC,
client_repo: ClientRepositoryABC,
known_users: KnownUserRepositoryABC,
user_joins: UserJoinedServerRepositoryABC,
user_joins_vc: UserJoinedVoiceChannelRepositoryABC
):
self._config = config
self._logger = logger
self._bot = bot
self._db_context = db_context
self._servers = server_repo
self._users = user_repo
self._clients = client_repo
self._known_users = known_users
self._user_joins = user_joins
self._user_joins_vc = user_joins_vc
OnReadyABC.__init__(self)
self._logger.info(__name__, f'Module {type(self)} loaded')
def _validate_init_time(self):
try:
start_time = self._config.get_configuration('Database_StartTime')
init_time = round((datetime.now() - datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S.%f')).total_seconds(), 2)
self._config.add_configuration('Database_InitTime', str(init_time))
self._logger.debug(__name__, f'Database Init time: {init_time}s')
# print warning if initialisation took too long
if init_time >= 30:
self._logger.warn(
__name__, 'It takes long time to start the bot!')
# print error if initialisation took way too long
elif init_time >= 90:
self._logger.error(
__name__, 'It takes very long time to start the bot!!!')
except Exception as e: #
self._logger.error(__name__, 'Database init time calculation failed', e)
return
def _check_known_users(self):
self._logger.debug(__name__, f'Start checking KnownUsers table, {len(self._bot.users)}')
for u in self._bot.users:
u: discord.User = u
try:
if u.bot:
self._logger.trace(__name__, f'User {u.id} is ignored, because its a bot')
continue
user = self._known_users.find_user_by_discord_id(u.id)
if user is not None:
continue
self._logger.warn(__name__, f'Unknown user: {u.id}')
self._logger.debug(__name__, f'Add user: {u.id}')
self._known_users.add_user(KnownUser(u.id))
self._db_context.save_changes()
user = self._known_users.find_user_by_discord_id(u.id)
if user is None:
self._logger.fatal(__name__, f'Cannot add user: {u.id}')
self._logger.debug(__name__, f'Added user: {u.id}')
except Exception as e:
self._logger.error(__name__, f'Cannot get user', e)
def _check_servers(self):
self._logger.debug(__name__, f'Start checking Servers table')
for g in self._bot.guilds:
g: discord.Guild = g
try:
server = self._servers.find_server_by_discord_id(g.id)
if server is not None:
continue
self._logger.warn(__name__, f'Server not found in database: {g.id}')
self._logger.debug(__name__, f'Add server: {g.id}')
self._servers.add_server(Server(g.id))
self._db_context.save_changes()
server = self._servers.find_server_by_discord_id(g.id)
if server is None:
self._logger.fatal(__name__, f'Cannot add server: {g.id}')
self._logger.debug(__name__, f'Added server: {g.id}')
except Exception as e:
self._logger.error(__name__, f'Cannot get server', e)
results = self._servers.get_servers()
if results is None or len(results) == 0:
self._logger.error(__name__, f'Table Servers is empty!')
def _check_clients(self):
self._logger.debug(__name__, f'Start checking Clients table')
for g in self._bot.guilds:
g: discord.Guild = g
try:
server: Server = self._servers.find_server_by_discord_id(g.id)
if server is None:
self._logger.fatal(__name__, f'Server not found in database: {g.id}')
client = self._clients.find_client_by_server_id(server.server_id)
if client is not None:
continue
self._logger.warn(__name__, f'Client for server {g.id} not found in database: {self._bot.user.id}')
self._logger.debug(__name__, f'Add client: {self._bot.user.id}')
self._clients.add_client(Client(self._bot.user.id, 0, 0, 0, 0, 0, server))
self._db_context.save_changes()
client = self._clients.find_client_by_server_id(server.server_id)
if client is None:
self._logger.fatal(__name__, f'Cannot add client {self._bot.user.id} for server {g.id}')
self._logger.debug(__name__, f'Added client: {g.id}')
except Exception as e:
self._logger.error(__name__, f'Cannot get client', e)
results = self._servers.get_servers()
if results is None or len(results) == 0:
self._logger.error(__name__, f'Table Servers is empty!')
def _check_users(self):
self._logger.debug(__name__, f'Start checking Users table')
for g in self._bot.guilds:
g: discord.Guild = g
try:
server = self._servers.find_server_by_discord_id(g.id)
if server is None:
self._logger.fatal(__name__, f'Server not found in database: {g.id}')
for u in g.members:
u: Union[discord.Member, discord.User] = u
if u.bot:
self._logger.trace(__name__, f'User {u.id} is ignored, because its a bot')
continue
user = self._users.find_user_by_discord_id_and_server_id(u.id, server.server_id)
if user is not None:
continue
self._logger.warn(__name__, f'User not found in database: {u.id}')
self._logger.debug(__name__, f'Add user: {u.id}')
self._users.add_user(User(u.id, 0, server))
self._db_context.save_changes()
self._logger.debug(__name__, f'Added User: {u.id}')
except Exception as e:
self._logger.error(__name__, f'Cannot get User', e)
results = self._users.get_users()
if results is None or len(results) == 0:
self._logger.error(__name__, f'Table Users is empty!')
def _check_user_joins(self):
self._logger.debug(__name__, f'Start checking UserJoinedServers table')
for guild in self._bot.guilds:
guild: discord.Guild = guild
server = self._servers.find_server_by_discord_id(guild.id)
if server is None:
self._logger.fatal(__name__, f'Server not found in database: {guild.id}')
try:
for u in guild.members:
u: discord.User = u
if u.bot:
self._logger.trace(__name__, f'User {u.id} is ignored, because its a bot')
continue
user = self._users.find_user_by_discord_id_and_server_id(u.id, server.server_id)
if user is None:
self._logger.fatal(__name__, f'User not found in database: {u.id}')
join = self._user_joins.find_active_user_joined_server_by_user_id(user.user_id)
if join is not None:
continue
m: discord.Member = u
self._logger.warn(__name__, f'Active UserJoinedServer not found in database: {guild.id}:{u.id}@{m.joined_at}')
self._logger.debug(__name__, f'Add UserJoinedServer: {guild.id}:{u.id}@{m.joined_at}')
self._user_joins.add_user_joined_server(UserJoinedServer(user, m.joined_at, None))
self._db_context.save_changes()
self._logger.debug(__name__, f'Added UserJoinedServer: {u.id}')
except Exception as e:
self._logger.error(__name__, f'Cannot get UserJoinedServer', e)
results = self._users.get_users()
if results is None or len(results) == 0:
self._logger.error(__name__, f'Table Users is empty!')
joins = self._user_joins.get_user_joined_servers()
for join in joins:
join: UserJoinedServer = join
if join.user.server.discord_server_id != guild.id:
continue
if join.leaved_on is not None:
continue
dc_user = guild.get_member(join.user.discord_id)
if dc_user is None:
self._logger.warn(__name__, f'User {join.user.discord_id} already left the server.')
join.leaved_on = datetime.now()
self._user_joins.update_user_joined_server(join)
self._db_context.save_changes()
def _check_user_joins_vc(self):
self._logger.debug(__name__, f'Start checking UserJoinedVoiceChannel table')
for guild in self._bot.guilds:
guild: discord.Guild = guild
server = self._servers.find_server_by_discord_id(guild.id)
if server is None:
self._logger.fatal(__name__, f'Server not found in database: {guild.id}')
try:
for member in guild.members:
if member.bot:
self._logger.trace(__name__, f'User {member.id} is ignored, because its a bot')
continue
user = self._users.find_user_by_discord_id_and_server_id(member.id, server.server_id)
if user is None:
self._logger.fatal(__name__, f'User not found in database: {member.id}')
joins = self._user_joins_vc.find_active_user_joined_voice_channels_by_user_id(user.user_id)
if joins is None or len(joins) == 0:
continue
for join in joins:
self._logger.warn(__name__, f'Active UserJoinedVoiceChannel found in database: {guild.id}:{member.id}@{join.joined_on}')
join.leaved_on = datetime.now()
settings: BaseSettings = self._config.get_configuration(f'BaseServerSettings_{guild.id}')
if ((join.leaved_on - join.joined_on).total_seconds() / 60 / 60) > settings.max_voice_state_hours:
join.leaved_on = join.joined_on + timedelta(hours=settings.max_voice_state_hours)
self._user_joins_vc.update_user_joined_voice_channel(join)
# todo: maybe add XP
self._db_context.save_changes()
for member in guild.members:
if member.bot:
self._logger.trace(__name__, f'User {member.id} is ignored, because its a bot')
continue
if member.voice is None:
continue
user = self._users.find_user_by_discord_id_and_server_id(member.id, server.server_id)
if user is None:
self._logger.fatal(__name__, f'User not found in database: {member.id}')
join = UserJoinedVoiceChannel(user, member.voice.channel.id, datetime.now())
self._user_joins_vc.add_user_joined_voice_channel(join)
self._db_context.save_changes()
self._logger.warn(__name__, f'VS {member.voice}')
except Exception as e:
self._logger.error(__name__, f'Cannot get UserJoinedVoiceChannel', e)
async def on_ready(self):
self._logger.debug(__name__, f'Module {type(self)} started')
self._check_known_users()
self._check_servers()
self._check_clients()
self._check_users()
self._check_user_joins()
self._check_user_joins_vc()
self._validate_init_time()
self._logger.trace(__name__, f'Module {type(self)} stopped')

View File

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

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
"""
gismo sh-edraft Gismo
~~~~~~~~~~~~~~~~~~~
sh-edraft Dicord bot Gismo
:copyright: (c) 2021 - 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'modules.permission.abc'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2021 - 2022 sh-edraft.de'
__version__ = '0.4.2'
from collections import namedtuple
# imports
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='4', micro='2')

View File

@ -0,0 +1,39 @@
from abc import ABC, abstractmethod
import discord
class PermissionServiceABC(ABC):
@abstractmethod
def __init__(self): pass
@abstractmethod
def on_ready(self): pass
@abstractmethod
def on_member_update(self, before: discord.Member, after: discord.Member): pass
@abstractmethod
def get_admin_role_ids(self, g_id: int) -> list[int]: pass
@abstractmethod
def get_admin_roles(self, g_id: int) -> list[discord.Role]: pass
@abstractmethod
def get_admins(self, g_id: int) -> list[discord.Member]: pass
@abstractmethod
def get_moderator_role_ids(self, g_id: int) -> list[int]: pass
@abstractmethod
def get_moderator_roles(self, g_id: int) -> list[discord.Role]: pass
@abstractmethod
def get_moderators(self, g_id: int) -> list[discord.Member]: pass
@abstractmethod
def is_member_admin(self, member: discord.Member) -> bool: pass
@abstractmethod
def is_member_moderator(self, member: discord.Member) -> bool: pass

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
"""
gismo sh-edraft Gismo
~~~~~~~~~~~~~~~~~~~
sh-edraft Dicord bot Gismo
:copyright: (c) 2021 - 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'modules.permission.configuration'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2021 - 2022 sh-edraft.de'
__version__ = '0.4.2'
from collections import namedtuple
# imports
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='4', micro='2')

View File

@ -0,0 +1,38 @@
import traceback
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl_core.console import Console
class PermissionServerSettings(ConfigurationModelABC):
def __init__(self):
ConfigurationModelABC.__init__(self)
self._id: int = 0
self._admin_roles: list[int] = []
self._moderator_roles: list[int] = []
@property
def id(self) -> int:
return self._id
@property
def admin_roles(self) -> list[int]:
return self._admin_roles
@property
def moderator_roles(self) -> list[int]:
return self._moderator_roles
def from_dict(self, settings: dict):
try:
self._id = int(settings['Id'])
for index in settings['AdminRoleIds']:
self._admin_roles.append(int(index))
for index in settings['ModeratorRoleIds']:
self._moderator_roles.append(int(index))
except Exception as e:
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {self.__name__} settings')
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')

View File

@ -0,0 +1,32 @@
import traceback
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl_core.console import Console
from cpl_query.extension import List
from modules.permission.configuration.permission_server_settings import PermissionServerSettings
class PermissionSettings(ConfigurationModelABC):
def __init__(self):
ConfigurationModelABC.__init__(self)
self._servers: List[PermissionServerSettings] = List()
@property
def servers(self) -> List[PermissionServerSettings]:
return self._servers
def from_dict(self, settings: dict):
try:
servers = List(PermissionServerSettings)
for s in settings:
st = PermissionServerSettings()
settings[s]['Id'] = s
st.from_dict(settings[s])
servers.append(st)
self._servers = servers
except Exception as e:
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings')
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')

View File

@ -0,0 +1,19 @@
import discord
from cpl_core.logging import LoggerABC
from cpl_discord.events import OnMemberUpdateABC
from modules.permission.abc.permission_service_abc import PermissionServiceABC
class PermissionOnMemberUpdateEvent(OnMemberUpdateABC):
def __init__(self, logger: LoggerABC, permission_service: PermissionServiceABC):
OnMemberUpdateABC.__init__(self)
self._logger = logger
self._permission_service = permission_service
async def on_member_update(self, before: discord.Member, after: discord.Member):
self._logger.debug(__name__, f'Module {type(self)} started')
if before.roles != after.roles:
self._permission_service.on_member_update(before, after)

View File

@ -0,0 +1,16 @@
from cpl_core.logging import LoggerABC
from cpl_discord.events import OnReadyABC
from modules.permission.abc.permission_service_abc import PermissionServiceABC
class PermissionOnReadyEvent(OnReadyABC):
def __init__(self, logger: LoggerABC, permission_service: PermissionServiceABC):
OnReadyABC.__init__(self)
self._logger = logger
self._permission_service = permission_service
async def on_ready(self):
self._logger.debug(__name__, f'Module {type(self)} started')
self._permission_service.on_ready()

View File

@ -0,0 +1,46 @@
{
"ProjectSettings": {
"Name": "permission",
"Version": {
"Major": "0",
"Minor": "0",
"Micro": "0"
},
"Author": "",
"AuthorEmail": "",
"Description": "",
"LongDescription": "",
"URL": "",
"CopyrightDate": "",
"CopyrightName": "",
"LicenseName": "",
"LicenseDescription": "",
"Dependencies": [
"cpl-core>=2022.7.0.post2"
],
"DevDependencies": [
"cpl-cli>=2022.7.0.post2"
],
"PythonVersion": ">=3.10.4",
"PythonPath": {
"linux": ""
},
"Classifiers": []
},
"BuildSettings": {
"ProjectType": "library",
"SourcePath": "",
"OutputPath": "../../dist",
"Main": "permission.main",
"EntryPoint": "permission",
"IncludePackageData": false,
"Included": [],
"Excluded": [
"*/__pycache__",
"*/logs",
"*/tests"
],
"PackageData": {},
"ProjectReferences": []
}
}

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
"""
gismo sh-edraft Gismo
~~~~~~~~~~~~~~~~~~~
sh-edraft Dicord bot Gismo
:copyright: (c) 2021 - 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'modules.permission.service'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2021 - 2022 sh-edraft.de'
__version__ = '0.4.2'
from collections import namedtuple
# imports
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='4', micro='2')

View File

@ -0,0 +1,126 @@
import discord
from cpl_core.logging import LoggerABC
from cpl_core.configuration import ConfigurationABC
from cpl_discord.service import DiscordBotServiceABC
from modules.permission.abc.permission_service_abc import PermissionServiceABC
from modules.permission.configuration.permission_server_settings import PermissionServerSettings
class PermissionService(PermissionServiceABC):
def __init__(self, logger: LoggerABC, bot: DiscordBotServiceABC, config: ConfigurationABC):
PermissionServiceABC.__init__(self)
self._logger = logger
self._bot = bot
self._config = config
self._admin_role_ids: dict[int, list[int]] = {}
self._admin_roles: dict[int, list[discord.Role]] = {}
self._admins: dict[int, list[discord.Member]] = {}
self._moderator_role_ids: dict[int, list[int]] = {}
self._moderator_roles: dict[int, list[discord.Role]] = {}
self._moderators: dict[int, list[discord.Member]] = {}
def on_ready(self):
for guild in self._bot.guilds:
guild: discord.Guild = guild
self._logger.debug(__name__, f'Validate permission settings')
settings: PermissionServerSettings = self._config.get_configuration(f'PermissionServerSettings_{guild.id}')
if settings is None:
self._logger.error(__name__, 'Permission settings not found')
return
self._admin_role_ids[guild.id] = settings.admin_roles
self._moderator_role_ids[guild.id] = settings.moderator_roles
admin_roles = []
admins = []
mod_roles = []
mods = []
for role in guild.roles:
role: discord.Role = role
if role.id in self._admin_role_ids[guild.id]:
admin_roles.append(role)
self._logger.trace(__name__, f'Added admin role {role}')
for member in role.members:
admins.append(member)
self._logger.trace(__name__, f'Added admin {member}')
if role.id in self._moderator_role_ids[guild.id]:
mod_roles.append(role)
self._logger.trace(__name__, f'Added moderator role {role}')
for member in role.members:
mods.append(member)
self._logger.trace(__name__, f'Added moderator {member}')
self._admin_roles[guild.id] = admin_roles
self._admins[guild.id] = admins
self._moderator_roles[guild.id] = mod_roles
self._moderators[guild.id] = mods
self._logger.error(__name__, f'USERS {self._admins} {self._moderators}')
def on_member_update(self, before: discord.Member, after: discord.Member):
g_id = after.guild.id
for admin_role in self._admin_roles[g_id]:
if admin_role in before.roles and admin_role not in after.roles:
self._admins[g_id].remove(after)
self._logger.trace(__name__, f'Removed {after.id} from admins')
elif admin_role in after.roles and admin_role not in before.roles:
self._admins[g_id].append(after)
self._logger.trace(__name__, f'Added {after.id} to admins')
for moderator_role in self._moderator_roles[g_id]:
if moderator_role in before.roles and moderator_role not in after.roles:
self._moderators[g_id].remove(after)
self._logger.trace(__name__, f'Removed {after.id} from moderators')
elif moderator_role in after.roles and moderator_role not in before.roles:
self._moderators[g_id].append(after)
self._logger.trace(__name__, f'Added {after.id} to moderators')
def get_admin_role_ids(self, g_id: int) -> list[int]:
return self._admin_role_ids[g_id]
def get_admin_roles(self, g_id: int) -> list[discord.Role]:
return self._admin_roles[g_id]
def get_admins(self, g_id: int) -> list[discord.Member]:
return self._admins[g_id]
def get_moderator_role_ids(self, g_id: int) -> list[int]:
return self._moderator_role_ids[g_id]
def get_moderator_roles(self, g_id: int) -> list[discord.Role]:
return self._moderator_roles[g_id]
def get_moderators(self, g_id: int) -> list[discord.Member]:
return self._moderators[g_id]
def is_member_admin(self, member: discord.Member) -> bool:
role_match = False
for role in member.roles:
if role in self._admin_roles:
role_match = True
return member in self._admins[member.guild.id] or role_match
def is_member_moderator(self, member: discord.Member) -> bool:
role_match = False
for role in member.roles:
if role in self._moderator_roles:
role_match = True
return member in self._moderators[member.guild.id] or role_match or self.is_member_admin(member)