Moved folders #405

This commit is contained in:
2023-10-13 17:10:00 +02:00
parent eb32bec43c
commit f435d3dd48
807 changed files with 3801 additions and 1297 deletions

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
bot sh-edraft.de Discord bot
~~~~~~~~~~~~~~~~~~~
Discord bot for customers of sh-edraft.de
:copyright: (c) 2022 - 2023 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = "modules.technician"
__author__ = "Sven Heidemann"
__license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.0"
from collections import namedtuple
# imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -0,0 +1,46 @@
from cpl_core.configuration import ConfigurationABC
from cpl_core.database.context import DatabaseContextABC
from cpl_discord.service import DiscordBotServiceABC
from bot_core.logging.database_logger import DatabaseLogger
from bot_data.abc.api_key_repository_abc import ApiKeyRepositoryABC
from bot_data.abc.data_seeder_abc import DataSeederABC
from bot_data.abc.user_repository_abc import UserRepositoryABC
from bot_data.model.api_key import ApiKey
class ApiKeySeeder(DataSeederABC):
def __init__(
self,
logger: DatabaseLogger,
config: ConfigurationABC,
bot: DiscordBotServiceABC,
db: DatabaseContextABC,
users: UserRepositoryABC,
api_keys: ApiKeyRepositoryABC,
):
DataSeederABC.__init__(self)
self._logger = logger
self._config = config
self._bot = bot
self._db = db
self._users = users
self._api_keys = api_keys
async def seed(self):
self._logger.debug(__name__, f"API-Key seeder started")
if self._api_keys.get_api_keys().count() > 0:
self._logger.debug(__name__, f"Skip API-Key seeder")
return
try:
frontend_key = ApiKey(
"frontend", "87f529fd-a32e-40b3-a1d1-7a1583cf3ff5", None
)
self._api_keys.add_api_key(frontend_key)
self._db.save_changes()
self._logger.info(__name__, f"Created frontend API-Key")
except Exception as e:
self._logger.fatal(__name__, "Cannot create frontend API-Key", e)

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
bot sh-edraft.de Discord bot
~~~~~~~~~~~~~~~~~~~
Discord bot for customers of sh-edraft.de
:copyright: (c) 2022 - 2023 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = "modules.technician.command"
__author__ = "Sven Heidemann"
__license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.0"
from collections import namedtuple
# imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -0,0 +1,157 @@
import hashlib
import uuid
from typing import List as TList
import discord
from cpl_core.database.context import DatabaseContextABC
from cpl_discord.command import DiscordCommandABC
from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslatePipe
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
from bot_api.configuration.authentication_settings import AuthenticationSettings
from bot_core.abc.client_utils_abc import ClientUtilsABC
from bot_core.abc.message_service_abc import MessageServiceABC
from bot_core.helper.command_checks import CommandChecks
from bot_core.logging.command_logger import CommandLogger
from bot_data.abc.api_key_repository_abc import ApiKeyRepositoryABC
from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.abc.user_repository_abc import UserRepositoryABC
from bot_data.model.api_key import ApiKey
from modules.permission.abc.permission_service_abc import PermissionServiceABC
class ApiKeyGroup(DiscordCommandABC):
def __init__(
self,
logger: CommandLogger,
auth_settings: AuthenticationSettings,
message_service: MessageServiceABC,
bot: DiscordBotServiceABC,
client_utils: ClientUtilsABC,
permission_service: PermissionServiceABC,
translate: TranslatePipe,
db: DatabaseContextABC,
servers: ServerRepositoryABC,
users: UserRepositoryABC,
api_keys: ApiKeyRepositoryABC,
):
DiscordCommandABC.__init__(self)
self._logger = logger
self._auth_settings = auth_settings
self._message_service = message_service
self._bot = bot
self._client_utils = client_utils
self._permissions = permission_service
self._t = translate
self._db = db
self._servers = servers
self._users = users
self._api_keys = api_keys
def _get_api_key_str(self, api_key: ApiKey) -> str:
return hashlib.sha256(
f"{api_key.identifier}:{api_key.key}+{self._auth_settings.secret_key}".encode(
"utf-8"
)
).hexdigest()
@commands.hybrid_group(name="api-key")
@commands.guild_only()
async def api_key(self, ctx: Context):
pass
@api_key.command()
@commands.guild_only()
@CommandChecks.check_is_ready()
@CommandChecks.check_is_member_technician()
async def get(self, ctx: Context, key: str, wait: int = None):
self._logger.debug(
__name__, f"Received command api-key get {ctx}: {key},{wait}"
)
api_key = self._api_keys.get_api_key_by_key(key)
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.technician.api_key.get").format(
api_key.identifier, self._get_api_key_str(api_key)
),
)
self._logger.trace(__name__, f"Finished command api-key get")
@get.autocomplete("key")
async def get_autocomplete(
self, interaction: discord.Interaction, current: str
) -> TList[app_commands.Choice[str]]:
keys = self._api_keys.get_api_keys()
return [
app_commands.Choice(name=f"{key.identifier}: {key.key}", value=key.key)
for key in self._client_utils.get_auto_complete_list(
keys, current, lambda x: x.key
)
]
@api_key.command()
@commands.guild_only()
@CommandChecks.check_is_ready()
@CommandChecks.check_is_member_moderator()
async def add(self, ctx: Context, identifier: str):
self._logger.debug(
__name__, f"Received command api-key add {ctx}: {identifier}"
)
server = self._servers.get_server_by_discord_id(ctx.guild.id)
user = self._users.get_user_by_discord_id_and_server_id(
ctx.author.id, server.id
)
api_key = ApiKey(identifier, str(uuid.uuid4()), user)
self._api_keys.add_api_key(api_key)
self._db.save_changes()
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.technician.api_key.add.success").format(
identifier, self._get_api_key_str(api_key)
),
)
self._logger.trace(__name__, f"Finished command api-key add")
@api_key.command()
@commands.guild_only()
@CommandChecks.check_is_ready()
@CommandChecks.check_is_member_moderator()
async def remove(self, ctx: Context, key: str):
self._logger.debug(__name__, f"Received command api-key remove {ctx}: {key}")
keys = self._api_keys.get_api_keys().where(lambda x: x.key == key)
if keys.count() < 1:
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.technician.api_key.remove.not_found"),
)
api_key = keys.single()
self._api_keys.delete_api_key(api_key)
self._db.save_changes()
await self._message_service.send_ctx_msg(
ctx, self._t.transform("modules.technician.api_key.remove.success")
)
self._logger.trace(__name__, f"Finished command api-key remove")
@remove.autocomplete("key")
async def set_autocomplete(
self, interaction: discord.Interaction, current: str
) -> TList[app_commands.Choice[str]]:
keys = self._api_keys.get_api_keys()
return [
app_commands.Choice(name=f"{key.identifier}: {key.key}", value=key.key)
for key in self._client_utils.get_auto_complete_list(
keys, current, lambda x: x.key
)
]

View File

@@ -0,0 +1,122 @@
import os
from string import Template
from zipfile import ZipFile
import discord
from cpl_core.dependency_injection import ServiceProviderABC
from cpl_core.environment import ApplicationEnvironmentABC
from cpl_core.logging import LoggingSettings
from cpl_core.time import TimeFormatSettings
from cpl_discord.command import DiscordCommandABC
from cpl_query.extension import List
from cpl_translation import TranslatePipe
from discord.ext import commands
from discord.ext.commands import Context
from bot_core.abc.client_utils_abc import ClientUtilsABC
from bot_core.abc.custom_file_logger_abc import CustomFileLoggerABC
from bot_core.abc.message_service_abc import MessageServiceABC
from bot_core.helper.command_checks import CommandChecks
from bot_core.logging.command_logger import CommandLogger
from modules.permission.abc.permission_service_abc import PermissionServiceABC
class LogCommand(DiscordCommandABC):
def __init__(
self,
logger: CommandLogger,
logging_settings: LoggingSettings,
services: ServiceProviderABC,
message_service: MessageServiceABC,
client_utils: ClientUtilsABC,
translate: TranslatePipe,
permissions: PermissionServiceABC,
time_format: TimeFormatSettings,
env: ApplicationEnvironmentABC,
):
DiscordCommandABC.__init__(self)
self._logger = logger
self._logging_settings = logging_settings
self._services = services
self._message_service = message_service
self._client_utils = client_utils
self._t = translate
self._permissions = permissions
self._env = env
self._log_settings: LoggingSettings = logging_settings
self._time_format_settings: TimeFormatSettings = time_format
self._logger.trace(__name__, f"Loaded command service: {type(self).__name__}")
def _reduce_path(self, p: str) -> str:
if len(p.split("/")) == 1 or p == "":
return p
return self._reduce_path(os.path.dirname(p))
@commands.hybrid_command()
@commands.guild_only()
@CommandChecks.check_is_ready()
@CommandChecks.check_is_member_technician()
async def log(self, ctx: Context):
self._logger.debug(__name__, f"Received command log {ctx}")
possible_log_paths = List(str)
possible_log_paths.append(self._reduce_path(self._logging_settings.path))
file_extensions = List(str)
if "." in self._logging_settings.filename:
split_filename = self._logging_settings.filename.split(".")
file_extensions.append(f".{split_filename[len(split_filename) - 1]}")
for subclass in CustomFileLoggerABC.__subclasses__():
logger: CustomFileLoggerABC = self._services.get_service(subclass)
if logger is None:
continue
path = self._reduce_path(logger.settings.path)
if "." in logger.settings.filename:
split_filename = logger.settings.filename.split(".")
file_extension = f".{split_filename[len(split_filename) - 1]}"
if file_extension not in file_extensions:
file_extensions.append(file_extension)
if path in possible_log_paths:
continue
possible_log_paths.append(path)
files_str = "\n\t".join(possible_log_paths.to_list())
self._logger.debug(__name__, f"Possible log files: \n\t{files_str}")
files = List(str)
for possible_path in possible_log_paths:
for r, d, f in os.walk(possible_path):
for file in f:
if "." not in file:
continue
split_filename = file.split(".")
if (
f".{split_filename[len(split_filename) - 1]}"
not in file_extensions
):
continue
files.append(os.path.join(r, file))
files_str = "\n\t".join(files.to_list())
self._logger.debug(__name__, f"Log files: \n\t{files_str}")
zip_file = ZipFile("logs.zip", "w")
files.for_each(lambda x: zip_file.write(x))
zip_file.close()
await self._message_service.send_interaction_msg(
ctx.interaction,
self._t.transform("modules.technician.log_message"),
file=discord.File(zip_file.filename, "logs.zip"),
)
os.remove(zip_file.filename)
self._logger.trace(__name__, f"Finished log command")

View File

@@ -0,0 +1,62 @@
import asyncio
from cpl_core.configuration import ConfigurationABC
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_abc import ClientUtilsABC
from bot_core.abc.message_service_abc import MessageServiceABC
from bot_core.helper.command_checks import CommandChecks
from bot_core.logging.command_logger import CommandLogger
from bot_core.service.data_integrity_service import DataIntegrityService
from bot_data.model.technician_config import TechnicianConfig
from modules.permission.abc.permission_service_abc import PermissionServiceABC
class RestartCommand(DiscordCommandABC):
def __init__(
self,
logger: CommandLogger,
config: ConfigurationABC,
message_service: MessageServiceABC,
bot: DiscordBotServiceABC,
client_utils: ClientUtilsABC,
translate: TranslatePipe,
permissions: PermissionServiceABC,
settings: TechnicianConfig,
data_integrity: DataIntegrityService,
):
DiscordCommandABC.__init__(self)
self._logger = logger
self._config = config
self._message_service = message_service
self._bot = bot
self._client_utils = client_utils
self._t = translate
self._permissions = permissions
self._settings = settings
self._data_integrity = data_integrity
self._logger.trace(__name__, f"Loaded command service: {type(self).__name__}")
@commands.hybrid_command()
@commands.guild_only()
@CommandChecks.check_is_ready()
@CommandChecks.check_is_member_technician()
async def restart(self, ctx: Context):
self._logger.debug(__name__, f"Received command restart {ctx}")
self._config.add_configuration("IS_RESTART", "true")
await self._client_utils.presence_game("common.presence.restart")
await self._message_service.send_ctx_msg(
ctx, self._t.transform("modules.technician.restart_message")
)
await asyncio.sleep(self._settings.wait_for_restart)
await self._data_integrity.check_data_integrity(is_for_shutdown=True)
await self._bot.stop_async()
self._logger.trace(__name__, f"Finished restart command")

View File

@@ -0,0 +1,61 @@
import asyncio
from cpl_core.configuration import ConfigurationABC
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_abc import ClientUtilsABC
from bot_core.abc.message_service_abc import MessageServiceABC
from bot_core.helper.command_checks import CommandChecks
from bot_core.logging.command_logger import CommandLogger
from bot_core.service.data_integrity_service import DataIntegrityService
from bot_data.model.technician_config import TechnicianConfig
from modules.permission.abc.permission_service_abc import PermissionServiceABC
class ShutdownCommand(DiscordCommandABC):
def __init__(
self,
logger: CommandLogger,
config: ConfigurationABC,
message_service: MessageServiceABC,
bot: DiscordBotServiceABC,
client_utils: ClientUtilsABC,
translate: TranslatePipe,
permissions: PermissionServiceABC,
settings: TechnicianConfig,
data_integrity: DataIntegrityService,
):
DiscordCommandABC.__init__(self)
self._logger = logger
self._config = config
self._message_service = message_service
self._bot = bot
self._client_utils = client_utils
self._t = translate
self._permissions = permissions
self._settings = settings
self._data_integrity = data_integrity
self._logger.trace(__name__, f"Loaded command service: {type(self).__name__}")
@commands.hybrid_command()
@commands.guild_only()
@CommandChecks.check_is_ready()
@CommandChecks.check_is_member_technician()
async def shutdown(self, ctx: Context):
self._logger.debug(__name__, f"Received command shutdown {ctx}")
await self._client_utils.presence_game("common.presence.shutdown")
await self._message_service.send_ctx_msg(
ctx, self._t.transform("modules.technician.shutdown_message")
)
await asyncio.sleep(self._settings.wait_for_shutdown)
await self._data_integrity.check_data_integrity(is_for_shutdown=True)
await self._bot.stop_async()
self._logger.trace(__name__, f"Finished shutdown command")

View File

@@ -0,0 +1,187 @@
import discord
from cpl_core.configuration import ConfigurationABC
from cpl_core.database.context import DatabaseContextABC
from cpl_discord.command import DiscordCommandABC
from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslatePipe
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
from bot_core.abc.client_utils_abc import ClientUtilsABC
from bot_core.abc.message_service_abc import MessageServiceABC
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
from bot_core.helper.command_checks import CommandChecks
from bot_core.logging.command_logger import CommandLogger
from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.abc.user_repository_abc import UserRepositoryABC
from bot_data.model.server_config import ServerConfig
from bot_data.model.technician_config import TechnicianConfig
from bot_data.model.user import User
from modules.level.service.level_service import LevelService
from modules.permission.abc.permission_service_abc import PermissionServiceABC
class SyncXpGroup(DiscordCommandABC):
def __init__(
self,
logger: CommandLogger,
config: ConfigurationABC,
message_service: MessageServiceABC,
bot: DiscordBotServiceABC,
client_utils: ClientUtilsABC,
translate: TranslatePipe,
servers: ServerRepositoryABC,
users: UserRepositoryABC,
permissions: PermissionServiceABC,
settings: TechnicianConfig,
db: DatabaseContextABC,
level_service: LevelService,
):
DiscordCommandABC.__init__(self)
self._logger = logger
self._config = config
self._message_service = message_service
self._bot = bot
self._client_utils = client_utils
self._t = translate
self._servers = servers
self._users = users
self._permissions = permissions
self._settings = settings
self._db = db
self._level_service = level_service
self._logger.trace(__name__, f"Loaded command service: {type(self).__name__}")
@commands.hybrid_group(name="sync-xp")
@commands.guild_only()
async def sync_xp(self, ctx: Context):
pass
@sync_xp.command(name="all-members")
@commands.guild_only()
@CommandChecks.check_is_ready()
@CommandChecks.check_is_member_technician()
async def all_members(self, ctx: Context, server_id: int):
self._logger.debug(__name__, f"Received command sync xp {ctx}")
if ctx.guild is None:
return
settings: ServerConfig = self._config.get_configuration(
f"ServerConfig_{ctx.guild.id}"
)
if not FeatureFlagsSettings.get_flag_from_dict(
settings.feature_flags, FeatureFlagsEnum.sync_xp
):
await self._message_service.send_ctx_msg(
ctx, self._t.transform("common.feature_not_activated")
)
return
other_server = self._servers.get_server_by_id(server_id)
users_on_other_server = self._users.get_users_by_server_id(
other_server.id
).where(lambda x: not x.left_server)
discord_ids_on_other_server = users_on_other_server.select(
lambda x: x.discord_id
)
for user in self._users.get_users_by_server_id(
self._servers.get_server_by_discord_id(ctx.guild.id).id
).where(lambda x: not x.left_server):
try:
if user.discord_id not in discord_ids_on_other_server:
continue
user_on_other_server: User = users_on_other_server.where(
lambda x: x.discord_id == user.discord_id
).first_or_default()
if user_on_other_server is None or user_on_other_server.xp <= user.xp:
continue
user.xp = user_on_other_server.xp
self._users.update_user(user)
self._db.save_changes()
await self._level_service.check_level(
ctx.guild.get_member(user.discord_id)
)
except Exception as e:
self._logger.error(__name__, f"Cannot sync user {user.name}", e)
await self._message_service.send_ctx_msg(
ctx, self._t.transform("modules.technician.synced_message")
)
self._logger.trace(__name__, f"Finished sync xp command")
@all_members.autocomplete("server_id")
async def list_autocomplete(
self, interaction: discord.Interaction, current: str
) -> list[app_commands.Choice]:
return [
app_commands.Choice(name=server.name, value=server.id)
for server in self._client_utils.get_auto_complete_list(
self._servers.get_servers(), current, lambda x: x.name
)
]
@sync_xp.command(name="by_member")
@commands.guild_only()
@CommandChecks.check_is_ready()
@CommandChecks.check_is_member_technician()
async def by_member(self, ctx: Context, server_id: int, member: discord.Member):
self._logger.debug(__name__, f"Received command sync xp {ctx}")
if ctx.guild is None:
return
settings: ServerConfig = self._config.get_configuration(
f"ServerConfig_{ctx.guild.id}"
)
if not FeatureFlagsSettings.get_flag_from_dict(
settings.feature_flags, FeatureFlagsEnum.sync_xp
):
await self._message_service.send_ctx_msg(
ctx, self._t.transform("common.feature_not_activated")
)
return
other_server = self._servers.get_server_by_id(server_id)
user = self._users.get_user_by_discord_id_and_server_id(
self._servers.get_server_by_discord_id(ctx.guild.id).id, member.id
)
try:
user_on_other_server = (
self._users.get_users_by_server_id(other_server.id)
.where(lambda x: x.discord_id == member.id)
.first_or_default()
)
if user_on_other_server is None or user_on_other_server.xp <= user.xp:
return
user.xp = user_on_other_server.xp
self._users.update_user(user)
self._db.save_changes()
except Exception as e:
self._logger.error(__name__, f"Cannot sync user {user.name}", e)
await self._message_service.send_ctx_msg(
ctx, self._t.transform("modules.technician.synced_message")
)
await self._level_service.check_level(member)
self._logger.trace(__name__, f"Finished sync xp command")
@by_member.autocomplete("server_id")
async def list_autocomplete(
self, interaction: discord.Interaction, current: str
) -> list[app_commands.Choice]:
return [
app_commands.Choice(name=server.name, value=server.id)
for server in self._client_utils.get_auto_complete_list(
self._servers.get_servers(), current, lambda x: x.name
)
]

View File

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

View File

@@ -0,0 +1,36 @@
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceCollectionABC
from cpl_core.environment import ApplicationEnvironmentABC
from cpl_discord.service.discord_collection_abc import DiscordCollectionABC
from bot_core.abc.module_abc import ModuleABC
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
from bot_data.abc.data_seeder_abc import DataSeederABC
from modules.technician.api_key_seeder import ApiKeySeeder
from modules.technician.command.api_key_group import ApiKeyGroup
from modules.technician.command.log_command import LogCommand
from modules.technician.command.restart_command import RestartCommand
from modules.technician.command.shutdown_command import ShutdownCommand
from modules.technician.command.sync_xp_command import SyncXpGroup
class TechnicianModule(ModuleABC):
def __init__(self, dc: DiscordCollectionABC):
ModuleABC.__init__(self, dc, FeatureFlagsEnum.base_module)
def configure_configuration(
self, config: ConfigurationABC, env: ApplicationEnvironmentABC
):
pass
def configure_services(
self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC
):
services.add_transient(DataSeederABC, ApiKeySeeder)
# commands
services.add_transient(RestartCommand)
services.add_transient(ShutdownCommand)
services.add_transient(LogCommand)
services.add_transient(ApiKeyGroup)
services.add_transient(SyncXpGroup)
# events