Moved folders #405
This commit is contained in:
1
bot/src/modules/__init__.py
Normal file
1
bot/src/modules/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# imports
|
26
bot/src/modules/achievements/__init__.py
Normal file
26
bot/src/modules/achievements/__init__.py
Normal 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.achievements"
|
||||
__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")
|
@@ -0,0 +1,67 @@
|
||||
from typing import List
|
||||
|
||||
from bot_data.abc.achievement_repository_abc import AchievementRepositoryABC
|
||||
from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC
|
||||
from bot_data.abc.client_repository_abc import ClientRepositoryABC
|
||||
from bot_data.abc.game_server_repository_abc import GameServerRepositoryABC
|
||||
from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC
|
||||
from bot_data.abc.level_repository_abc import LevelRepositoryABC
|
||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
from bot_data.abc.user_joined_game_server_repository_abc import (
|
||||
UserJoinedGameServerRepositoryABC,
|
||||
)
|
||||
from bot_data.abc.user_joined_server_repository_abc import UserJoinedServerRepositoryABC
|
||||
from bot_data.abc.user_joined_voice_channel_repository_abc import (
|
||||
UserJoinedVoiceChannelRepositoryABC,
|
||||
)
|
||||
from bot_data.abc.user_repository_abc import UserRepositoryABC
|
||||
from bot_data.model.user import User
|
||||
|
||||
|
||||
class AchievementAttributeResolver:
|
||||
def __init__(
|
||||
self,
|
||||
auto_roles: AutoRoleRepositoryABC,
|
||||
clients: ClientRepositoryABC,
|
||||
known_users: KnownUserRepositoryABC,
|
||||
levels: LevelRepositoryABC,
|
||||
servers: ServerRepositoryABC,
|
||||
game_servers: GameServerRepositoryABC,
|
||||
user_joined_servers: UserJoinedServerRepositoryABC,
|
||||
user_joined_voice_channels: UserJoinedVoiceChannelRepositoryABC,
|
||||
user_joined_game_server: UserJoinedGameServerRepositoryABC,
|
||||
users: UserRepositoryABC,
|
||||
achievements: AchievementRepositoryABC,
|
||||
):
|
||||
self._auto_roles = auto_roles
|
||||
self._clients = clients
|
||||
self._known_users = known_users
|
||||
self._levels = levels
|
||||
self._servers = servers
|
||||
self._game_servers = game_servers
|
||||
self._user_joined_servers = user_joined_servers
|
||||
self._user_joined_voice_channels = user_joined_voice_channels
|
||||
self._user_joined_game_server = user_joined_game_server
|
||||
self._users = users
|
||||
self._achievements = achievements
|
||||
|
||||
def get_played_on_game_server(self, user: User) -> List[str]:
|
||||
joins = self._user_joined_game_server.get_user_joined_game_servers_by_user_id(
|
||||
user.id
|
||||
)
|
||||
return joins.select(lambda x: x.game_server.name)
|
||||
|
||||
def get_last_ontime_hours(self, user: User) -> int:
|
||||
ujvs = (
|
||||
self._user_joined_voice_channels.get_user_joined_voice_channels_by_user_id(
|
||||
user.id
|
||||
)
|
||||
)
|
||||
return int(
|
||||
str(
|
||||
ujvs.max(
|
||||
lambda join: (join.leaved_on - join.joined_on).total_seconds()
|
||||
/ 3600
|
||||
)
|
||||
)
|
||||
)
|
156
bot/src/modules/achievements/achievement_service.py
Normal file
156
bot/src/modules/achievements/achievement_service.py
Normal file
@@ -0,0 +1,156 @@
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.database.context import DatabaseContextABC
|
||||
from cpl_core.logging import LoggerABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from cpl_query.extension import List
|
||||
from cpl_translation import TranslatePipe
|
||||
|
||||
from bot_core.service.message_service import MessageService
|
||||
from bot_data.abc.achievement_repository_abc import AchievementRepositoryABC
|
||||
from bot_data.abc.user_repository_abc import UserRepositoryABC
|
||||
from bot_data.model.achievement import Achievement
|
||||
from bot_data.model.server_config import ServerConfig
|
||||
from bot_data.model.user import User
|
||||
from bot_data.model.user_got_achievement import UserGotAchievement
|
||||
from modules.achievements.achievement_attribute_resolver import (
|
||||
AchievementAttributeResolver,
|
||||
)
|
||||
from modules.achievements.model.achievement_attribute import AchievementAttribute
|
||||
|
||||
|
||||
class AchievementService:
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: LoggerABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
achievements: AchievementRepositoryABC,
|
||||
users: UserRepositoryABC,
|
||||
db: DatabaseContextABC,
|
||||
message_service: MessageService,
|
||||
resolver: AchievementAttributeResolver,
|
||||
t: TranslatePipe,
|
||||
):
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._achievements = achievements
|
||||
self._users = users
|
||||
self._db = db
|
||||
self._message_service = message_service
|
||||
self._t = t
|
||||
|
||||
self._attributes = List(AchievementAttribute)
|
||||
|
||||
self._attributes.extend(
|
||||
[
|
||||
AchievementAttribute("xp", lambda user: user.xp, "number"),
|
||||
AchievementAttribute(
|
||||
"message_count", lambda user: user.message_count, "number"
|
||||
),
|
||||
AchievementAttribute(
|
||||
"reaction_count", lambda user: user.reaction_count, "number"
|
||||
),
|
||||
AchievementAttribute("ontime", lambda user: user.ontime, "number"),
|
||||
AchievementAttribute("level", lambda user: user.level, "Level"),
|
||||
# special cases
|
||||
AchievementAttribute(
|
||||
"played_on_game_server",
|
||||
lambda user: resolver.get_played_on_game_server(user),
|
||||
"GameServer",
|
||||
),
|
||||
AchievementAttribute(
|
||||
"last_single_ontime_hours",
|
||||
lambda user: resolver.get_last_ontime_hours(user),
|
||||
"number",
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
self._operators = {
|
||||
"==": lambda value, expected_value: value == expected_value,
|
||||
"!=": lambda value, expected_value: value != expected_value,
|
||||
"<=": lambda value, expected_value: self._num(value)
|
||||
<= self._num(expected_value),
|
||||
">=": lambda value, expected_value: self._num(value)
|
||||
>= self._num(expected_value),
|
||||
"<": lambda value, expected_value: self._num(value)
|
||||
< self._num(expected_value),
|
||||
">": lambda value, expected_value: self._num(value)
|
||||
> self._num(expected_value),
|
||||
"contains": lambda value, expected_value: expected_value in value,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _num(s: str):
|
||||
try:
|
||||
return int(s)
|
||||
except ValueError:
|
||||
return float(s)
|
||||
|
||||
def add_achievement_attribute(self, atr: AchievementAttribute):
|
||||
self._attributes.add(atr)
|
||||
|
||||
def get_operators(self) -> list[str]:
|
||||
return [x for x in self._operators.keys()]
|
||||
|
||||
def get_attributes(self) -> List[AchievementAttribute]:
|
||||
return self._attributes
|
||||
|
||||
def _match(self, value: any, operator: str, expected_value: str) -> bool:
|
||||
return self._operators[operator](str(value), expected_value)
|
||||
|
||||
def has_user_achievement_already(
|
||||
self, user: User, achievement: Achievement
|
||||
) -> bool:
|
||||
user_achievements = self._achievements.get_achievements_by_user_id(user.id)
|
||||
return user_achievements.where(lambda x: x.name == achievement.name).count() > 0
|
||||
|
||||
def has_user_achievement(self, user: User, achievement: Achievement) -> bool:
|
||||
attribute: AchievementAttribute = self._attributes.where(
|
||||
lambda x: x.name == achievement.attribute
|
||||
).single()
|
||||
return self._match(
|
||||
attribute.resolve(user), achievement.operator, achievement.value
|
||||
)
|
||||
|
||||
async def validate_achievements_for_user(self, user: User):
|
||||
achievements = self._achievements.get_achievements_by_server_id(user.server.id)
|
||||
for achievement in achievements:
|
||||
if self.has_user_achievement_already(
|
||||
user, achievement
|
||||
) or not self.has_user_achievement(user, achievement):
|
||||
continue
|
||||
|
||||
self._achievements.add_user_got_achievement(
|
||||
UserGotAchievement(user, achievement, user.server)
|
||||
)
|
||||
self._db.save_changes()
|
||||
self._give_user_xp(user)
|
||||
await self._send_achievement_notification(
|
||||
user.server.discord_id, user.discord_id, achievement.name
|
||||
)
|
||||
|
||||
def _give_user_xp(self, user: User):
|
||||
settings: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{user.server.discord_id}"
|
||||
)
|
||||
user.xp += settings.xp_per_achievement
|
||||
self._users.update_user(user)
|
||||
self._db.save_changes()
|
||||
|
||||
async def _send_achievement_notification(
|
||||
self, guild_id: int, member_id: int, achievement_name: str
|
||||
):
|
||||
member = self._bot.get_guild(guild_id).get_member(member_id)
|
||||
|
||||
settings: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{guild_id}"
|
||||
)
|
||||
await self._message_service.send_channel_message(
|
||||
self._bot.get_channel(settings.notification_chat_id),
|
||||
self._t.transform("modules.achievements.got_new_achievement").format(
|
||||
member.mention, achievement_name
|
||||
),
|
||||
is_persistent=True,
|
||||
)
|
44
bot/src/modules/achievements/achievements.json
Normal file
44
bot/src/modules/achievements/achievements.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"ProjectSettings": {
|
||||
"Name": "achievements",
|
||||
"Version": {
|
||||
"Major": "1",
|
||||
"Minor": "2",
|
||||
"Micro": "0"
|
||||
},
|
||||
"Author": "Sven Heidemann",
|
||||
"AuthorEmail": "sven.heidemann@sh-edraft.de",
|
||||
"Description": "Keksdose bot - achievements",
|
||||
"LongDescription": "Discord bot for the Keksdose discord Server - achievements module",
|
||||
"URL": "https://www.sh-edraft.de",
|
||||
"CopyrightDate": "2023",
|
||||
"CopyrightName": "sh-edraft.de",
|
||||
"LicenseName": "MIT",
|
||||
"LicenseDescription": "MIT, see LICENSE for more details.",
|
||||
"Dependencies": [
|
||||
"cpl-core>=1.2.0"
|
||||
],
|
||||
"DevDependencies": [
|
||||
"cpl-cli>=1.2.0"
|
||||
],
|
||||
"PythonVersion": ">=3.10.4",
|
||||
"PythonPath": {},
|
||||
"Classifiers": []
|
||||
},
|
||||
"BuildSettings": {
|
||||
"ProjectType": "library",
|
||||
"SourcePath": "",
|
||||
"OutputPath": "../../dist",
|
||||
"Main": "achievements.main",
|
||||
"EntryPoint": "achievements",
|
||||
"IncludePackageData": false,
|
||||
"Included": [],
|
||||
"Excluded": [
|
||||
"*/__pycache__",
|
||||
"*/logs",
|
||||
"*/tests"
|
||||
],
|
||||
"PackageData": {},
|
||||
"ProjectReferences": []
|
||||
}
|
||||
}
|
58
bot/src/modules/achievements/achievements_module.py
Normal file
58
bot/src/modules/achievements/achievements_module.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.dependency_injection import ServiceCollectionABC
|
||||
from cpl_core.environment import ApplicationEnvironmentABC
|
||||
from cpl_discord.discord_event_types_enum import DiscordEventTypesEnum
|
||||
from cpl_discord.service.discord_collection_abc import DiscordCollectionABC
|
||||
|
||||
from bot_core.abc.module_abc import ModuleABC
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from modules.achievements.achievement_attribute_resolver import (
|
||||
AchievementAttributeResolver,
|
||||
)
|
||||
from modules.achievements.achievement_service import AchievementService
|
||||
from modules.achievements.commands.achievements_group import AchievementGroup
|
||||
from modules.achievements.events.achievement_on_message_event import (
|
||||
AchievementOnMessageEvent,
|
||||
)
|
||||
from modules.achievements.events.achievement_on_reaction_add_event import (
|
||||
AchievementOnReactionAddEvent,
|
||||
)
|
||||
from modules.achievements.events.achievement_on_reaction_remove_event import (
|
||||
AchievementOnReactionRemoveEvent,
|
||||
)
|
||||
from modules.achievements.events.achievement_on_voice_state_update_event import (
|
||||
AchievementOnVoiceStateUpdateEvent,
|
||||
)
|
||||
|
||||
|
||||
class AchievementsModule(ModuleABC):
|
||||
def __init__(self, dc: DiscordCollectionABC):
|
||||
ModuleABC.__init__(self, dc, FeatureFlagsEnum.achievements_module)
|
||||
|
||||
def configure_configuration(
|
||||
self, config: ConfigurationABC, env: ApplicationEnvironmentABC
|
||||
):
|
||||
pass
|
||||
|
||||
def configure_services(
|
||||
self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC
|
||||
):
|
||||
services.add_transient(AchievementAttributeResolver)
|
||||
services.add_transient(AchievementService)
|
||||
|
||||
services.add_transient(AchievementGroup)
|
||||
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_message.value, AchievementOnMessageEvent
|
||||
)
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_reaction_add.value, AchievementOnReactionAddEvent
|
||||
)
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_reaction_remove.value,
|
||||
AchievementOnReactionRemoveEvent,
|
||||
)
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_voice_state_update.value,
|
||||
AchievementOnVoiceStateUpdateEvent,
|
||||
)
|
26
bot/src/modules/achievements/commands/__init__.py
Normal file
26
bot/src/modules/achievements/commands/__init__.py
Normal 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.achievements.commands"
|
||||
__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")
|
73
bot/src/modules/achievements/commands/achievements_group.py
Normal file
73
bot/src/modules/achievements/commands/achievements_group.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import discord
|
||||
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.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 modules.achievements.achievement_service import AchievementService
|
||||
|
||||
|
||||
class AchievementGroup(DiscordCommandABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: CommandLogger,
|
||||
message_service: MessageServiceABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
servers: ServerRepositoryABC,
|
||||
users: UserRepositoryABC,
|
||||
achievement_service: AchievementService,
|
||||
translate: TranslatePipe,
|
||||
):
|
||||
DiscordCommandABC.__init__(self)
|
||||
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._message_service = message_service
|
||||
self._bot = bot
|
||||
self._servers = servers
|
||||
self._users = users
|
||||
self._achievement_service = achievement_service
|
||||
self._t = translate
|
||||
|
||||
@commands.hybrid_group()
|
||||
@commands.guild_only()
|
||||
async def achievement(self, ctx: Context):
|
||||
pass
|
||||
|
||||
@achievement.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def check(self, ctx: Context, member: discord.Member):
|
||||
self._logger.debug(__name__, f"Received command achievement check {ctx}")
|
||||
if ctx.guild is None:
|
||||
return
|
||||
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{ctx.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.achievements_module
|
||||
):
|
||||
return
|
||||
|
||||
server = self._servers.get_server_by_discord_id(member.guild.id)
|
||||
user = self._users.get_user_by_discord_id_and_server_id(member.id, server.id)
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.achievements.commands.check"),
|
||||
)
|
||||
await self._achievement_service.validate_achievements_for_user(user)
|
||||
|
||||
self._logger.trace(__name__, f"Finished command achievement check")
|
26
bot/src/modules/achievements/events/__init__.py
Normal file
26
bot/src/modules/achievements/events/__init__.py
Normal 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.achievements.events"
|
||||
__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")
|
@@ -0,0 +1,63 @@
|
||||
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 OnMessageABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
from bot_core.helper.event_checks import EventChecks
|
||||
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 modules.achievements.achievement_service import AchievementService
|
||||
|
||||
|
||||
class AchievementOnMessageEvent(OnMessageABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: LoggerABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
achievements: AchievementService,
|
||||
db: DatabaseContextABC,
|
||||
servers: ServerRepositoryABC,
|
||||
users: UserRepositoryABC,
|
||||
):
|
||||
OnMessageABC.__init__(self)
|
||||
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._achievements = achievements
|
||||
self._db = db
|
||||
self._servers = servers
|
||||
self._users = users
|
||||
|
||||
@EventChecks.check_is_ready()
|
||||
async def on_message(self, message: discord.Message):
|
||||
if message.guild is None:
|
||||
return
|
||||
|
||||
if message.author.bot:
|
||||
return
|
||||
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{message.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.achievements_module
|
||||
):
|
||||
return
|
||||
|
||||
server = self._servers.get_server_by_discord_id(message.guild.id)
|
||||
user = self._users.get_user_by_discord_id_and_server_id(
|
||||
message.author.id, server.id
|
||||
)
|
||||
|
||||
user.message_count += 1
|
||||
self._users.update_user(user)
|
||||
self._db.save_changes()
|
||||
|
||||
await self._achievements.validate_achievements_for_user(user)
|
@@ -0,0 +1,68 @@
|
||||
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 OnReactionAddABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
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 modules.achievements.achievement_service import AchievementService
|
||||
|
||||
|
||||
class AchievementOnReactionAddEvent(OnReactionAddABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: LoggerABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
achievements: AchievementService,
|
||||
db: DatabaseContextABC,
|
||||
servers: ServerRepositoryABC,
|
||||
users: UserRepositoryABC,
|
||||
):
|
||||
OnReactionAddABC.__init__(self)
|
||||
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._achievements = achievements
|
||||
self._db = db
|
||||
self._servers = servers
|
||||
self._users = users
|
||||
|
||||
async def on_reaction_add(
|
||||
self,
|
||||
reaction: discord.reaction.Reaction,
|
||||
user: Union[discord.Member, discord.User],
|
||||
):
|
||||
if not isinstance(user, discord.Member):
|
||||
return
|
||||
|
||||
if user.guild is None:
|
||||
return
|
||||
|
||||
if user.bot:
|
||||
return
|
||||
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{user.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.achievements_module
|
||||
):
|
||||
return
|
||||
|
||||
server = self._servers.get_server_by_discord_id(user.guild.id)
|
||||
user = self._users.get_user_by_discord_id_and_server_id(user.id, server.id)
|
||||
|
||||
user.reaction_count += 1
|
||||
self._db.save_changes()
|
||||
self._users.update_user(user)
|
||||
|
||||
await self._achievements.validate_achievements_for_user(user)
|
@@ -0,0 +1,68 @@
|
||||
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 OnReactionRemoveABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
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 modules.achievements.achievement_service import AchievementService
|
||||
|
||||
|
||||
class AchievementOnReactionRemoveEvent(OnReactionRemoveABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: LoggerABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
achievements: AchievementService,
|
||||
db: DatabaseContextABC,
|
||||
servers: ServerRepositoryABC,
|
||||
users: UserRepositoryABC,
|
||||
):
|
||||
OnReactionRemoveABC.__init__(self)
|
||||
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._achievements = achievements
|
||||
self._db = db
|
||||
self._servers = servers
|
||||
self._users = users
|
||||
|
||||
async def on_reaction_remove(
|
||||
self,
|
||||
reaction: discord.reaction.Reaction,
|
||||
user: Union[discord.Member, discord.User],
|
||||
):
|
||||
if not isinstance(user, discord.Member):
|
||||
return
|
||||
|
||||
if user.guild is None:
|
||||
return
|
||||
|
||||
if user.bot:
|
||||
return
|
||||
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{user.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.achievements_module
|
||||
):
|
||||
return
|
||||
|
||||
server = self._servers.get_server_by_discord_id(user.guild.id)
|
||||
user = self._users.get_user_by_discord_id_and_server_id(user.id, server.id)
|
||||
|
||||
user.reaction_count -= 1
|
||||
self._db.save_changes()
|
||||
self._users.update_user(user)
|
||||
|
||||
await self._achievements.validate_achievements_for_user(user)
|
@@ -0,0 +1,60 @@
|
||||
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 cpl_discord.service import DiscordBotServiceABC
|
||||
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
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 modules.achievements.achievement_service import AchievementService
|
||||
|
||||
|
||||
class AchievementOnVoiceStateUpdateEvent(OnVoiceStateUpdateABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: LoggerABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
achievements: AchievementService,
|
||||
db: DatabaseContextABC,
|
||||
servers: ServerRepositoryABC,
|
||||
users: UserRepositoryABC,
|
||||
):
|
||||
OnVoiceStateUpdateABC.__init__(self)
|
||||
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._achievements = achievements
|
||||
self._db = db
|
||||
self._servers = servers
|
||||
self._users = users
|
||||
|
||||
async def on_voice_state_update(
|
||||
self,
|
||||
member: discord.member.Member,
|
||||
before: discord.member.VoiceState,
|
||||
after: discord.member.VoiceState,
|
||||
):
|
||||
if member.guild is None:
|
||||
return
|
||||
|
||||
if member.bot:
|
||||
return
|
||||
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{member.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.achievements_module
|
||||
):
|
||||
return
|
||||
|
||||
server = self._servers.get_server_by_discord_id(member.guild.id)
|
||||
user = self._users.get_user_by_discord_id_and_server_id(member.id, server.id)
|
||||
|
||||
await self._achievements.validate_achievements_for_user(user)
|
26
bot/src/modules/achievements/model/__init__.py
Normal file
26
bot/src/modules/achievements/model/__init__.py
Normal 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.achievements.model"
|
||||
__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")
|
20
bot/src/modules/achievements/model/achievement_attribute.py
Normal file
20
bot/src/modules/achievements/model/achievement_attribute.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from typing import Callable
|
||||
|
||||
|
||||
class AchievementAttribute:
|
||||
# frontend type = TypeScript types
|
||||
def __init__(self, name: str, resolver: Callable, frontend_type: str):
|
||||
self._name = name
|
||||
self._resolver = resolver
|
||||
self._frontend_type = frontend_type
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def type(self) -> str:
|
||||
return self._frontend_type
|
||||
|
||||
def resolve(self, *args, **kwargs):
|
||||
return self._resolver(*args, **kwargs)
|
26
bot/src/modules/auto_role/__init__.py
Normal file
26
bot/src/modules/auto_role/__init__.py
Normal 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.auto_role"
|
||||
__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")
|
44
bot/src/modules/auto_role/auto-role.json
Normal file
44
bot/src/modules/auto_role/auto-role.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"ProjectSettings": {
|
||||
"Name": "auto-role",
|
||||
"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.4",
|
||||
"PythonPath": {},
|
||||
"Classifiers": []
|
||||
},
|
||||
"BuildSettings": {
|
||||
"ProjectType": "library",
|
||||
"SourcePath": "",
|
||||
"OutputPath": "../../dist",
|
||||
"Main": "admin.main",
|
||||
"EntryPoint": "admin",
|
||||
"IncludePackageData": false,
|
||||
"Included": [],
|
||||
"Excluded": [
|
||||
"*/__pycache__",
|
||||
"*/logs",
|
||||
"*/tests"
|
||||
],
|
||||
"PackageData": {},
|
||||
"ProjectReferences": []
|
||||
}
|
||||
}
|
42
bot/src/modules/auto_role/auto_role_module.py
Normal file
42
bot/src/modules/auto_role/auto_role_module.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.dependency_injection import ServiceCollectionABC
|
||||
from cpl_core.environment import ApplicationEnvironmentABC
|
||||
from cpl_discord.discord_event_types_enum import DiscordEventTypesEnum
|
||||
from cpl_discord.service.discord_collection_abc import DiscordCollectionABC
|
||||
|
||||
from bot_core.abc.module_abc import ModuleABC
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from modules.auto_role.command.auto_role_group import AutoRoleGroup
|
||||
from modules.auto_role.events.auto_role_on_raw_reaction_add import (
|
||||
AutoRoleOnRawReactionAddEvent,
|
||||
)
|
||||
from modules.auto_role.events.auto_role_on_raw_reaction_remove import (
|
||||
AutoRoleOnRawReactionRemoveEvent,
|
||||
)
|
||||
from modules.auto_role.helper.auto_role_reaction_handler import AutoRoleReactionHandler
|
||||
|
||||
|
||||
class AutoRoleModule(ModuleABC):
|
||||
def __init__(self, dc: DiscordCollectionABC):
|
||||
ModuleABC.__init__(self, dc, FeatureFlagsEnum.auto_role_module)
|
||||
|
||||
def configure_configuration(
|
||||
self, config: ConfigurationABC, env: ApplicationEnvironmentABC
|
||||
):
|
||||
pass
|
||||
|
||||
def configure_services(
|
||||
self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC
|
||||
):
|
||||
services.add_transient(AutoRoleReactionHandler)
|
||||
# commands
|
||||
services.add_transient(AutoRoleGroup)
|
||||
# events
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_raw_reaction_add.value,
|
||||
AutoRoleOnRawReactionAddEvent,
|
||||
)
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_raw_reaction_remove.value,
|
||||
AutoRoleOnRawReactionRemoveEvent,
|
||||
)
|
26
bot/src/modules/auto_role/command/__init__.py
Normal file
26
bot/src/modules/auto_role/command/__init__.py
Normal 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.auto_role.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")
|
579
bot/src/modules/auto_role/command/auto_role_group.py
Normal file
579
bot/src/modules/auto_role/command/auto_role_group.py
Normal file
@@ -0,0 +1,579 @@
|
||||
from typing import List as TList
|
||||
|
||||
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_query.extension import List
|
||||
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.auto_role_repository_abc import AutoRoleRepositoryABC
|
||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
from bot_data.model.auto_role import AutoRole
|
||||
from bot_data.model.auto_role_rule import AutoRoleRule
|
||||
from bot_data.model.server_config import ServerConfig
|
||||
from modules.permission.abc.permission_service_abc import PermissionServiceABC
|
||||
|
||||
|
||||
class AutoRoleGroup(DiscordCommandABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: CommandLogger,
|
||||
message_service: MessageServiceABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
client_utils: ClientUtilsABC,
|
||||
translate: TranslatePipe,
|
||||
servers: ServerRepositoryABC,
|
||||
auto_roles: AutoRoleRepositoryABC,
|
||||
db_context: DatabaseContextABC,
|
||||
permission_service: PermissionServiceABC,
|
||||
):
|
||||
DiscordCommandABC.__init__(self)
|
||||
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._message_service = message_service
|
||||
self._bot = bot
|
||||
self._client_utils = client_utils
|
||||
self._t = translate
|
||||
self._servers = servers
|
||||
self._auto_roles = auto_roles
|
||||
self._db_context = db_context
|
||||
self._permissions = permission_service
|
||||
|
||||
self._logger.trace(__name__, f"Loaded command service: {type(self).__name__}")
|
||||
|
||||
async def _auto_role_auto_complete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
server = self._servers.get_server_by_discord_id(interaction.guild.id)
|
||||
auto_roles = self._auto_roles.get_auto_roles_by_server_id(server.id).select(
|
||||
lambda x: x.id
|
||||
)
|
||||
return [
|
||||
app_commands.Choice(name=auto_role, value=auto_role)
|
||||
for auto_role in self._client_utils.get_auto_complete_list(
|
||||
auto_roles, current, lambda x: x.name
|
||||
)
|
||||
]
|
||||
|
||||
@commands.hybrid_group(name="auto-role")
|
||||
@commands.guild_only()
|
||||
async def auto_role(self, ctx: Context):
|
||||
pass
|
||||
|
||||
@auto_role.command(alias="auto-roles")
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def list(self, ctx: Context, wait: int = None):
|
||||
self._logger.debug(__name__, f"Received command auto-role list {ctx}")
|
||||
if ctx.guild is None:
|
||||
return
|
||||
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{ctx.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.auto_role_module
|
||||
):
|
||||
return
|
||||
|
||||
embed = discord.Embed(
|
||||
title=self._t.transform("modules.auto_role.list.title"),
|
||||
description=self._t.transform("modules.auto_role.list.description"),
|
||||
color=int("ef9d0d", 16),
|
||||
)
|
||||
server = self._servers.get_server_by_discord_id(ctx.guild.id)
|
||||
auto_roles = self._auto_roles.get_auto_roles_by_server_id(server.id)
|
||||
if auto_roles.count() < 1:
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.auto_role.error.nothing_found")
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command auto-role list")
|
||||
return
|
||||
|
||||
auto_role_id = ""
|
||||
message_id = ""
|
||||
for auto_role in auto_roles:
|
||||
auto_role_id += f"\n{auto_role.id}"
|
||||
message_id += f"\n{auto_role.discord_message_id}"
|
||||
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.auto_role.list.auto_role_id"),
|
||||
value=auto_role_id,
|
||||
inline=True,
|
||||
)
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.auto_role.list.message_id"),
|
||||
value=message_id,
|
||||
inline=True,
|
||||
)
|
||||
await self._message_service.send_ctx_msg(ctx, embed, wait_before_delete=wait)
|
||||
self._logger.trace(__name__, f"Finished command auto-role list")
|
||||
|
||||
@auto_role.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def add(self, ctx: Context, channel: discord.TextChannel, message_id: str):
|
||||
self._logger.debug(
|
||||
__name__, f"Received command auto-role add {ctx} {message_id}"
|
||||
)
|
||||
if ctx.guild is None:
|
||||
return
|
||||
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{ctx.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.auto_role_module
|
||||
):
|
||||
return
|
||||
|
||||
message = (
|
||||
List(
|
||||
discord.Message,
|
||||
[message async for message in channel.history(limit=50)],
|
||||
)
|
||||
.where(lambda m: m.id == int(message_id))
|
||||
.single_or_default()
|
||||
)
|
||||
if message is None:
|
||||
self._logger.debug(
|
||||
__name__, f"Message with id {message_id} not found in {channel.name}"
|
||||
)
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.auto_role.add.error.not_found").format(
|
||||
message_id, channel.mention
|
||||
),
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command auto-role add")
|
||||
return
|
||||
|
||||
if self._auto_roles.find_auto_role_by_message_id(int(message_id)) is not None:
|
||||
self._logger.debug(
|
||||
__name__, f"auto-role for message {message_id} already exists"
|
||||
)
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.auto_role.add.error.already_exists").format(
|
||||
message_id
|
||||
),
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command auto-role add")
|
||||
return
|
||||
|
||||
server = self._servers.get_server_by_discord_id(ctx.guild.id)
|
||||
self._auto_roles.add_auto_role(
|
||||
AutoRole(server, int(channel.id), int(message_id))
|
||||
)
|
||||
self._db_context.save_changes()
|
||||
self._logger.info(
|
||||
__name__, f"Saved auto-role for message {message_id} at server {server}"
|
||||
)
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.auto_role.add.success").format(message_id)
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command auto-role add")
|
||||
|
||||
@add.autocomplete("message_id")
|
||||
async def add_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
channel = discord.utils.get(
|
||||
interaction.guild.text_channels, id=interaction.channel_id
|
||||
)
|
||||
try:
|
||||
channel_from_data = interaction.data["options"][0]["options"][0]["value"]
|
||||
found_channel = discord.utils.get(
|
||||
interaction.guild.text_channels, id=int(channel_from_data)
|
||||
)
|
||||
if found_channel is not None:
|
||||
channel = found_channel
|
||||
finally:
|
||||
pass
|
||||
|
||||
messages = [message async for message in channel.history(limit=10)]
|
||||
return [
|
||||
app_commands.Choice(
|
||||
name=f"{message.author}@{message.created_at}", value=str(message.id)
|
||||
)
|
||||
for message in messages
|
||||
if current in str(message.id)
|
||||
]
|
||||
|
||||
@auto_role.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def remove(self, ctx: Context, auto_role: int):
|
||||
self._logger.debug(
|
||||
__name__, f"Received command auto-role remove {ctx} {auto_role}"
|
||||
)
|
||||
if ctx.guild is None:
|
||||
return
|
||||
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{ctx.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.auto_role_module
|
||||
):
|
||||
return
|
||||
|
||||
auto_role_from_db = self._auto_roles.find_auto_role_by_id(auto_role)
|
||||
if auto_role_from_db is None:
|
||||
self._logger.debug(__name__, f"auto-role {auto_role} not found")
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.auto_role.remove.error.not_found").format(
|
||||
auto_role
|
||||
),
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command auto-role remove")
|
||||
return
|
||||
|
||||
for rule in self._auto_roles.get_auto_role_rules_by_auto_role_id(
|
||||
auto_role_from_db.id
|
||||
):
|
||||
self._auto_roles.delete_auto_role_rule(rule)
|
||||
self._logger.info(__name__, f"Removed auto-role rule {rule.role_id}")
|
||||
|
||||
self._auto_roles.delete_auto_role(auto_role_from_db)
|
||||
self._db_context.save_changes()
|
||||
self._logger.info(__name__, f"Removed auto-role {auto_role}")
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.auto_role.remove.success").format(auto_role)
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command auto-role remove")
|
||||
|
||||
@remove.autocomplete("auto_role")
|
||||
async def remove_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
return await self._auto_role_auto_complete(interaction, current)
|
||||
|
||||
@auto_role.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def react(self, ctx: Context, auto_role: int):
|
||||
self._logger.debug(
|
||||
__name__, f"Received command auto-role react {ctx} {auto_role}"
|
||||
)
|
||||
if ctx.guild is None:
|
||||
return
|
||||
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{ctx.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.auto_role_module
|
||||
):
|
||||
return
|
||||
|
||||
auto_role_from_db = self._auto_roles.find_auto_role_by_id(auto_role)
|
||||
if auto_role_from_db is None:
|
||||
self._logger.debug(__name__, f"auto-role {auto_role} not found")
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.auto_role.remove.error.not_found").format(
|
||||
auto_role
|
||||
),
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command auto-role react")
|
||||
return
|
||||
|
||||
for rule in self._auto_roles.get_auto_role_rules_by_auto_role_id(
|
||||
auto_role_from_db.id
|
||||
):
|
||||
await self._client_utils.react_to_message_by_auto_role_rule(
|
||||
auto_role_from_db.discord_channel_id,
|
||||
auto_role_from_db.discord_message_id,
|
||||
rule,
|
||||
ctx.guild,
|
||||
)
|
||||
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.auto_role.react.success"),
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command auto-role react")
|
||||
|
||||
@react.autocomplete("auto_role")
|
||||
async def react_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
return await self._auto_role_auto_complete(interaction, current)
|
||||
|
||||
@auto_role.group()
|
||||
@commands.guild_only()
|
||||
async def rule(self, ctx: Context):
|
||||
pass
|
||||
|
||||
@rule.command(alias="rules")
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def list(self, ctx: Context, auto_role: int, wait: int = None):
|
||||
self._logger.debug(__name__, f"Received command auto-role rule list {ctx}")
|
||||
if ctx.guild is None:
|
||||
return
|
||||
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{ctx.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.auto_role_module
|
||||
):
|
||||
return
|
||||
|
||||
embed = discord.Embed(
|
||||
title=self._t.transform("modules.auto_role.list.title"),
|
||||
description=self._t.transform("modules.auto_role.list.description"),
|
||||
color=int("ef9d0d", 16),
|
||||
)
|
||||
rules = self._auto_roles.get_auto_role_rules_by_auto_role_id(auto_role)
|
||||
if rules.count() < 1:
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.auto_role.rule.error.id_not_found")
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command auto-role rule list")
|
||||
return
|
||||
|
||||
auto_role_rule_id = ""
|
||||
emoji = ""
|
||||
role = ""
|
||||
for rule in rules:
|
||||
auto_role_rule_id += f"\n{rule.role_id}"
|
||||
emoji += f"\n{rule.emoji_name}"
|
||||
role += f"\n{ctx.guild.get_role(rule.role_id)}"
|
||||
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.auto_role.rule.list.auto_role_rule_id"),
|
||||
value=auto_role_rule_id,
|
||||
inline=True,
|
||||
)
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.auto_role.rule.list.emoji"),
|
||||
value=emoji,
|
||||
inline=True,
|
||||
)
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.auto_role.rule.list.role"),
|
||||
value=role,
|
||||
inline=True,
|
||||
)
|
||||
await self._message_service.send_ctx_msg(ctx, embed, wait_before_delete=wait)
|
||||
self._logger.trace(__name__, f"Finished command auto-role rule list")
|
||||
|
||||
@list.autocomplete("auto_role")
|
||||
async def list_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
return await self._auto_role_auto_complete(interaction, current)
|
||||
|
||||
@rule.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def add(self, ctx: Context, auto_role: int, emoji_name: str, role_id: str):
|
||||
self._logger.debug(
|
||||
__name__, f"Received command auto-role add {ctx} {auto_role}"
|
||||
)
|
||||
if ctx.guild is None:
|
||||
return
|
||||
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{ctx.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.auto_role_module
|
||||
):
|
||||
return
|
||||
|
||||
emoji = discord.utils.get(self._bot.emojis, name=emoji_name)
|
||||
if emoji is None:
|
||||
self._logger.debug(
|
||||
__name__, f"auto-role rule add emoji {emoji_name} not found"
|
||||
)
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform(
|
||||
"modules.auto_role.rule.add.error.emoji_not_found"
|
||||
).format(emoji_name, auto_role),
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command auto-role add")
|
||||
return
|
||||
|
||||
role = ctx.guild.get_role(int(role_id))
|
||||
if role is None:
|
||||
self._logger.debug(__name__, f"auto-role rule add role {role_id} not found")
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform(
|
||||
"modules.auto_role.rule.add.error.role_not_found"
|
||||
).format(role_id, auto_role),
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command auto-role add")
|
||||
return
|
||||
|
||||
auto_role_from_db = self._auto_roles.get_auto_role_by_id(auto_role)
|
||||
if auto_role_from_db is None:
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.auto_role.remove.error.not_found").format(
|
||||
auto_role
|
||||
),
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command auto-role rule add")
|
||||
return
|
||||
|
||||
if (
|
||||
self._auto_roles.get_auto_role_rules_by_auto_role_id(auto_role)
|
||||
.where(lambda r: r.emoji_name == emoji.name and int(role_id) == role.id)
|
||||
.first_or_default()
|
||||
is not None
|
||||
):
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.auto_role.add.error.already_exists").format(
|
||||
auto_role
|
||||
),
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command auto-role rule add")
|
||||
return
|
||||
|
||||
self._auto_roles.add_auto_role_rule(
|
||||
AutoRoleRule(auto_role_from_db, emoji_name, int(role_id))
|
||||
)
|
||||
self._db_context.save_changes()
|
||||
rule = (
|
||||
self._auto_roles.get_auto_role_rules_by_auto_role_id(auto_role)
|
||||
.where(lambda r: r.emoji_name == emoji.name and int(role_id) == role.id)
|
||||
.single()
|
||||
)
|
||||
|
||||
# as task to run in background
|
||||
self._bot.loop.create_task(
|
||||
await self._client_utils.react_to_message_by_auto_role_rule(
|
||||
auto_role_from_db.discord_channel_id,
|
||||
auto_role_from_db.discord_message_id,
|
||||
rule,
|
||||
ctx.guild,
|
||||
)
|
||||
)
|
||||
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.auto_role.rule.add.success").format(
|
||||
emoji, role.mention, auto_role
|
||||
),
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command auto-role rule add")
|
||||
|
||||
@add.autocomplete("auto_role")
|
||||
async def add_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
return await self._auto_role_auto_complete(interaction, current)
|
||||
|
||||
@add.autocomplete("emoji_name")
|
||||
async def add_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
emojis = List(discord.Emoji, interaction.guild.emojis)
|
||||
return [
|
||||
app_commands.Choice(name=emoji.name, value=emoji.name)
|
||||
for emoji in self._client_utils.get_auto_complete_list(
|
||||
emojis, current, lambda e: e.name
|
||||
)
|
||||
]
|
||||
|
||||
@add.autocomplete("role_id")
|
||||
async def rule_add_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
roles = List(discord.Role, interaction.guild.roles)
|
||||
return [
|
||||
app_commands.Choice(name=role.name, value=str(role.id))
|
||||
for role in self._client_utils.get_auto_complete_list(
|
||||
roles, current, lambda r: r.name
|
||||
)
|
||||
]
|
||||
|
||||
@rule.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def remove(self, ctx: Context, auto_role_rule: int):
|
||||
self._logger.debug(
|
||||
__name__, f"Received command auto-role remove {ctx} {auto_role_rule}"
|
||||
)
|
||||
if ctx.guild is None:
|
||||
return
|
||||
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{ctx.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.auto_role_module
|
||||
):
|
||||
return
|
||||
|
||||
auto_role_from_db = self._auto_roles.get_auto_role_rule_by_id(auto_role_rule)
|
||||
if auto_role_from_db is None:
|
||||
self._logger.debug(__name__, f"auto-role rule {auto_role_rule} not found")
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.auto_role.remove.error.not_found").format(
|
||||
auto_role_rule
|
||||
),
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command auto-role rule remove")
|
||||
return
|
||||
|
||||
self._auto_roles.delete_auto_role_rule(auto_role_from_db)
|
||||
self._db_context.save_changes()
|
||||
self._logger.info(__name__, f"Removed auto-role rule {auto_role_rule}")
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.auto_role.rule.remove.success").format(
|
||||
auto_role_rule
|
||||
),
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command auto-role remove")
|
||||
|
||||
@remove.autocomplete("auto_role_rule")
|
||||
async def remove_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
server = self._servers.get_server_by_discord_id(interaction.guild.id)
|
||||
auto_roles = self._auto_roles.get_auto_roles_by_server_id(server.id).select(
|
||||
lambda x: x.id
|
||||
)
|
||||
rules = auto_roles.select_many(
|
||||
lambda ar: self._auto_roles.get_auto_role_rules_by_auto_role_id(ar)
|
||||
)
|
||||
return [
|
||||
app_commands.Choice(
|
||||
name=f"{rule.id} {rule.emoji_name} {interaction.guild.get_role(int(rule.role_id))}",
|
||||
value=rule.id,
|
||||
)
|
||||
for rule in self._client_utils.get_auto_complete_list(
|
||||
rules, current, lambda r: r.id
|
||||
)
|
||||
]
|
26
bot/src/modules/auto_role/events/__init__.py
Normal file
26
bot/src/modules/auto_role/events/__init__.py
Normal 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.auto_role.events"
|
||||
__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")
|
@@ -0,0 +1,48 @@
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.logging import LoggerABC
|
||||
from cpl_discord.events.on_raw_reaction_add_abc import OnRawReactionAddABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from discord import RawReactionActionEvent
|
||||
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
from bot_core.helper.event_checks import EventChecks
|
||||
from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC
|
||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
from bot_data.model.server_config import ServerConfig
|
||||
from modules.auto_role.helper.auto_role_reaction_handler import AutoRoleReactionHandler
|
||||
|
||||
|
||||
class AutoRoleOnRawReactionAddEvent(OnRawReactionAddABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: LoggerABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
servers: ServerRepositoryABC,
|
||||
auto_roles: AutoRoleRepositoryABC,
|
||||
reaction_handler: AutoRoleReactionHandler,
|
||||
):
|
||||
OnRawReactionAddABC.__init__(self)
|
||||
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._servers = servers
|
||||
self._auto_roles = auto_roles
|
||||
self._reaction_handler = reaction_handler
|
||||
|
||||
@EventChecks.check_is_ready()
|
||||
async def on_raw_reaction_add(self, payload: RawReactionActionEvent):
|
||||
self._logger.debug(__name__, f"Module {type(self)} started")
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{payload.guild_id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.auto_role_module
|
||||
):
|
||||
return
|
||||
|
||||
await self._reaction_handler.handle(payload, "add")
|
||||
|
||||
self._logger.debug(__name__, f"Module {type(self)} stopped")
|
@@ -0,0 +1,48 @@
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.logging import LoggerABC
|
||||
from cpl_discord.events.on_raw_reaction_remove_abc import OnRawReactionRemoveABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from discord import RawReactionActionEvent
|
||||
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
from bot_core.helper.event_checks import EventChecks
|
||||
from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC
|
||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
from bot_data.model.server_config import ServerConfig
|
||||
from modules.auto_role.helper.auto_role_reaction_handler import AutoRoleReactionHandler
|
||||
|
||||
|
||||
class AutoRoleOnRawReactionRemoveEvent(OnRawReactionRemoveABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: LoggerABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
servers: ServerRepositoryABC,
|
||||
auto_roles: AutoRoleRepositoryABC,
|
||||
reaction_handler: AutoRoleReactionHandler,
|
||||
):
|
||||
OnRawReactionRemoveABC.__init__(self)
|
||||
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._servers = servers
|
||||
self._auto_roles = auto_roles
|
||||
self._reaction_handler = reaction_handler
|
||||
|
||||
@EventChecks.check_is_ready()
|
||||
async def on_raw_reaction_remove(self, payload: RawReactionActionEvent):
|
||||
self._logger.debug(__name__, f"Module {type(self)} started")
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{payload.guild_id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.auto_role_module
|
||||
):
|
||||
return
|
||||
|
||||
await self._reaction_handler.handle(payload, "remove")
|
||||
|
||||
self._logger.debug(__name__, f"Module {type(self)} stopped")
|
26
bot/src/modules/auto_role/helper/__init__.py
Normal file
26
bot/src/modules/auto_role/helper/__init__.py
Normal 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.auto_role.helper"
|
||||
__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")
|
@@ -0,0 +1,76 @@
|
||||
from cpl_core.logging import LoggerABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from cpl_query.extension import List
|
||||
from discord import RawReactionActionEvent
|
||||
|
||||
from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC
|
||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
from bot_data.model.auto_role import AutoRole
|
||||
from bot_data.model.auto_role_rule import AutoRoleRule
|
||||
|
||||
|
||||
class AutoRoleReactionHandler:
|
||||
def __init__(
|
||||
self,
|
||||
logger: LoggerABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
servers: ServerRepositoryABC,
|
||||
auto_roles: AutoRoleRepositoryABC,
|
||||
):
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._servers = servers
|
||||
self._auto_roles = auto_roles
|
||||
|
||||
self._message_ids = self._auto_roles.get_auto_roles().select(
|
||||
lambda x: x.discord_message_id
|
||||
)
|
||||
self._roles = self._auto_roles.get_auto_roles()
|
||||
|
||||
async def handle(self, payload: RawReactionActionEvent, r_type=None) -> None:
|
||||
self._logger.trace(__name__, f"Handle reaction {payload} {r_type}")
|
||||
if payload.message_id not in self._message_ids:
|
||||
self._logger.debug(__name__, f"Message not in auto-roles - skipping")
|
||||
return
|
||||
|
||||
guild = self._bot.get_guild(payload.guild_id)
|
||||
user = guild.get_member(payload.user_id)
|
||||
if user is None:
|
||||
self._logger.warn(
|
||||
__name__, f"User {payload.user_id} in {guild.name} not found - skipping"
|
||||
)
|
||||
return
|
||||
|
||||
if user.bot:
|
||||
self._logger.debug(__name__, f"User is bot - skipping")
|
||||
return
|
||||
|
||||
emoji = payload.emoji.name
|
||||
auto_role: AutoRole = self._roles.where(
|
||||
lambda x: x.discord_message_id == payload.message_id
|
||||
).single_or_default()
|
||||
if auto_role is None:
|
||||
self._logger.debug(__name__, f"auto-role for message not found - skipping")
|
||||
return
|
||||
|
||||
rules: List[
|
||||
AutoRoleRule
|
||||
] = self._auto_roles.get_auto_role_rules_by_auto_role_id(auto_role.id)
|
||||
if rules.count() == 0:
|
||||
self._logger.debug(__name__, f"auto-role rules not found - skipping")
|
||||
return
|
||||
|
||||
for rule in rules:
|
||||
if emoji != rule.emoji_name:
|
||||
continue
|
||||
|
||||
if r_type == "add":
|
||||
role = guild.get_role(rule.role_id)
|
||||
self._logger.debug(__name__, f"Assign role {role.name} to {user.name}")
|
||||
await user.add_roles(role)
|
||||
elif r_type == "remove":
|
||||
role = guild.get_role(rule.role_id)
|
||||
self._logger.debug(__name__, f"Remove role {role.name} to {user.name}")
|
||||
await user.remove_roles(role)
|
||||
else:
|
||||
self._logger.warn(__name__, f"Invalid reaction type {r_type}")
|
26
bot/src/modules/base/__init__.py
Normal file
26
bot/src/modules/base/__init__.py
Normal 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.base"
|
||||
__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")
|
44
bot/src/modules/base/base.json
Normal file
44
bot/src/modules/base/base.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"ProjectSettings": {
|
||||
"Name": "base",
|
||||
"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.4",
|
||||
"PythonPath": {},
|
||||
"Classifiers": []
|
||||
},
|
||||
"BuildSettings": {
|
||||
"ProjectType": "library",
|
||||
"SourcePath": "",
|
||||
"OutputPath": "../../dist",
|
||||
"Main": "base.main",
|
||||
"EntryPoint": "base",
|
||||
"IncludePackageData": false,
|
||||
"Included": [],
|
||||
"Excluded": [
|
||||
"*/__pycache__",
|
||||
"*/logs",
|
||||
"*/tests"
|
||||
],
|
||||
"PackageData": {},
|
||||
"ProjectReferences": []
|
||||
}
|
||||
}
|
133
bot/src/modules/base/base_module.py
Normal file
133
bot/src/modules/base/base_module.py
Normal file
@@ -0,0 +1,133 @@
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.dependency_injection import ServiceCollectionABC
|
||||
from cpl_core.environment import ApplicationEnvironmentABC
|
||||
from cpl_discord.discord_event_types_enum import DiscordEventTypesEnum
|
||||
from cpl_discord.service.discord_collection_abc import DiscordCollectionABC
|
||||
|
||||
from bot_core.abc.module_abc import ModuleABC
|
||||
from bot_core.abc.task_abc import TaskABC
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from modules.base.birthday_watcher import BirthdayWatcher
|
||||
from modules.base.command.afk_command import AFKCommand
|
||||
from modules.base.command.game_server_group import GameServerGroup
|
||||
from modules.base.command.help_command import HelpCommand
|
||||
from modules.base.command.info_command import InfoCommand
|
||||
from modules.base.command.make_coffee_command import MakeCoffeeCommand
|
||||
from modules.base.command.mass_move_command import MassMoveCommand
|
||||
from modules.base.command.ping_command import PingCommand
|
||||
from modules.base.command.presence_command import PresenceCommand
|
||||
from modules.base.command.purge_command import PurgeCommand
|
||||
from modules.base.command.register_group import RegisterGroup
|
||||
from modules.base.command.submit_group import SubmitGroup
|
||||
from modules.base.command.unregister_group import UnregisterGroup
|
||||
from modules.base.command.user_group import UserGroup
|
||||
from modules.base.events.base_on_command_error_event import BaseOnCommandErrorEvent
|
||||
from modules.base.events.base_on_command_event import BaseOnCommandEvent
|
||||
from modules.base.events.base_on_guild_join_event import BaseOnGuildJoinEvent
|
||||
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_delete_event import BaseOnMessageDeleteEvent
|
||||
from modules.base.events.base_on_message_event import BaseOnMessageEvent
|
||||
from modules.base.events.base_on_raw_reaction_add import BaseOnRawReactionAddEvent
|
||||
from modules.base.events.base_on_raw_reaction_remove import BaseOnRawReactionRemoveEvent
|
||||
from modules.base.events.base_on_scheduled_event_update_event import (
|
||||
BaseOnScheduledEventUpdateEvent,
|
||||
)
|
||||
from modules.base.events.base_on_voice_state_update_event import (
|
||||
BaseOnVoiceStateUpdateEvent,
|
||||
)
|
||||
from modules.base.events.base_on_voice_state_update_event_help_channel import (
|
||||
BaseOnVoiceStateUpdateEventHelpChannel,
|
||||
)
|
||||
from modules.base.events.base_on_voice_state_update_event_scheduled_event_bonus import (
|
||||
BaseOnVoiceStateUpdateEventScheduledEventBonus,
|
||||
)
|
||||
from modules.base.forms.bug_report_form import BugReportForm
|
||||
from modules.base.forms.complaint_form import ComplaintForm
|
||||
from modules.base.helper.base_reaction_handler import BaseReactionHandler
|
||||
from modules.base.service.event_service import EventService
|
||||
from modules.base.service.user_warnings_service import UserWarningsService
|
||||
|
||||
|
||||
class BaseModule(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(BaseReactionHandler)
|
||||
services.add_singleton(EventService)
|
||||
services.add_transient(UserWarningsService)
|
||||
services.add_singleton(TaskABC, BirthdayWatcher)
|
||||
|
||||
# forms
|
||||
services.add_transient(BugReportForm)
|
||||
services.add_transient(ComplaintForm)
|
||||
|
||||
# commands
|
||||
services.add_transient(AFKCommand)
|
||||
services.add_transient(SubmitGroup)
|
||||
services.add_transient(HelpCommand)
|
||||
services.add_transient(InfoCommand)
|
||||
services.add_transient(MassMoveCommand)
|
||||
services.add_transient(PingCommand)
|
||||
services.add_transient(PresenceCommand)
|
||||
services.add_transient(MakeCoffeeCommand)
|
||||
|
||||
services.add_transient(PurgeCommand)
|
||||
services.add_transient(UserGroup)
|
||||
services.add_transient(RegisterGroup)
|
||||
services.add_transient(UnregisterGroup)
|
||||
services.add_transient(GameServerGroup)
|
||||
# events
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_command.value, BaseOnCommandEvent
|
||||
)
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_command_error.value, BaseOnCommandErrorEvent
|
||||
)
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_member_join.value, BaseOnMemberJoinEvent
|
||||
)
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_member_remove.value, BaseOnMemberRemoveEvent
|
||||
)
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_message.value, BaseOnMessageEvent
|
||||
)
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_message_delete.value, BaseOnMessageDeleteEvent
|
||||
)
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_raw_reaction_add.value, BaseOnRawReactionAddEvent
|
||||
)
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_raw_reaction_remove.value,
|
||||
BaseOnRawReactionRemoveEvent,
|
||||
)
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_voice_state_update.value,
|
||||
BaseOnVoiceStateUpdateEvent,
|
||||
)
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_voice_state_update.value,
|
||||
BaseOnVoiceStateUpdateEventHelpChannel,
|
||||
)
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_voice_state_update.value,
|
||||
BaseOnVoiceStateUpdateEventScheduledEventBonus,
|
||||
)
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_scheduled_event_update.value,
|
||||
BaseOnScheduledEventUpdateEvent,
|
||||
)
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_guild_join.value,
|
||||
BaseOnGuildJoinEvent,
|
||||
)
|
71
bot/src/modules/base/birthday_watcher.py
Normal file
71
bot/src/modules/base/birthday_watcher.py
Normal file
@@ -0,0 +1,71 @@
|
||||
import datetime
|
||||
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.database.context import DatabaseContextABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from cpl_translation import TranslatePipe
|
||||
from discord.ext import tasks
|
||||
|
||||
from bot_core.abc.task_abc import TaskABC
|
||||
from bot_core.logging.task_logger import TaskLogger
|
||||
from bot_core.service.message_service import MessageService
|
||||
from bot_data.abc.user_repository_abc import UserRepositoryABC
|
||||
from bot_data.model.server_config import ServerConfig
|
||||
|
||||
|
||||
class BirthdayWatcher(TaskABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: TaskLogger,
|
||||
bot: DiscordBotServiceABC,
|
||||
db: DatabaseContextABC,
|
||||
users: UserRepositoryABC,
|
||||
message_service: MessageService,
|
||||
t: TranslatePipe,
|
||||
):
|
||||
TaskABC.__init__(self)
|
||||
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._db = db
|
||||
self._users = users
|
||||
self._message_service = message_service
|
||||
self._t = t
|
||||
|
||||
self.watch.start()
|
||||
|
||||
@tasks.loop(time=datetime.time(hour=8, minute=0))
|
||||
async def watch(self):
|
||||
self._logger.info(__name__, "Watching birthdays")
|
||||
try:
|
||||
today = datetime.date.today()
|
||||
users = self._users.get_users().where(lambda x: x.birthday is not None)
|
||||
for user in users:
|
||||
if user.birthday.day != today.day or user.birthday.month != today.month:
|
||||
continue
|
||||
|
||||
settings: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{user.server.discord_id}"
|
||||
)
|
||||
|
||||
user.xp += settings.xp_for_birthday
|
||||
self._users.update_user(user)
|
||||
self._db.save_changes()
|
||||
|
||||
guild = self._bot.get_guild(user.server.discord_id)
|
||||
member = guild.get_member(user.discord_id)
|
||||
await self._message_service.send_channel_message(
|
||||
self._bot.get_channel(settings.notification_chat_id),
|
||||
self._t.transform("modules.base.user.birthday.has_birthday").format(
|
||||
member.mention
|
||||
),
|
||||
is_persistent=True,
|
||||
)
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Watching birthdays failed", e)
|
||||
|
||||
@watch.before_loop
|
||||
async def wait(self):
|
||||
await self._wait_until_ready()
|
26
bot/src/modules/base/command/__init__.py
Normal file
26
bot/src/modules/base/command/__init__.py
Normal 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.base.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")
|
73
bot/src/modules/base/command/afk_command.py
Normal file
73
bot/src/modules/base/command/afk_command.py
Normal file
@@ -0,0 +1,73 @@
|
||||
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 import VoiceChannel
|
||||
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_data.model.server_config import ServerConfig
|
||||
|
||||
|
||||
class AFKCommand(DiscordCommandABC):
|
||||
def __init__(
|
||||
self,
|
||||
logger: CommandLogger,
|
||||
config: ConfigurationABC,
|
||||
message_service: MessageServiceABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
client_utils: ClientUtilsABC,
|
||||
translate: TranslatePipe,
|
||||
):
|
||||
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._logger.trace(__name__, f"Loaded command service: {type(self).__name__}")
|
||||
|
||||
@commands.hybrid_command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
async def afk(self, ctx: Context):
|
||||
self._logger.debug(__name__, f"Received command afk {ctx}")
|
||||
settings: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{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")
|
275
bot/src/modules/base/command/game_server_group.py
Normal file
275
bot/src/modules/base/command/game_server_group.py
Normal file
@@ -0,0 +1,275 @@
|
||||
from typing import List as TList
|
||||
|
||||
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.api_key_repository_abc import ApiKeyRepositoryABC
|
||||
from bot_data.abc.game_server_repository_abc import GameServerRepositoryABC
|
||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
from bot_data.abc.user_game_ident_repository_abc import UserGameIdentRepositoryABC
|
||||
from bot_data.abc.user_repository_abc import UserRepositoryABC
|
||||
from bot_data.model.game_server import GameServer
|
||||
from bot_data.model.server_config import ServerConfig
|
||||
from modules.permission.abc.permission_service_abc import PermissionServiceABC
|
||||
|
||||
|
||||
class GameServerGroup(DiscordCommandABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: CommandLogger,
|
||||
message_service: MessageServiceABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
client_utils: ClientUtilsABC,
|
||||
translate: TranslatePipe,
|
||||
servers: ServerRepositoryABC,
|
||||
user: UserRepositoryABC,
|
||||
game_servers: GameServerRepositoryABC,
|
||||
user_game_idents: UserGameIdentRepositoryABC,
|
||||
api_keys: ApiKeyRepositoryABC,
|
||||
db: DatabaseContextABC,
|
||||
permission_service: PermissionServiceABC,
|
||||
):
|
||||
DiscordCommandABC.__init__(self)
|
||||
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._message_service = message_service
|
||||
self._bot = bot
|
||||
self._client_utils = client_utils
|
||||
self._t = translate
|
||||
self._servers = servers
|
||||
self._user = user
|
||||
self._game_servers = game_servers
|
||||
self._user_game_idents = user_game_idents
|
||||
self._api_keys = api_keys
|
||||
self._db = db
|
||||
self._permissions = permission_service
|
||||
|
||||
self._logger.trace(__name__, f"Loaded command service: {type(self).__name__}")
|
||||
|
||||
@commands.hybrid_group(name="game-server")
|
||||
@commands.guild_only()
|
||||
async def game_server(self, ctx: Context):
|
||||
pass
|
||||
|
||||
@game_server.command(alias="game-servers")
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def list(self, ctx: Context, wait: int = None):
|
||||
self._logger.debug(__name__, f"Received command game_server list {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.game_server
|
||||
):
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("common.feature_not_activated")
|
||||
)
|
||||
return
|
||||
|
||||
server = self._servers.get_server_by_discord_id(ctx.guild.id)
|
||||
game_servers = self._game_servers.get_game_servers_by_server_id(server.id)
|
||||
if game_servers.count() < 1:
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.base.game_server.error.nothing_found")
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command game_server list")
|
||||
return
|
||||
|
||||
game_server_name = ""
|
||||
api_key = ""
|
||||
for game_server in game_servers:
|
||||
game_server: GameServer = game_server
|
||||
game_server_name += f"\n{game_server.name}"
|
||||
api_key += f"\n{game_server.api_key.identifier}"
|
||||
|
||||
embed = discord.Embed(
|
||||
title=self._t.transform("modules.base.game_server.list.title"),
|
||||
description=self._t.transform("modules.base.game_server.list.description"),
|
||||
color=int("ef9d0d", 16),
|
||||
)
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.game_server.list.name"),
|
||||
value=game_server_name,
|
||||
inline=True,
|
||||
)
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.game_server.list.api_key"),
|
||||
value=api_key,
|
||||
inline=True,
|
||||
)
|
||||
|
||||
await self._message_service.send_ctx_msg(ctx, embed, wait_before_delete=wait)
|
||||
self._logger.trace(__name__, f"Finished command game_server list")
|
||||
|
||||
@game_server.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_admin()
|
||||
async def add(self, ctx: Context, name: str, api_key_id: int):
|
||||
self._logger.debug(
|
||||
__name__, f"Received command game-server add {ctx}: {name} {api_key_id}"
|
||||
)
|
||||
|
||||
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.game_server
|
||||
):
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("common.feature_not_activated")
|
||||
)
|
||||
return
|
||||
|
||||
server = self._servers.get_server_by_discord_id(ctx.guild.id)
|
||||
api_key = self._api_keys.get_api_key_by_id(api_key_id)
|
||||
game_server = GameServer(name, server, api_key)
|
||||
|
||||
self._game_servers.add_game_server(game_server)
|
||||
self._db.save_changes()
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.base.game_server.add.success").format(name),
|
||||
)
|
||||
|
||||
self._logger.trace(__name__, f"Finished command game-server add")
|
||||
|
||||
@add.autocomplete("api_key_id")
|
||||
async def api_key_id_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.id)
|
||||
for key in self._client_utils.get_auto_complete_list(
|
||||
keys, current, lambda x: x.key
|
||||
)
|
||||
]
|
||||
|
||||
@game_server.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_admin()
|
||||
async def remove(self, ctx: Context, id: int):
|
||||
self._logger.debug(__name__, f"Received command game-server remove {ctx}: {id}")
|
||||
|
||||
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.game_server
|
||||
):
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("common.feature_not_activated")
|
||||
)
|
||||
return
|
||||
|
||||
game_server = self._game_servers.get_game_server_by_id(id)
|
||||
self._game_servers.delete_game_server(game_server)
|
||||
self._db.save_changes()
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.base.game_server.remove.success")
|
||||
)
|
||||
|
||||
self._logger.trace(__name__, f"Finished command game-server remove")
|
||||
|
||||
@remove.autocomplete("id")
|
||||
async def id_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
server = self._servers.get_server_by_discord_id(interaction.guild.id)
|
||||
game_servers = self._game_servers.get_game_servers_by_server_id(server.id)
|
||||
|
||||
return [
|
||||
app_commands.Choice(name=gs.name, value=gs.id)
|
||||
for gs in self._client_utils.get_auto_complete_list(
|
||||
game_servers, current, lambda x: x.name
|
||||
)
|
||||
]
|
||||
|
||||
@game_server.command(name="list-members")
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def list_members(self, ctx: Context, id: int, wait: int = None):
|
||||
self._logger.debug(__name__, f"Received command game-server remove {ctx}: {id}")
|
||||
|
||||
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.game_server
|
||||
):
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("common.feature_not_activated")
|
||||
)
|
||||
return
|
||||
|
||||
game_server = self._game_servers.get_game_server_by_id(id)
|
||||
game_idents = self._user_game_idents.get_user_game_idents_by_game_server_id(
|
||||
game_server.id
|
||||
)
|
||||
users = ""
|
||||
for game_ident in game_idents:
|
||||
users += f"\n{game_ident.user.name}"
|
||||
|
||||
embed = discord.Embed(
|
||||
title=self._t.transform("modules.base.game_server.list_members.title"),
|
||||
description=self._t.transform(
|
||||
"modules.base.game_server.list_members.description"
|
||||
),
|
||||
color=int("ef9d0d", 16),
|
||||
)
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.game_server.list_members.users"),
|
||||
value=users,
|
||||
inline=True,
|
||||
)
|
||||
await self._message_service.send_ctx_msg(ctx, embed, wait_before_delete=wait)
|
||||
|
||||
self._logger.trace(__name__, f"Finished command game-server remove")
|
||||
|
||||
@list_members.autocomplete("id")
|
||||
async def id_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
server = self._servers.get_server_by_discord_id(interaction.guild.id)
|
||||
game_servers = self._game_servers.get_game_servers_by_server_id(server.id)
|
||||
|
||||
return [
|
||||
app_commands.Choice(name=gs.name, value=gs.id)
|
||||
for gs in self._client_utils.get_auto_complete_list(
|
||||
game_servers, current, lambda x: x.name
|
||||
)
|
||||
]
|
57
bot/src/modules/base/command/help_command.py
Normal file
57
bot/src/modules/base/command/help_command.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from typing import List
|
||||
|
||||
import discord
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_discord.command import DiscordCommandABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
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.helper.command_checks import CommandChecks
|
||||
from bot_core.logging.command_logger import CommandLogger
|
||||
from bot_data.model.technician_config import TechnicianConfig
|
||||
|
||||
|
||||
class HelpCommand(DiscordCommandABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: CommandLogger,
|
||||
message_service: MessageServiceABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
client_utils: ClientUtilsABC,
|
||||
):
|
||||
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.hybrid_command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
async def help(self, ctx: Context, persistent_flag: str = None):
|
||||
self._logger.debug(__name__, f"Received command help {ctx}:{persistent_flag}")
|
||||
settings: TechnicianConfig = self._config.get_configuration(TechnicianConfig)
|
||||
is_persistent = persistent_flag == "--stay"
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
settings.help_command_reference_url,
|
||||
is_persistent=is_persistent,
|
||||
is_public=True,
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished help command")
|
||||
|
||||
@help.autocomplete("persistent_flag")
|
||||
async def help_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> List[app_commands.Choice[str]]:
|
||||
flags = ["--stay"]
|
||||
return [app_commands.Choice(name=key, value=key) for key in flags]
|
101
bot/src/modules/base/command/info_command.py
Normal file
101
bot/src/modules/base/command/info_command.py
Normal file
@@ -0,0 +1,101 @@
|
||||
from datetime import datetime
|
||||
|
||||
import discord
|
||||
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
|
||||
|
||||
import bot
|
||||
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
|
||||
|
||||
|
||||
class InfoCommand(DiscordCommandABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: CommandLogger,
|
||||
message_service: MessageServiceABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
client_utils: ClientUtilsABC,
|
||||
translate: TranslatePipe,
|
||||
):
|
||||
DiscordCommandABC.__init__(self)
|
||||
|
||||
self._config = config
|
||||
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.hybrid_command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
async def info(self, ctx: Context, *, wait: int = None):
|
||||
self._logger.debug(__name__, f"Received command info {ctx},{wait}")
|
||||
client = self._client_utils.get_client(self._bot.user.id, ctx.guild.id)
|
||||
|
||||
embed = discord.Embed(
|
||||
title=self._t.transform("modules.base.info.title"),
|
||||
description=self._t.transform("modules.base.info.description"),
|
||||
color=int("ef9d0d", 16),
|
||||
)
|
||||
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.info.fields.version"),
|
||||
value=bot.__version__,
|
||||
)
|
||||
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,
|
||||
)
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.info.fields.ontime"),
|
||||
value=f"{ontime}h",
|
||||
)
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.info.fields.sent_message_count"),
|
||||
value=client.sent_message_count,
|
||||
inline=False,
|
||||
)
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.info.fields.received_message_count"),
|
||||
value=client.received_message_count,
|
||||
)
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.info.fields.deleted_message_count"),
|
||||
value=client.deleted_message_count,
|
||||
inline=False,
|
||||
)
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.info.fields.received_command_count"),
|
||||
value=client.received_command_count,
|
||||
)
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.info.fields.moved_users_count"),
|
||||
value=client.moved_users_count,
|
||||
)
|
||||
from bot.module_list import ModuleList
|
||||
|
||||
modules = ModuleList.get_modules()
|
||||
modules = modules.select(lambda x: x.__name__.replace("Module", ""))
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.info.fields.modules"),
|
||||
value="\n".join(modules),
|
||||
inline=False,
|
||||
)
|
||||
|
||||
await self._message_service.send_ctx_msg(ctx, embed, wait_before_delete=wait)
|
||||
self._logger.trace(__name__, f"Finished info command")
|
17
bot/src/modules/base/command/make_coffee_command.py
Normal file
17
bot/src/modules/base/command/make_coffee_command.py
Normal file
@@ -0,0 +1,17 @@
|
||||
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
|
||||
|
||||
|
||||
class MakeCoffeeCommand(DiscordCommandABC):
|
||||
def __init__(self, logger: LoggerABC, bot: DiscordBotServiceABC):
|
||||
DiscordCommandABC.__init__(self)
|
||||
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
|
||||
@commands.hybrid_command(name="make-coffee")
|
||||
async def make_coffee(self, ctx: Context):
|
||||
await ctx.send("https://media.giphy.com/media/M4ecx9P2jI4tq/giphy.gif")
|
66
bot/src/modules/base/command/mass_move_command.py
Normal file
66
bot/src/modules/base/command/mass_move_command.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import asyncio
|
||||
|
||||
import discord
|
||||
from cpl_discord.command import DiscordCommandABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from cpl_translation import TranslatePipe
|
||||
from discord.app_commands import Transform
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
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.client_utils_service import ClientUtilsService
|
||||
from modules.base.helper.voice_channel_transformer import VoiceChannelTransformer
|
||||
|
||||
|
||||
class MassMoveCommand(DiscordCommandABC):
|
||||
def __init__(
|
||||
self,
|
||||
logger: CommandLogger,
|
||||
message_service: MessageServiceABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
translate: TranslatePipe,
|
||||
client_utils: ClientUtilsService,
|
||||
):
|
||||
DiscordCommandABC.__init__(self)
|
||||
self._logger = logger
|
||||
self._message_service = message_service
|
||||
self._bot = bot
|
||||
self._t = translate
|
||||
self._client_utils = client_utils
|
||||
|
||||
@commands.hybrid_command(name="mass-move")
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def mass_move(
|
||||
self,
|
||||
ctx: Context,
|
||||
channel_to: discord.VoiceChannel,
|
||||
channel_from: Transform[str, VoiceChannelTransformer] = None,
|
||||
):
|
||||
self._logger.debug(__name__, f"Received command mass-move {ctx}")
|
||||
|
||||
if channel_from is None and ctx.author.voice is None:
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.base.mass_move.channel_from_error")
|
||||
)
|
||||
return
|
||||
|
||||
if channel_from is None:
|
||||
channel_from = ctx.author.voice.channel
|
||||
|
||||
moves = [member.move_to(channel_to) for member in channel_from.members]
|
||||
move_count = len(moves)
|
||||
await asyncio.gather(*moves)
|
||||
self._client_utils.moved_users(ctx.guild.id, move_count)
|
||||
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.base.mass_move.moved").format(
|
||||
channel_from.mention, channel_to.mention
|
||||
),
|
||||
)
|
||||
|
||||
self._logger.trace(__name__, f"Finished mass-move command")
|
69
bot/src/modules/base/command/ping_command.py
Normal file
69
bot/src/modules/base/command/ping_command.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import discord
|
||||
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_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
from bot_data.model.technician_config import TechnicianConfig
|
||||
from modules.permission.abc.permission_service_abc import PermissionServiceABC
|
||||
|
||||
|
||||
class PingCommand(DiscordCommandABC):
|
||||
def __init__(
|
||||
self,
|
||||
logger: CommandLogger,
|
||||
message_service: MessageServiceABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
client_utils: ClientUtilsABC,
|
||||
translate: TranslatePipe,
|
||||
permissions: PermissionServiceABC,
|
||||
servers: ServerRepositoryABC,
|
||||
settings: TechnicianConfig,
|
||||
):
|
||||
DiscordCommandABC.__init__(self)
|
||||
|
||||
self._logger = logger
|
||||
self._message_service = message_service
|
||||
self._bot = bot
|
||||
self._client_utils = client_utils
|
||||
self._t = translate
|
||||
self._permissions = permissions
|
||||
self._servers = servers
|
||||
self._settings = settings
|
||||
|
||||
self._logger.trace(__name__, f"Loaded command service: {type(self).__name__}")
|
||||
|
||||
@staticmethod
|
||||
def _get_ping(url: str) -> float:
|
||||
from icmplib import ping
|
||||
|
||||
ping_result = ping(url, count=4, interval=0.2, privileged=False)
|
||||
return ping_result.avg_rtt
|
||||
|
||||
@commands.hybrid_command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
async def ping(self, ctx: Context):
|
||||
self._logger.debug(__name__, f"Received command ping {ctx}")
|
||||
if self._permissions.is_member_technician(ctx.author):
|
||||
embed = discord.Embed(
|
||||
title=self._t.transform("modules.base.info.title"),
|
||||
description=self._t.transform("modules.base.info.description"),
|
||||
color=int("ef9d0d", 16),
|
||||
)
|
||||
for server in self._settings.ping_urls:
|
||||
embed.add_field(
|
||||
name=server, value=f"{self._get_ping(server)} ms", inline=False
|
||||
)
|
||||
await self._message_service.send_ctx_msg(ctx, embed)
|
||||
else:
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.base.pong")
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished ping command")
|
53
bot/src/modules/base/command/presence_command.py
Normal file
53
bot/src/modules/base/command/presence_command.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import discord
|
||||
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.message_service_abc import MessageServiceABC
|
||||
from bot_core.helper.command_checks import CommandChecks
|
||||
from bot_core.logging.command_logger import CommandLogger
|
||||
|
||||
|
||||
class PresenceCommand(DiscordCommandABC):
|
||||
def __init__(
|
||||
self,
|
||||
logger: CommandLogger,
|
||||
message_service: MessageServiceABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
translate: TranslatePipe,
|
||||
):
|
||||
DiscordCommandABC.__init__(self)
|
||||
|
||||
self._logger = logger
|
||||
self._message_service = message_service
|
||||
self._bot = bot
|
||||
self._t = translate
|
||||
|
||||
@commands.hybrid_command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def presence(self, ctx: Context, text: str = ""):
|
||||
self._logger.debug(__name__, f"Received command presence {ctx}")
|
||||
|
||||
if text == "":
|
||||
await self._bot.change_presence(activity=None)
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.base.presence.removed")
|
||||
)
|
||||
return
|
||||
|
||||
if len(text) > 128:
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.base.presence.max_char_count_exceeded")
|
||||
)
|
||||
return
|
||||
|
||||
await self._bot.change_presence(activity=discord.Game(name=text))
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.base.presence.changed")
|
||||
)
|
||||
|
||||
self._logger.trace(__name__, f"Finished presence command")
|
60
bot/src/modules/base/command/purge_command.py
Normal file
60
bot/src/modules/base/command/purge_command.py
Normal file
@@ -0,0 +1,60 @@
|
||||
import asyncio
|
||||
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
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_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.model.server_config import ServerConfig
|
||||
from modules.permission.abc.permission_service_abc import PermissionServiceABC
|
||||
|
||||
|
||||
class PurgeCommand(DiscordCommandABC):
|
||||
def __init__(
|
||||
self,
|
||||
logger: CommandLogger,
|
||||
config: ConfigurationABC,
|
||||
message_service: MessageServiceABC,
|
||||
permissions: PermissionServiceABC,
|
||||
client_utils: ClientUtilsABC,
|
||||
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.hybrid_command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def purge(self, ctx: Context):
|
||||
self._logger.debug(__name__, f"Received command purge {ctx}")
|
||||
server_settings: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{ctx.guild.id}"
|
||||
)
|
||||
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.moderator.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")
|
116
bot/src/modules/base/command/register_group.py
Normal file
116
bot/src/modules/base/command/register_group.py
Normal file
@@ -0,0 +1,116 @@
|
||||
from typing import List as TList
|
||||
|
||||
import discord
|
||||
import requests
|
||||
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.helper.command_checks import CommandChecks
|
||||
from bot_core.logging.command_logger import CommandLogger
|
||||
from bot_data.abc.game_server_repository_abc import GameServerRepositoryABC
|
||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
from bot_data.abc.user_game_ident_repository_abc import UserGameIdentRepositoryABC
|
||||
from bot_data.abc.user_repository_abc import UserRepositoryABC
|
||||
from bot_data.model.user_game_ident import UserGameIdent
|
||||
|
||||
|
||||
class RegisterGroup(DiscordCommandABC):
|
||||
def __init__(
|
||||
self,
|
||||
logger: CommandLogger,
|
||||
message_service: MessageServiceABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
client_utils: ClientUtilsABC,
|
||||
servers: ServerRepositoryABC,
|
||||
users: UserRepositoryABC,
|
||||
game_server: GameServerRepositoryABC,
|
||||
user_game_ident: UserGameIdentRepositoryABC,
|
||||
db: DatabaseContextABC,
|
||||
t: TranslatePipe,
|
||||
):
|
||||
DiscordCommandABC.__init__(self)
|
||||
|
||||
self._logger = logger
|
||||
self._message_service = message_service
|
||||
self._bot = bot
|
||||
self._client_utils = client_utils
|
||||
self._servers = servers
|
||||
self._users = users
|
||||
self._game_server = game_server
|
||||
self._user_game_ident = user_game_ident
|
||||
self._db = db
|
||||
self._t = t
|
||||
|
||||
self._logger.trace(__name__, f"Loaded command service: {type(self).__name__}")
|
||||
|
||||
@commands.hybrid_group()
|
||||
@commands.guild_only()
|
||||
async def register(self, ctx: Context):
|
||||
pass
|
||||
|
||||
@register.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def minecraft(
|
||||
self, ctx: Context, member: discord.Member, game_server: int, name: str
|
||||
):
|
||||
self._logger.debug(__name__, f"Received command register minecraft {ctx}")
|
||||
|
||||
minecraft_id = None
|
||||
try:
|
||||
self._logger.debug(__name__, f"Try to get minecraft id for {name}")
|
||||
response = requests.get(
|
||||
url=f"https://api.mojang.com/users/profiles/minecraft/{name}"
|
||||
)
|
||||
if len(response.content) == 0:
|
||||
await self._message_service.send_interaction_msg(
|
||||
ctx.interaction,
|
||||
self._t.transform("modules.base.register.not_found"),
|
||||
)
|
||||
return
|
||||
|
||||
minecraft_id = response.json()["id"]
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Get minecraft id for {name} failed", e)
|
||||
await self._message_service.send_interaction_msg(
|
||||
ctx.interaction, self._t.transform("modules.base.register.not_found")
|
||||
)
|
||||
|
||||
if minecraft_id is None:
|
||||
return
|
||||
|
||||
server = self._servers.get_server_by_discord_id(ctx.guild.id)
|
||||
user = self._users.get_user_by_discord_id_and_server_id(member.id, server.id)
|
||||
gs = self._game_server.get_game_server_by_id(game_server)
|
||||
|
||||
game_ident = UserGameIdent(user, gs, minecraft_id)
|
||||
self._user_game_ident.add_user_game_ident(game_ident)
|
||||
self._db.save_changes()
|
||||
|
||||
await self._message_service.send_interaction_msg(
|
||||
ctx.interaction, self._t.transform("modules.base.register.success")
|
||||
)
|
||||
|
||||
self._logger.trace(__name__, f"Finished register minecraft command")
|
||||
|
||||
@minecraft.autocomplete("game_server")
|
||||
async def game_server_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
server = self._servers.get_server_by_discord_id(interaction.guild.id)
|
||||
game_servers = self._game_server.get_game_servers_by_server_id(server.id)
|
||||
|
||||
return [
|
||||
app_commands.Choice(name=gs.name, value=gs.id)
|
||||
for gs in self._client_utils.get_auto_complete_list(
|
||||
game_servers, current, lambda x: x.name
|
||||
)
|
||||
]
|
44
bot/src/modules/base/command/submit_group.py
Normal file
44
bot/src/modules/base/command/submit_group.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from cpl_core.dependency_injection import ServiceProviderABC
|
||||
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.helper.command_checks import CommandChecks
|
||||
from modules.base.forms.bug_report_form import BugReportForm
|
||||
from modules.base.forms.complaint_form import ComplaintForm
|
||||
|
||||
|
||||
class SubmitGroup(DiscordCommandABC):
|
||||
def __init__(
|
||||
self, services: ServiceProviderABC, logger: LoggerABC, bot: DiscordBotServiceABC
|
||||
):
|
||||
DiscordCommandABC.__init__(self)
|
||||
|
||||
self._services = services
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
|
||||
@commands.hybrid_group()
|
||||
@commands.guild_only()
|
||||
async def submit(self, ctx: Context):
|
||||
pass
|
||||
|
||||
@submit.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
async def complaint(self, ctx: Context):
|
||||
self._logger.debug(__name__, f"Received command complaint {ctx}")
|
||||
complaint_form: ComplaintForm = self._services.get_service(ComplaintForm)
|
||||
await ctx.interaction.response.send_modal(complaint_form)
|
||||
self._logger.trace(__name__, f"Finished command complaint {ctx}")
|
||||
|
||||
@submit.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
async def bug_report(self, ctx: Context):
|
||||
self._logger.debug(__name__, f"Received command complaint {ctx}")
|
||||
bug_form: BugReportForm = self._services.get_service(BugReportForm)
|
||||
await ctx.interaction.response.send_modal(bug_form)
|
||||
self._logger.trace(__name__, f"Finished command complaint {ctx}")
|
90
bot/src/modules/base/command/unregister_group.py
Normal file
90
bot/src/modules/base/command/unregister_group.py
Normal file
@@ -0,0 +1,90 @@
|
||||
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_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.game_server_repository_abc import GameServerRepositoryABC
|
||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
from bot_data.abc.user_game_ident_repository_abc import UserGameIdentRepositoryABC
|
||||
from bot_data.abc.user_repository_abc import UserRepositoryABC
|
||||
|
||||
|
||||
class UnregisterGroup(DiscordCommandABC):
|
||||
def __init__(
|
||||
self,
|
||||
logger: CommandLogger,
|
||||
message_service: MessageServiceABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
client_utils: ClientUtilsABC,
|
||||
servers: ServerRepositoryABC,
|
||||
users: UserRepositoryABC,
|
||||
game_server: GameServerRepositoryABC,
|
||||
user_game_idents: UserGameIdentRepositoryABC,
|
||||
db: DatabaseContextABC,
|
||||
t: TranslatePipe,
|
||||
):
|
||||
DiscordCommandABC.__init__(self)
|
||||
|
||||
self._logger = logger
|
||||
self._message_service = message_service
|
||||
self._bot = bot
|
||||
self._client_utils = client_utils
|
||||
self._servers = servers
|
||||
self._users = users
|
||||
self._game_server = game_server
|
||||
self._user_game_idents = user_game_idents
|
||||
self._db = db
|
||||
self._t = t
|
||||
|
||||
self._logger.trace(__name__, f"Loaded command service: {type(self).__name__}")
|
||||
|
||||
# @commands.hybrid_group()
|
||||
# @commands.guild_only()
|
||||
# async def unregister(self, ctx: Context):
|
||||
# pass
|
||||
|
||||
@commands.hybrid_command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def unregister(self, ctx: Context, member: discord.Member, game_server: int):
|
||||
self._logger.debug(__name__, f"Received command unregister {ctx}")
|
||||
|
||||
server = self._servers.get_server_by_discord_id(ctx.guild.id)
|
||||
user = self._users.get_user_by_discord_id_and_server_id(member.id, server.id)
|
||||
ident = user.game_idents.where(
|
||||
lambda x: x.game_server.id == game_server
|
||||
).single()
|
||||
self._user_game_idents.delete_user_game_ident(ident)
|
||||
self._users.update_user(user)
|
||||
self._db.save_changes()
|
||||
|
||||
await self._message_service.send_interaction_msg(
|
||||
ctx.interaction, self._t.transform("modules.base.unregister.success")
|
||||
)
|
||||
|
||||
self._logger.trace(__name__, f"Finished unregister command")
|
||||
|
||||
@unregister.autocomplete("game_server")
|
||||
async def game_server_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
server = self._servers.get_server_by_discord_id(interaction.guild.id)
|
||||
game_servers = self._game_server.get_game_servers_by_server_id(server.id)
|
||||
|
||||
return [
|
||||
app_commands.Choice(name=gs.name, value=gs.id)
|
||||
for gs in self._client_utils.get_auto_complete_list(
|
||||
game_servers, current, lambda x: x.name
|
||||
)
|
||||
]
|
563
bot/src/modules/base/command/user_group.py
Normal file
563
bot/src/modules/base/command/user_group.py
Normal file
@@ -0,0 +1,563 @@
|
||||
import datetime
|
||||
from typing import Optional, List as TList
|
||||
|
||||
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_query.extension import List
|
||||
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.helper.command_checks import CommandChecks
|
||||
from bot_core.logging.command_logger import CommandLogger
|
||||
from bot_core.pipes.date_time_offset_pipe import DateTimeOffsetPipe
|
||||
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_repository_abc import (
|
||||
UserJoinedVoiceChannelRepositoryABC,
|
||||
)
|
||||
from bot_data.abc.user_repository_abc import UserRepositoryABC
|
||||
from bot_data.abc.user_warnings_repository_abc import UserWarningsRepositoryABC
|
||||
from bot_data.model.server_config import ServerConfig
|
||||
from modules.base.service.user_warnings_service import UserWarningsService
|
||||
from modules.level.service.level_service import LevelService
|
||||
from modules.permission.abc.permission_service_abc import PermissionServiceABC
|
||||
|
||||
|
||||
class UserGroup(DiscordCommandABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: CommandLogger,
|
||||
message_service: MessageServiceABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
client_utils: ClientUtilsABC,
|
||||
permissions: PermissionServiceABC,
|
||||
servers: ServerRepositoryABC,
|
||||
db: DatabaseContextABC,
|
||||
users: UserRepositoryABC,
|
||||
user_joined_servers: UserJoinedServerRepositoryABC,
|
||||
user_joined_voice_channel: UserJoinedVoiceChannelRepositoryABC,
|
||||
translate: TranslatePipe,
|
||||
date: DateTimeOffsetPipe,
|
||||
level: LevelService,
|
||||
user_warnings: UserWarningsRepositoryABC,
|
||||
user_warnings_service: UserWarningsService,
|
||||
):
|
||||
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._db = db
|
||||
self._users = users
|
||||
self._user_joined_servers = user_joined_servers
|
||||
self._user_joined_voice_channel = user_joined_voice_channel
|
||||
self._t = translate
|
||||
self._date = date
|
||||
self._level = level
|
||||
self._user_warnings = user_warnings
|
||||
self._user_warnings_service = user_warnings_service
|
||||
|
||||
self._logger.trace(__name__, f"Loaded command service: {type(self).__name__}")
|
||||
|
||||
self._atr_dict = {
|
||||
"xp": self._t.transform("modules.base.user.atr.xp"),
|
||||
"ontime": self._t.transform("modules.base.user.atr.ontime"),
|
||||
}
|
||||
|
||||
self._atr_TList = [(key, self._atr_dict[key]) for key in self._atr_dict]
|
||||
|
||||
async def _handle_atr_calc(
|
||||
self,
|
||||
ctx: Context,
|
||||
atr: str,
|
||||
value: int,
|
||||
member: discord.Member,
|
||||
is_remove=False,
|
||||
):
|
||||
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.id)
|
||||
|
||||
if atr == "xp":
|
||||
if is_remove:
|
||||
user.xp -= value
|
||||
else:
|
||||
user.xp += value
|
||||
self._users.update_user(user)
|
||||
self._db.save_changes()
|
||||
await self._level.check_level(member)
|
||||
|
||||
else:
|
||||
await self._message_service.send_interaction_msg(
|
||||
ctx.interaction,
|
||||
self._t.transform("modules.base.user.error.atr_not_found").format(atr),
|
||||
)
|
||||
return
|
||||
|
||||
type = "add" if not is_remove else "remove"
|
||||
await self._message_service.send_interaction_msg(
|
||||
ctx.interaction,
|
||||
self._t.transform(f"modules.base.user.{type}.{atr.lower()}").format(
|
||||
atr, value, member.mention
|
||||
),
|
||||
)
|
||||
|
||||
@commands.hybrid_group()
|
||||
@commands.guild_only()
|
||||
async def user(self, ctx: Context):
|
||||
pass
|
||||
|
||||
@user.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
async def birthday(self, ctx: Context, day: int, month: int, year: int):
|
||||
self._logger.debug(
|
||||
__name__,
|
||||
f"Received command user birthday {ctx}:{ctx.author},{day}:{month}:{year}",
|
||||
)
|
||||
date = datetime.date(year, month, day)
|
||||
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
|
||||
)
|
||||
user.birthday = date
|
||||
self._users.update_user(user)
|
||||
self._db.save_changes()
|
||||
await self._message_service.send_interaction_msg(
|
||||
ctx.interaction, self._t.transform("modules.base.user.birthday.success")
|
||||
)
|
||||
# notify team to prevent multiple entries every day
|
||||
settings: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{ctx.guild.id}"
|
||||
)
|
||||
channel = ctx.guild.get_channel(settings.team_channel_id)
|
||||
await self._message_service.send_channel_message(
|
||||
channel,
|
||||
self._t.transform("modules.base.user.birthday.success_team").format(
|
||||
ctx.author.mention, date
|
||||
),
|
||||
is_persistent=True,
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished user-info command")
|
||||
|
||||
@birthday.autocomplete("day")
|
||||
async def birthday_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[int]]:
|
||||
days = List(int, range(0, 32)).select(lambda x: str(x))
|
||||
return [
|
||||
app_commands.Choice(name=date, value=date)
|
||||
for date in self._client_utils.get_auto_complete_list(days, current)
|
||||
]
|
||||
|
||||
@birthday.autocomplete("month")
|
||||
async def birthday_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[int]]:
|
||||
days = List(int, range(0, 13)).select(lambda x: str(x))
|
||||
return [
|
||||
app_commands.Choice(name=date, value=date)
|
||||
for date in self._client_utils.get_auto_complete_list(days, current)
|
||||
]
|
||||
|
||||
@birthday.autocomplete("year")
|
||||
async def birthday_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[int]]:
|
||||
today = datetime.date.today()
|
||||
days = List(int, range(today.year - 75, today.year)).select(lambda x: str(x))
|
||||
return [
|
||||
app_commands.Choice(name=date, value=date)
|
||||
for date in self._client_utils.get_auto_complete_list(days, current)
|
||||
]
|
||||
|
||||
@user.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
async def info(
|
||||
self, ctx: Context, member: Optional[discord.Member] = None, *, wait: int = None
|
||||
):
|
||||
self._logger.debug(
|
||||
__name__, f"Received command user-info {ctx}:{member},{wait}"
|
||||
)
|
||||
|
||||
is_mod = self._permissions.is_member_moderator(ctx.author)
|
||||
if member is not None and not is_mod:
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("common.no_permission_message")
|
||||
)
|
||||
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.id)
|
||||
joins = self._user_joined_servers.get_user_joined_servers_by_user_id(user.id)
|
||||
|
||||
embed = discord.Embed(
|
||||
title=member.name, description=member.name, color=int("ef9d0d", 16)
|
||||
)
|
||||
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.user.atr.id"), value=member.id
|
||||
)
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.user.atr.name"), value=member.name
|
||||
)
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.user.atr.discord_join"),
|
||||
value=self._date.transform(member.created_at),
|
||||
inline=False,
|
||||
)
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.user.atr.last_join"),
|
||||
value=self._date.transform(member.joined_at),
|
||||
inline=False,
|
||||
)
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.user.atr.xp"), value=str(user.xp)
|
||||
)
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.user.atr.ontime"),
|
||||
value=str(self._client_utils.get_ontime_for_user(user)),
|
||||
)
|
||||
|
||||
roles = ""
|
||||
for role in member.roles:
|
||||
roles += f"{role.name}\n"
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.user.atr.roles"),
|
||||
value=roles,
|
||||
inline=False,
|
||||
)
|
||||
|
||||
if is_mod or member == ctx.author:
|
||||
joins_string = ""
|
||||
for join in joins:
|
||||
joins_string += f"{self._date.transform(join.joined_on)}\n"
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.user.atr.joins"),
|
||||
value=joins_string,
|
||||
)
|
||||
|
||||
if is_mod or member == ctx.author:
|
||||
lefts_string = ""
|
||||
for join in joins:
|
||||
if join.leaved_on is None:
|
||||
if lefts_string == "":
|
||||
lefts_string = "/"
|
||||
continue
|
||||
lefts_string += f"{self._date.transform(join.leaved_on)}\n"
|
||||
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.user.atr.lefts"),
|
||||
value=lefts_string,
|
||||
)
|
||||
|
||||
if is_mod or member == ctx.author:
|
||||
warnings_string = ""
|
||||
for warning in self._user_warnings.get_user_warnings_by_user_id(user.id):
|
||||
warnings_string += f"{warning.id} - {warning.description}\n"
|
||||
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.user.atr.warnings"),
|
||||
value=warnings_string,
|
||||
inline=False,
|
||||
)
|
||||
|
||||
# send to interaction because of sensitive data
|
||||
await self._message_service.send_interaction_msg(
|
||||
ctx.interaction, embed, wait_before_delete=wait
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished user-info command")
|
||||
|
||||
@user.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
async def get(self, ctx: Context, atr: str, member: discord.Member = None):
|
||||
self._logger.debug(__name__, f"Received command user-get {atr} {ctx}:{member}")
|
||||
|
||||
is_mod = self._permissions.is_member_moderator(ctx.author)
|
||||
if member is not None and not is_mod:
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("common.no_permission_message")
|
||||
)
|
||||
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.id)
|
||||
|
||||
if atr == "xp":
|
||||
value = str(user.xp)
|
||||
|
||||
elif atr == "ontime":
|
||||
value = str(self._client_utils.get_ontime_for_user(user))
|
||||
|
||||
else:
|
||||
await self._message_service.send_interaction_msg(
|
||||
ctx.interaction,
|
||||
self._t.transform("modules.base.user.error.atr_not_found").format(atr),
|
||||
)
|
||||
return
|
||||
|
||||
await self._message_service.send_interaction_msg(
|
||||
ctx.interaction,
|
||||
self._t.transform(f"modules.base.user.get.{atr.lower()}").format(
|
||||
member.mention, value
|
||||
),
|
||||
)
|
||||
|
||||
@get.autocomplete("atr")
|
||||
async def get_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
return [
|
||||
app_commands.Choice(name=value, value=key) for key, value in self._atr_TList
|
||||
]
|
||||
|
||||
@user.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def set(
|
||||
self, ctx: Context, atr: str, value: int, member: discord.Member = None
|
||||
):
|
||||
self._logger.debug(__name__, f"Received command user-set {atr} {ctx}:{member}")
|
||||
|
||||
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.id)
|
||||
|
||||
if atr == "xp":
|
||||
try:
|
||||
user.xp = value
|
||||
except TypeError as te:
|
||||
self._logger.error(
|
||||
__name__, f"String value couldn't be converted to int", te
|
||||
)
|
||||
await self._message_service.send_interaction_msg(
|
||||
ctx.interaction,
|
||||
self._t.transform("modules.base.user.set.error.type_error"),
|
||||
)
|
||||
return
|
||||
else:
|
||||
self._users.update_user(user)
|
||||
self._db.save_changes()
|
||||
await self._level.check_level(member)
|
||||
|
||||
else:
|
||||
await self._message_service.send_interaction_msg(
|
||||
ctx.interaction,
|
||||
self._t.transform("modules.base.user.error.atr_not_found").format(atr),
|
||||
)
|
||||
return
|
||||
|
||||
await self._message_service.send_interaction_msg(
|
||||
ctx.interaction,
|
||||
self._t.transform(f"modules.base.user.set.{atr.lower()}").format(
|
||||
member.mention, value
|
||||
),
|
||||
)
|
||||
|
||||
@set.autocomplete("atr")
|
||||
async def set_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
atr_TList = [("xp", self._atr_dict["xp"])]
|
||||
return [app_commands.Choice(name=value, value=key) for key, value in atr_TList]
|
||||
|
||||
@user.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def add(
|
||||
self, ctx: Context, atr: str, value: int, member: discord.Member = None
|
||||
):
|
||||
self._logger.debug(
|
||||
__name__, f"Received command user-add {atr}-={value} {ctx}:{member}"
|
||||
)
|
||||
await self._handle_atr_calc(ctx, atr, value, member)
|
||||
|
||||
@add.autocomplete("atr")
|
||||
async def set_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
atr_TList = [("xp", self._atr_dict["xp"])]
|
||||
return [app_commands.Choice(name=value, value=key) for key, value in atr_TList]
|
||||
|
||||
@user.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def remove(
|
||||
self, ctx: Context, atr: str, value: int, member: discord.Member = None
|
||||
):
|
||||
self._logger.debug(
|
||||
__name__, f"Received command user-remove {atr}-={value} {ctx}:{member}"
|
||||
)
|
||||
await self._handle_atr_calc(ctx, atr, value, member, is_remove=True)
|
||||
|
||||
@remove.autocomplete("atr")
|
||||
async def set_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
atr_TList = [("xp", self._atr_dict["xp"])]
|
||||
return [app_commands.Choice(name=value, value=key) for key, value in atr_TList]
|
||||
|
||||
@user.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def reset(self, ctx: Context, atr: str, member: discord.Member = None):
|
||||
self._logger.debug(
|
||||
__name__, f"Received command user-reset {atr} {ctx}:{member}"
|
||||
)
|
||||
|
||||
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.id)
|
||||
|
||||
if atr == "xp":
|
||||
user.xp = 0
|
||||
self._users.update_user(user)
|
||||
self._db.save_changes()
|
||||
await self._level.check_level(member)
|
||||
|
||||
elif atr == "ontime":
|
||||
self._user_joined_voice_channel.delete_user_joined_voice_channel_by_user_id(
|
||||
user.id
|
||||
)
|
||||
self._db.save_changes()
|
||||
|
||||
else:
|
||||
await self._message_service.send_interaction_msg(
|
||||
ctx.interaction,
|
||||
self._t.transform("modules.base.user.error.atr_not_found").format(atr),
|
||||
)
|
||||
return
|
||||
|
||||
await self._message_service.send_interaction_msg(
|
||||
ctx.interaction,
|
||||
self._t.transform(f"modules.base.user.reset.{atr.lower()}").format(
|
||||
atr, member.mention
|
||||
),
|
||||
)
|
||||
|
||||
@reset.autocomplete("atr")
|
||||
async def reset_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
return [
|
||||
app_commands.Choice(name=value, value=key) for key, value in self._atr_TList
|
||||
]
|
||||
|
||||
@user.group()
|
||||
@commands.guild_only()
|
||||
async def warning(self, ctx: Context):
|
||||
pass
|
||||
|
||||
@warning.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def show(self, ctx: Context, member: discord.Member, wait: int = None):
|
||||
self._logger.debug(
|
||||
__name__, f"Received command user warning show {ctx}:{member}"
|
||||
)
|
||||
|
||||
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.id)
|
||||
|
||||
embed = discord.Embed(
|
||||
title=member.name,
|
||||
description=self._t.transform("modules.base.user.atr.warnings"),
|
||||
color=int("ef9d0d", 16),
|
||||
)
|
||||
|
||||
warnings = self._user_warnings.get_user_warnings_by_user_id(user.id)
|
||||
warnings_id_string = ""
|
||||
for warning in warnings:
|
||||
warnings_id_string += f"{warning.id}\n"
|
||||
|
||||
warnings_description_string = ""
|
||||
for warning in warnings:
|
||||
warnings_description_string += f"{warning.description}\n"
|
||||
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.warnings.show.id"),
|
||||
value=warnings_id_string,
|
||||
inline=True,
|
||||
)
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.base.warnings.show.description"),
|
||||
value=warnings_description_string,
|
||||
inline=True,
|
||||
)
|
||||
await self._message_service.send_interaction_msg(
|
||||
ctx.interaction, embed, wait_before_delete=wait
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished user warning show command")
|
||||
|
||||
@warning.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def add(self, ctx: Context, member: discord.Member, description: str):
|
||||
self._logger.debug(
|
||||
__name__, f"Received command user warning add {ctx}:{member},{description}"
|
||||
)
|
||||
try:
|
||||
await self._user_warnings_service.add_warnings_async(
|
||||
member, description, ctx.author.id
|
||||
)
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.base.warnings.add.success")
|
||||
)
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Adding user warning failed", e)
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.base.warnings.add.failed")
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished user warning add command")
|
||||
|
||||
@warning.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def remove(self, ctx: Context, warning_id: int):
|
||||
self._logger.debug(
|
||||
__name__, f"Received command user warning remove {ctx}:{warning_id}"
|
||||
)
|
||||
try:
|
||||
await self._user_warnings_service.remove_warnings_async(warning_id)
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.base.warnings.remove.success")
|
||||
)
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Removing user warning failed", e)
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.base.warnings.remove.failed")
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished user warning remove command")
|
26
bot/src/modules/base/events/__init__.py
Normal file
26
bot/src/modules/base/events/__init__.py
Normal 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.base.events"
|
||||
__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")
|
279
bot/src/modules/base/events/base_on_command_error_event.py
Normal file
279
bot/src/modules/base/events/base_on_command_error_event.py
Normal file
@@ -0,0 +1,279 @@
|
||||
import datetime
|
||||
import uuid
|
||||
|
||||
from cpl_core.logging import LoggerABC
|
||||
from cpl_core.time import TimeFormatSettings
|
||||
from cpl_discord.events.on_command_error_abc import OnCommandErrorABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from cpl_translation import TranslatePipe
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context, CommandError
|
||||
|
||||
from bot_core.abc.message_service_abc import MessageServiceABC
|
||||
from bot_core.exception.check_error import CheckError
|
||||
from bot_data.model.technician_config import TechnicianConfig
|
||||
|
||||
|
||||
class BaseOnCommandErrorEvent(OnCommandErrorABC):
|
||||
def __init__(
|
||||
self,
|
||||
logger: LoggerABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
messenger: MessageServiceABC,
|
||||
tech_settings: TechnicianConfig,
|
||||
time_format_settings: TimeFormatSettings,
|
||||
translate: TranslatePipe,
|
||||
):
|
||||
OnCommandErrorABC.__init__(self)
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._messenger = messenger
|
||||
self._tech_settings = tech_settings
|
||||
self._time_format_settings = time_format_settings
|
||||
self._t = translate
|
||||
|
||||
async def on_command_error(self, ctx: Context, error: CommandError):
|
||||
if isinstance(error, CheckError):
|
||||
return
|
||||
|
||||
error = getattr(error, "original", error)
|
||||
uid = uuid.uuid4()
|
||||
self._logger.error(__name__, f"Got error: {type(error).__name__} UID: {uid}")
|
||||
|
||||
if isinstance(error, commands.MissingRequiredArgument):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.missing_required_argument"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.ArgumentParsingError):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.argument_parsing_error"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.UnexpectedQuoteError):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.unexpected_quote_error"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.InvalidEndOfQuotedStringError):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.invalid_end_of_quoted_string_error"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.ExpectedClosingQuoteError):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.expected_closing_quote_error"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.BadArgument):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.bad_argument"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.BadUnionArgument):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.bad_union_argument"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.PrivateMessageOnly):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.private_message_only"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.NoPrivateMessage):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.no_private_message"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.CheckFailure):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.check_failure"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.CheckAnyFailure):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.check_any_failure"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.CommandNotFound):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.command_not_found"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.DisabledCommand):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.disabled_command"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.CommandInvokeError):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.command_invoke_error"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.TooManyArguments):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.too_many_arguments"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.UserInputError):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.user_input_error"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.CommandOnCooldown):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.command_on_cooldown"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.MaxConcurrencyReached):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.max_concurrency_reached"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.NotOwner):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx, self._t.transform("common.errors.not_owner"), without_tracking=True
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.MissingPermissions):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.missing_permissions"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.BotMissingPermissions):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.bot_missing_permissions"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.MissingRole):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.missing_role"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.BotMissingRole):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.bot_missing_role"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.MissingAnyRole):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.missing_any_role"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.BotMissingAnyRole):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.bot_missing_any_role"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.NSFWChannelRequired):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.nsfw_channel_required"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.ExtensionError):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.extension_error"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.ExtensionAlreadyLoaded):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.extension_already_loaded"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.ExtensionNotLoaded):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.extension_not_loaded"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.NoEntryPointError):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.no_entry_point_error"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
elif isinstance(error, commands.ExtensionFailed):
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.extension_failed"),
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
else:
|
||||
await self._messenger.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("common.errors.command_error"),
|
||||
without_tracking=True,
|
||||
)
|
||||
message = self._t.transform(
|
||||
"modules.base.technician_command_error_message"
|
||||
).format(
|
||||
ctx.command,
|
||||
ctx.author,
|
||||
error,
|
||||
datetime.datetime.now().strftime(
|
||||
self._time_format_settings.date_time_format
|
||||
),
|
||||
uid,
|
||||
)
|
||||
for t in self._tech_settings.technician_ids:
|
||||
member = self._bot.get_user(t)
|
||||
await self._messenger.send_dm_message(
|
||||
message, member, without_tracking=True
|
||||
)
|
96
bot/src/modules/base/events/base_on_command_event.py
Normal file
96
bot/src/modules/base/events/base_on_command_event.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from typing import Optional
|
||||
|
||||
import discord
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.database.context import DatabaseContextABC
|
||||
from cpl_core.time import TimeFormatSettings
|
||||
from cpl_discord.events import OnCommandABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from cpl_translation import TranslatePipe
|
||||
from discord.ext.commands import Context
|
||||
|
||||
from bot_core.abc.message_service_abc import MessageServiceABC
|
||||
from bot_core.logging.command_logger import CommandLogger
|
||||
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.server_config import ServerConfig
|
||||
from bot_data.model.user import User
|
||||
|
||||
|
||||
class BaseOnCommandEvent(OnCommandABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: CommandLogger,
|
||||
bot: DiscordBotServiceABC,
|
||||
messenger: MessageServiceABC,
|
||||
time_format_settings: TimeFormatSettings,
|
||||
translate: TranslatePipe,
|
||||
db: DatabaseContextABC,
|
||||
users: UserRepositoryABC,
|
||||
clients: ClientRepositoryABC,
|
||||
servers: ServerRepositoryABC,
|
||||
):
|
||||
OnCommandABC.__init__(self)
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._messenger = messenger
|
||||
self._time_format_settings = time_format_settings
|
||||
self._t = translate
|
||||
self._db = db
|
||||
self._users = users
|
||||
self._clients = clients
|
||||
self._servers = servers
|
||||
|
||||
def _append_received_command_count(self, g_id: int):
|
||||
try:
|
||||
self._clients.append_received_command_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.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: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{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_command(self, ctx: Context):
|
||||
self._logger.debug(__name__, f"Module {type(self)} started")
|
||||
self._logger.info(
|
||||
__name__, f"Received command: {ctx.command} from {ctx.channel}"
|
||||
)
|
||||
if ctx is None or ctx.guild is None:
|
||||
return
|
||||
self._append_received_command_count(ctx.guild.id)
|
38
bot/src/modules/base/events/base_on_guild_join_event.py
Normal file
38
bot/src/modules/base/events/base_on_guild_join_event.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from cpl_core.database.context import DatabaseContextABC
|
||||
from cpl_core.logging import LoggerABC
|
||||
from cpl_discord.events import OnGuildJoinABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from discord import Guild
|
||||
|
||||
from bot_data.abc.server_config_repository_abc import ServerConfigRepositoryABC
|
||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
from bot_data.model.server import Server
|
||||
from bot_data.service.seeder_service import SeederService
|
||||
|
||||
|
||||
class BaseOnGuildJoinEvent(OnGuildJoinABC):
|
||||
def __init__(
|
||||
self,
|
||||
logger: LoggerABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
servers: ServerRepositoryABC,
|
||||
server_config: ServerConfigRepositoryABC,
|
||||
db: DatabaseContextABC,
|
||||
seeder: SeederService,
|
||||
):
|
||||
OnGuildJoinABC.__init__(self)
|
||||
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._servers = servers
|
||||
self._server_config = server_config
|
||||
self._db = db
|
||||
self._seeder = seeder
|
||||
|
||||
async def on_guild_join(self, guild: Guild):
|
||||
if self._servers.find_server_by_discord_id(guild.id) is None:
|
||||
self._servers.add_server(Server(guild.id))
|
||||
self._db.save_changes()
|
||||
|
||||
await self._seeder.seed()
|
||||
self._logger.debug(__name__, "Seeded technician config")
|
124
bot/src/modules/base/events/base_on_member_join_event.py
Normal file
124
bot/src/modules/base/events/base_on_member_join_event.py
Normal file
@@ -0,0 +1,124 @@
|
||||
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_discord.service import DiscordBotServiceABC
|
||||
from cpl_translation import TranslatePipe
|
||||
|
||||
from bot_core.abc.client_utils_abc import ClientUtilsABC
|
||||
from bot_core.abc.message_service_abc import MessageServiceABC
|
||||
from bot_core.helper.event_checks import EventChecks
|
||||
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.server_config import ServerConfig
|
||||
from bot_data.model.user import User
|
||||
from bot_data.model.user_joined_server import UserJoinedServer
|
||||
from modules.permission.abc.permission_service_abc import PermissionServiceABC
|
||||
|
||||
|
||||
class BaseOnMemberJoinEvent(OnMemberJoinABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: LoggerABC,
|
||||
messenger: MessageServiceABC,
|
||||
permissions: PermissionServiceABC,
|
||||
db: DatabaseContextABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
known_users: KnownUserRepositoryABC,
|
||||
users: UserRepositoryABC,
|
||||
servers: ServerRepositoryABC,
|
||||
user_joins: UserJoinedServerRepositoryABC,
|
||||
client_utils: ClientUtilsABC,
|
||||
translate: TranslatePipe,
|
||||
):
|
||||
OnMemberJoinABC.__init__(self)
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._messenger = messenger
|
||||
self._permission_service = permissions
|
||||
self._db = db
|
||||
self._bot = bot
|
||||
self._known_users = known_users
|
||||
self._users = users
|
||||
self._servers = servers
|
||||
self._user_joins = user_joins
|
||||
self._client_utils = client_utils
|
||||
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}")
|
||||
await self._messenger.send_dm_message(
|
||||
self._t.transform("modules.base.welcome_message").format(member.guild.name),
|
||||
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.id
|
||||
)
|
||||
if user is not None:
|
||||
self._user_joins.add_user_joined_server(
|
||||
UserJoinedServer(user, datetime.now())
|
||||
)
|
||||
self._db.save_changes()
|
||||
return
|
||||
|
||||
self._logger.debug(__name__, f"Add user: {member.id}")
|
||||
self._users.add_user(User(member.id, 0, 0, 0, server))
|
||||
self._db.save_changes()
|
||||
user = self._users.get_user_by_discord_id_and_server_id(
|
||||
member.id, 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 _notify_team(self, member: discord.Member):
|
||||
self._logger.debug(__name__, f"Notify team that a member left")
|
||||
settings: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{member.guild.id}"
|
||||
)
|
||||
channel = member.guild.get_channel(settings.team_channel_id)
|
||||
await self._messenger.send_channel_message(
|
||||
channel,
|
||||
self._t.transform("modules.base.welcome_message_for_team").format(
|
||||
member.mention
|
||||
),
|
||||
is_persistent=True,
|
||||
)
|
||||
self._logger.trace(__name__, f"Notified team that a member left")
|
||||
|
||||
@EventChecks.check_is_ready()
|
||||
async def on_member_join(self, member: discord.Member):
|
||||
self._logger.debug(__name__, f"Module {type(self)} started")
|
||||
await self._client_utils.check_default_role(member)
|
||||
self._check_for_known_user(member)
|
||||
await self._add_if_not_exists_user_async(member)
|
||||
await self._notify_team(member)
|
84
bot/src/modules/base/events/base_on_member_remove_event.py
Normal file
84
bot/src/modules/base/events/base_on_member_remove_event.py
Normal file
@@ -0,0 +1,84 @@
|
||||
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 OnMemberRemoveABC
|
||||
from cpl_translation import TranslatePipe
|
||||
|
||||
from bot_core.abc.message_service_abc import MessageServiceABC
|
||||
from bot_core.helper.event_checks import EventChecks
|
||||
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.server_config import ServerConfig
|
||||
|
||||
|
||||
class BaseOnMemberRemoveEvent(OnMemberRemoveABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: LoggerABC,
|
||||
db: DatabaseContextABC,
|
||||
message_service: MessageServiceABC,
|
||||
users: UserRepositoryABC,
|
||||
servers: ServerRepositoryABC,
|
||||
user_joins: UserJoinedServerRepositoryABC,
|
||||
translate: TranslatePipe,
|
||||
):
|
||||
OnMemberRemoveABC.__init__(self)
|
||||
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._db = db
|
||||
self._message_service = message_service
|
||||
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}")
|
||||
await self._message_service.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.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.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 remove user {member.id}", e)
|
||||
|
||||
async def _notify_team(self, member: discord.Member):
|
||||
self._logger.debug(__name__, f"Notify team that a member left")
|
||||
settings: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{member.guild.id}"
|
||||
)
|
||||
channel = member.guild.get_channel(settings.team_channel_id)
|
||||
await self._message_service.send_channel_message(
|
||||
channel,
|
||||
self._t.transform("modules.base.member_left_message").format(
|
||||
member.mention
|
||||
),
|
||||
is_persistent=True,
|
||||
)
|
||||
self._logger.trace(__name__, f"Notified team that a member left")
|
||||
|
||||
@EventChecks.check_is_ready()
|
||||
async def on_member_remove(self, member: discord.Member):
|
||||
self._logger.debug(__name__, f"Module {type(self)} started")
|
||||
await self._remove_user(member)
|
||||
await self._notify_team(member)
|
93
bot/src/modules/base/events/base_on_message_delete_event.py
Normal file
93
bot/src/modules/base/events/base_on_message_delete_event.py
Normal file
@@ -0,0 +1,93 @@
|
||||
from typing import Optional
|
||||
|
||||
import discord
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.database.context import DatabaseContextABC
|
||||
from cpl_discord.events import OnMessageDeleteABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
|
||||
from bot_core.helper.log_message_helper import LogMessageHelper
|
||||
from bot_core.logging.message_logger import MessageLogger
|
||||
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.server_config import ServerConfig
|
||||
from bot_data.model.user import User
|
||||
|
||||
|
||||
class BaseOnMessageDeleteEvent(OnMessageDeleteABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: MessageLogger,
|
||||
db: DatabaseContextABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
users: UserRepositoryABC,
|
||||
clients: ClientRepositoryABC,
|
||||
servers: ServerRepositoryABC,
|
||||
):
|
||||
OnMessageDeleteABC.__init__(self)
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._db = db
|
||||
self._bot = bot
|
||||
self._users = users
|
||||
self._clients = clients
|
||||
self._servers = servers
|
||||
|
||||
def _append_deleted_message_count(self, g_id: int):
|
||||
try:
|
||||
self._clients.append_deleted_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_delete(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.find_user_by_discord_id_and_server_id(
|
||||
dc_user_id, 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: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{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"Removed message from user {user}. xp: from {old_xp} to {user.xp}",
|
||||
)
|
||||
|
||||
async def on_message_delete(self, message: discord.Message):
|
||||
self._logger.debug(__name__, f"Module {type(self)} started")
|
||||
if message is None or message.guild is None:
|
||||
return
|
||||
|
||||
self._logger.info(
|
||||
__name__, f"Received message: {LogMessageHelper.get_log_string(message)}"
|
||||
)
|
||||
self._append_deleted_message_count(message.guild.id)
|
||||
|
||||
if not message.author.bot:
|
||||
self._handle_message_delete(message)
|
||||
self._logger.debug(__name__, f"Module {type(self)} stopped")
|
103
bot/src/modules/base/events/base_on_message_event.py
Normal file
103
bot/src/modules/base/events/base_on_message_event.py
Normal file
@@ -0,0 +1,103 @@
|
||||
from typing import Optional
|
||||
|
||||
import discord
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.database.context import DatabaseContextABC
|
||||
from cpl_discord.events import OnMessageABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
|
||||
from bot_core.helper.event_checks import EventChecks
|
||||
from bot_core.helper.log_message_helper import LogMessageHelper
|
||||
from bot_core.logging.message_logger import MessageLogger
|
||||
from bot_core.service.client_utils_service import ClientUtilsService
|
||||
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.server_config import ServerConfig
|
||||
from bot_data.model.user import User
|
||||
|
||||
|
||||
class BaseOnMessageEvent(OnMessageABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: MessageLogger,
|
||||
client_utils: ClientUtilsService,
|
||||
db: DatabaseContextABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
users: UserRepositoryABC,
|
||||
clients: ClientRepositoryABC,
|
||||
servers: ServerRepositoryABC,
|
||||
):
|
||||
OnMessageABC.__init__(self)
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._client_utils = client_utils
|
||||
self._bot = bot
|
||||
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.find_user_by_discord_id_and_server_id(
|
||||
dc_user_id, 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: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{server.discord_id}"
|
||||
)
|
||||
if self._client_utils.is_message_xp_count_by_hour_higher_that_max_message_count_per_hour(
|
||||
message.created_at, user, settings
|
||||
):
|
||||
return
|
||||
|
||||
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}"
|
||||
)
|
||||
|
||||
@EventChecks.check_is_ready()
|
||||
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._logger.info(
|
||||
__name__, f"Received message: {LogMessageHelper.get_log_string(message)}"
|
||||
)
|
||||
self._append_received_message_count(message.guild.id)
|
||||
|
||||
if not message.author.bot:
|
||||
self._handle_message_for_xp(message)
|
||||
self._logger.debug(__name__, f"Module {type(self)} stopped")
|
35
bot/src/modules/base/events/base_on_raw_reaction_add.py
Normal file
35
bot/src/modules/base/events/base_on_raw_reaction_add.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from cpl_core.logging import LoggerABC
|
||||
from cpl_discord.events.on_raw_reaction_add_abc import OnRawReactionAddABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from discord import RawReactionActionEvent
|
||||
|
||||
from bot_core.helper.event_checks import EventChecks
|
||||
from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC
|
||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
from modules.base.helper.base_reaction_handler import BaseReactionHandler
|
||||
|
||||
|
||||
class BaseOnRawReactionAddEvent(OnRawReactionAddABC):
|
||||
def __init__(
|
||||
self,
|
||||
logger: LoggerABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
servers: ServerRepositoryABC,
|
||||
auto_roles: AutoRoleRepositoryABC,
|
||||
reaction_handler: BaseReactionHandler,
|
||||
):
|
||||
OnRawReactionAddABC.__init__(self)
|
||||
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._servers = servers
|
||||
self._auto_roles = auto_roles
|
||||
self._reaction_handler = reaction_handler
|
||||
|
||||
@EventChecks.check_is_ready()
|
||||
async def on_raw_reaction_add(self, payload: RawReactionActionEvent):
|
||||
self._logger.debug(__name__, f"Module {type(self)} started")
|
||||
|
||||
await self._reaction_handler.handle(payload, "add")
|
||||
|
||||
self._logger.debug(__name__, f"Module {type(self)} stopped")
|
35
bot/src/modules/base/events/base_on_raw_reaction_remove.py
Normal file
35
bot/src/modules/base/events/base_on_raw_reaction_remove.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from cpl_core.logging import LoggerABC
|
||||
from cpl_discord.events.on_raw_reaction_remove_abc import OnRawReactionRemoveABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from discord import RawReactionActionEvent
|
||||
|
||||
from bot_core.helper.event_checks import EventChecks
|
||||
from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC
|
||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
from modules.base.helper.base_reaction_handler import BaseReactionHandler
|
||||
|
||||
|
||||
class BaseOnRawReactionRemoveEvent(OnRawReactionRemoveABC):
|
||||
def __init__(
|
||||
self,
|
||||
logger: LoggerABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
servers: ServerRepositoryABC,
|
||||
auto_roles: AutoRoleRepositoryABC,
|
||||
reaction_handler: BaseReactionHandler,
|
||||
):
|
||||
OnRawReactionRemoveABC.__init__(self)
|
||||
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._servers = servers
|
||||
self._auto_roles = auto_roles
|
||||
self._reaction_handler = reaction_handler
|
||||
|
||||
@EventChecks.check_is_ready()
|
||||
async def on_raw_reaction_remove(self, payload: RawReactionActionEvent):
|
||||
self._logger.debug(__name__, f"Module {type(self)} started")
|
||||
|
||||
await self._reaction_handler.handle(payload, "remove")
|
||||
|
||||
self._logger.debug(__name__, f"Module {type(self)} stopped")
|
@@ -0,0 +1,46 @@
|
||||
import discord
|
||||
from cpl_core.logging import LoggerABC
|
||||
from cpl_discord.events.on_scheduled_event_update_abc import OnScheduledEventUpdateABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from discord import EventStatus
|
||||
|
||||
from modules.base.model.active_event import ActiveEvent
|
||||
from modules.base.service.event_service import EventService
|
||||
|
||||
|
||||
class BaseOnScheduledEventUpdateEvent(OnScheduledEventUpdateABC):
|
||||
def __init__(
|
||||
self,
|
||||
logger: LoggerABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
events: EventService,
|
||||
):
|
||||
OnScheduledEventUpdateABC.__init__(self)
|
||||
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._events = events
|
||||
|
||||
async def on_scheduled_event_update(
|
||||
self, before: discord.ScheduledEvent, after: discord.ScheduledEvent
|
||||
):
|
||||
self._logger.debug(__name__, f"Module {type(self)} started")
|
||||
|
||||
# save started event
|
||||
if before.status != after.status and after.status == EventStatus.active:
|
||||
active_event = ActiveEvent(after)
|
||||
self._events.add_event(active_event)
|
||||
|
||||
for member in after.channel.members:
|
||||
self._events.give_xp_for_event_participation(member, active_event)
|
||||
# delete stopped event
|
||||
if before.status != after.status and (
|
||||
after.status.value == EventStatus.cancelled.value
|
||||
or after.status.value == EventStatus.completed.value
|
||||
):
|
||||
event = self._events.get_active_event(after)
|
||||
if event is None:
|
||||
return
|
||||
self._events.remove_event(event)
|
||||
|
||||
self._logger.debug(__name__, f"Module {type(self)} stopped")
|
168
bot/src/modules/base/events/base_on_voice_state_update_event.py
Normal file
168
bot/src/modules/base/events/base_on_voice_state_update_event.py
Normal file
@@ -0,0 +1,168 @@
|
||||
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_core.helper.event_checks import EventChecks
|
||||
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_repository_abc import (
|
||||
UserJoinedVoiceChannelRepositoryABC,
|
||||
)
|
||||
from bot_data.abc.user_repository_abc import UserRepositoryABC
|
||||
from bot_data.model.server import Server
|
||||
from bot_data.model.server_config import ServerConfig
|
||||
from bot_data.model.user import User
|
||||
from bot_data.model.user_joined_voice_channel import UserJoinedVoiceChannel
|
||||
|
||||
|
||||
class BaseOnVoiceStateUpdateEvent(OnVoiceStateUpdateABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: LoggerABC,
|
||||
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._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, 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.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, channel_id, datetime.now())
|
||||
self._user_joins_vc.add_user_joined_voice_channel(join)
|
||||
self._db.save_changes()
|
||||
return
|
||||
|
||||
settings: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{server.discord_id}"
|
||||
)
|
||||
|
||||
join = self._user_joins_vc.get_active_user_joined_voice_channel_by_user_id(
|
||||
user.id
|
||||
)
|
||||
join.leaved_on = datetime.now()
|
||||
|
||||
old_xp = user.xp
|
||||
user.xp += round(join.time * 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: {join.time}h | xp: from {old_xp} to {user.xp}",
|
||||
)
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Ontime validation failed", e)
|
||||
|
||||
@EventChecks.check_is_ready()
|
||||
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: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{member.guild.id}"
|
||||
)
|
||||
server = self._servers.get_server_by_discord_id(member.guild.id)
|
||||
|
||||
try:
|
||||
if before.channel == after.channel:
|
||||
return
|
||||
|
||||
# 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}"
|
||||
)
|
||||
self._update_voice_state(
|
||||
False, member.id, before.channel.id, server
|
||||
)
|
||||
self._update_voice_state(True, member.id, after.channel.id, server)
|
||||
except Exception as e:
|
||||
self._logger.error(
|
||||
__name__, f"Cannot handle voice state for user {member.id}", e
|
||||
)
|
@@ -0,0 +1,66 @@
|
||||
import discord
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.logging import LoggerABC
|
||||
from cpl_discord.events import OnVoiceStateUpdateABC
|
||||
from cpl_translation import TranslatePipe
|
||||
|
||||
from bot_core.abc.message_service_abc import MessageServiceABC
|
||||
from bot_core.helper.event_checks import EventChecks
|
||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
from bot_data.model.server_config import ServerConfig
|
||||
from modules.permission.abc.permission_service_abc import PermissionServiceABC
|
||||
|
||||
|
||||
class BaseOnVoiceStateUpdateEventHelpChannel(OnVoiceStateUpdateABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: LoggerABC,
|
||||
servers: ServerRepositoryABC,
|
||||
permissions: PermissionServiceABC,
|
||||
message_service: MessageServiceABC,
|
||||
t: TranslatePipe,
|
||||
):
|
||||
OnVoiceStateUpdateABC.__init__(self)
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._servers = servers
|
||||
self._permissions = permissions
|
||||
self._message_service = message_service
|
||||
self._t = t
|
||||
|
||||
self._logger.info(__name__, f"Module {type(self)} loaded")
|
||||
|
||||
async def _notify_team(self, member: discord.Member):
|
||||
self._logger.debug(__name__, f"Notify team that a member needs help")
|
||||
settings: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{member.guild.id}"
|
||||
)
|
||||
channel = member.guild.get_channel(settings.team_channel_id)
|
||||
await self._message_service.send_channel_message(
|
||||
channel,
|
||||
self._t.transform("modules.base.member_joined_help_voice_channel").format(
|
||||
member.mention
|
||||
),
|
||||
is_persistent=True,
|
||||
)
|
||||
self._logger.trace(__name__, f"Notified team that a member need help")
|
||||
|
||||
@EventChecks.check_is_ready()
|
||||
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")
|
||||
server = self._servers.get_server_by_discord_id(member.guild.id)
|
||||
settings: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{server.discord_id}"
|
||||
)
|
||||
if after.channel is None or after.channel.id != settings.help_voice_channel_id:
|
||||
return
|
||||
|
||||
await self._notify_team(member)
|
||||
|
||||
self._logger.debug(__name__, f"Module {type(self)} stopped")
|
@@ -0,0 +1,52 @@
|
||||
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_core.helper.event_checks import EventChecks
|
||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
from bot_data.abc.user_repository_abc import UserRepositoryABC
|
||||
from modules.base.service.event_service import EventService
|
||||
|
||||
|
||||
class BaseOnVoiceStateUpdateEventScheduledEventBonus(OnVoiceStateUpdateABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: LoggerABC,
|
||||
servers: ServerRepositoryABC,
|
||||
users: UserRepositoryABC,
|
||||
events: EventService,
|
||||
db: DatabaseContextABC,
|
||||
):
|
||||
OnVoiceStateUpdateABC.__init__(self)
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._servers = servers
|
||||
self._users = users
|
||||
self._events = events
|
||||
self._db = db
|
||||
|
||||
self._logger.info(__name__, f"Module {type(self)} loaded")
|
||||
|
||||
@EventChecks.check_is_ready()
|
||||
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")
|
||||
if member.bot or after.channel is None:
|
||||
self._logger.debug(__name__, f"Module {type(self)} stopped")
|
||||
return
|
||||
|
||||
active_event = self._events.get_active_event_by_channel_id(after.channel.id)
|
||||
if active_event is None:
|
||||
self._logger.debug(__name__, f"Module {type(self)} stopped")
|
||||
return
|
||||
|
||||
self._events.give_xp_for_event_participation(member, active_event)
|
||||
|
||||
self._logger.debug(__name__, f"Module {type(self)} stopped")
|
26
bot/src/modules/base/forms/__init__.py
Normal file
26
bot/src/modules/base/forms/__init__.py
Normal 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.base.forms"
|
||||
__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")
|
53
bot/src/modules/base/forms/bug_report_form.py
Normal file
53
bot/src/modules/base/forms/bug_report_form.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import discord
|
||||
from cpl_core.database.context import DatabaseContextABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from cpl_translation import TranslatePipe
|
||||
from discord import ui, TextStyle
|
||||
|
||||
from bot_core.abc.message_service_abc import MessageServiceABC
|
||||
from bot_core.logging.command_logger import CommandLogger
|
||||
from bot_data.model.technician_config import TechnicianConfig
|
||||
|
||||
|
||||
class BugReportForm(ui.Modal):
|
||||
description = ui.TextInput(
|
||||
label="Report a bug", required=True, style=TextStyle.long
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
technician_config: TechnicianConfig,
|
||||
bot: DiscordBotServiceABC,
|
||||
db: DatabaseContextABC,
|
||||
logger: CommandLogger,
|
||||
message_service: MessageServiceABC,
|
||||
t: TranslatePipe,
|
||||
):
|
||||
ui.Modal.__init__(self, title=t.transform("modules.base.bug.title"))
|
||||
|
||||
self._technician_config = technician_config
|
||||
self._bot = bot
|
||||
self._db = db
|
||||
self._message_service = message_service
|
||||
self._logger = logger
|
||||
self._t = t
|
||||
|
||||
self.description.label = t.transform("modules.base.bug.label")
|
||||
|
||||
async def on_submit(self, interaction: discord.Interaction):
|
||||
self._logger.debug(__name__, f"Started bug report form")
|
||||
|
||||
for t in self._technician_config.technician_ids:
|
||||
member = self._bot.get_user(t)
|
||||
await self._message_service.send_dm_message(
|
||||
self._t.transform("modules.base.bug.message").format(
|
||||
interaction.user.mention, self.description.value
|
||||
),
|
||||
member,
|
||||
without_tracking=True,
|
||||
)
|
||||
|
||||
await self._message_service.send_interaction_msg(
|
||||
interaction, self._t.transform("modules.base.bug.response")
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished bug report form")
|
51
bot/src/modules/base/forms/complaint_form.py
Normal file
51
bot/src/modules/base/forms/complaint_form.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import discord
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.database.context import DatabaseContextABC
|
||||
from cpl_translation import TranslatePipe
|
||||
from discord import ui, TextStyle
|
||||
|
||||
from bot_core.abc.message_service_abc import MessageServiceABC
|
||||
from bot_core.logging.command_logger import CommandLogger
|
||||
from bot_data.model.server_config import ServerConfig
|
||||
|
||||
|
||||
class ComplaintForm(ui.Modal):
|
||||
description = ui.TextInput(
|
||||
label="Complain about something", required=True, style=TextStyle.long
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
db: DatabaseContextABC,
|
||||
logger: CommandLogger,
|
||||
message_service: MessageServiceABC,
|
||||
t: TranslatePipe,
|
||||
):
|
||||
ui.Modal.__init__(self, title=t.transform("modules.base.complaints.title"))
|
||||
|
||||
self._config = config
|
||||
self._db = db
|
||||
self._message_service = message_service
|
||||
self._logger = logger
|
||||
self._t = t
|
||||
|
||||
self.description.label = t.transform("modules.base.complaints.label")
|
||||
|
||||
async def on_submit(self, interaction: discord.Interaction):
|
||||
self._logger.debug(__name__, f"Started complaint command form")
|
||||
settings: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{interaction.guild.id}"
|
||||
)
|
||||
channel = interaction.guild.get_channel(settings.team_channel_id)
|
||||
await self._message_service.send_channel_message(
|
||||
channel,
|
||||
self._t.transform("modules.base.complaints.message").format(
|
||||
interaction.user.mention, self.description.value
|
||||
),
|
||||
is_persistent=True,
|
||||
)
|
||||
await self._message_service.send_interaction_msg(
|
||||
interaction, self._t.transform("modules.base.complaints.response")
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished complaint command form")
|
26
bot/src/modules/base/helper/__init__.py
Normal file
26
bot/src/modules/base/helper/__init__.py
Normal 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.base.helper"
|
||||
__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")
|
97
bot/src/modules/base/helper/base_reaction_handler.py
Normal file
97
bot/src/modules/base/helper/base_reaction_handler.py
Normal file
@@ -0,0 +1,97 @@
|
||||
from datetime import datetime
|
||||
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.database.context import DatabaseContextABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from discord import RawReactionActionEvent
|
||||
|
||||
from bot_core.abc.client_utils_abc import ClientUtilsABC
|
||||
from bot_core.helper.log_message_helper import LogMessageHelper
|
||||
from bot_core.logging.message_logger import MessageLogger
|
||||
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
|
||||
|
||||
|
||||
class BaseReactionHandler:
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: MessageLogger,
|
||||
bot: DiscordBotServiceABC,
|
||||
servers: ServerRepositoryABC,
|
||||
users: UserRepositoryABC,
|
||||
client_utils: ClientUtilsABC,
|
||||
db: DatabaseContextABC,
|
||||
):
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._servers = servers
|
||||
self._users = users
|
||||
self._client_utils = client_utils
|
||||
self._db = db
|
||||
|
||||
async def handle(self, payload: RawReactionActionEvent, r_type=None) -> None:
|
||||
self._logger.trace(__name__, f"Handle reaction {payload} {r_type}")
|
||||
|
||||
guild = self._bot.get_guild(payload.guild_id)
|
||||
member = guild.get_member(payload.user_id)
|
||||
if member is None:
|
||||
self._logger.warn(
|
||||
__name__, f"User {payload.user_id} in {guild.name} not found - skipping"
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
log_msg = f"{member.name} reacted"
|
||||
if payload.emoji.name is not None:
|
||||
log_msg += f" with {payload.emoji.name}"
|
||||
try:
|
||||
channel = guild.get_channel(payload.channel_id)
|
||||
if channel is None:
|
||||
self._logger.warn(__name__, f"Channel not found")
|
||||
return
|
||||
|
||||
message = await channel.fetch_message(payload.message_id)
|
||||
self._logger.info(
|
||||
__name__,
|
||||
f"{log_msg} to message {LogMessageHelper.get_log_string(message)}",
|
||||
)
|
||||
if member == message.author:
|
||||
self._logger.debug(__name__, f"Skipping reaction handling")
|
||||
return
|
||||
except Exception as e:
|
||||
self._logger.error(
|
||||
__name__, f"Getting message for reaction logging failed", e
|
||||
)
|
||||
self._logger.info(
|
||||
__name__, f"{log_msg} to message {payload.message_id}"
|
||||
)
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Reaction logging failed", e)
|
||||
|
||||
if member.bot:
|
||||
return
|
||||
|
||||
server = self._servers.get_server_by_discord_id(guild.id)
|
||||
user = self._users.get_user_by_discord_id_and_server_id(member.id, server.id)
|
||||
settings: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{guild.id}"
|
||||
)
|
||||
|
||||
if r_type == "add":
|
||||
if self._client_utils.is_message_xp_count_by_hour_higher_that_max_message_count_per_hour(
|
||||
datetime.now(), user, settings, is_reaction=True
|
||||
):
|
||||
return
|
||||
|
||||
user.xp += settings.xp_per_reaction
|
||||
self._users.update_user(user)
|
||||
self._db.save_changes()
|
||||
elif r_type == "remove":
|
||||
user.xp -= settings.xp_per_reaction
|
||||
self._users.update_user(user)
|
||||
self._db.save_changes()
|
||||
else:
|
||||
self._logger.warn(__name__, f"Invalid reaction type {r_type}")
|
41
bot/src/modules/base/helper/voice_channel_transformer.py
Normal file
41
bot/src/modules/base/helper/voice_channel_transformer.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import discord
|
||||
from cpl_core.dependency_injection import ServiceProviderABC
|
||||
from cpl_query.extension import List
|
||||
from discord import Interaction, app_commands
|
||||
from discord.app_commands import Transformer, Choice
|
||||
|
||||
from bot_core.abc.client_utils_abc import ClientUtilsABC
|
||||
|
||||
|
||||
class VoiceChannelTransformer(Transformer):
|
||||
async def transform(
|
||||
self, interaction: Interaction, value: str, /
|
||||
) -> discord.VoiceChannel:
|
||||
voice_channel = (
|
||||
List(discord.VoiceChannel, interaction.guild.voice_channels)
|
||||
.where(lambda x: str(x.id) == value)
|
||||
.first_or_default()
|
||||
)
|
||||
return voice_channel
|
||||
|
||||
async def autocomplete(
|
||||
self, interaction: Interaction, current: str, /
|
||||
) -> list[Choice[str]]:
|
||||
@ServiceProviderABC.inject
|
||||
def get_client_utils(client_utils: ClientUtilsABC) -> ClientUtilsABC:
|
||||
return client_utils
|
||||
|
||||
voice_channels = List(discord.Role, interaction.guild.voice_channels).where(
|
||||
lambda x: len(x.members) > 0
|
||||
)
|
||||
return [
|
||||
app_commands.Choice(
|
||||
name=f"{vc.name}"
|
||||
if vc.category is None
|
||||
else f"{vc.name}: {vc.category.name}",
|
||||
value=vc.name,
|
||||
)
|
||||
for vc in get_client_utils().get_auto_complete_list(
|
||||
voice_channels, current, lambda x: x.name
|
||||
)
|
||||
]
|
26
bot/src/modules/base/model/__init__.py
Normal file
26
bot/src/modules/base/model/__init__.py
Normal 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.base.model"
|
||||
__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")
|
18
bot/src/modules/base/model/active_event.py
Normal file
18
bot/src/modules/base/model/active_event.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import discord
|
||||
from cpl_query.extension import List
|
||||
|
||||
from bot_data.model.user import User
|
||||
|
||||
|
||||
class ActiveEvent:
|
||||
def __init__(self, event: discord.ScheduledEvent):
|
||||
self._event = event
|
||||
self._participants = List(User)
|
||||
|
||||
@property
|
||||
def event(self) -> discord.ScheduledEvent:
|
||||
return self._event
|
||||
|
||||
@property
|
||||
def participants(self) -> List[User]:
|
||||
return self._participants
|
26
bot/src/modules/base/service/__init__.py
Normal file
26
bot/src/modules/base/service/__init__.py
Normal 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.base.service"
|
||||
__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")
|
69
bot/src/modules/base/service/event_service.py
Normal file
69
bot/src/modules/base/service/event_service.py
Normal file
@@ -0,0 +1,69 @@
|
||||
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_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.server_config import ServerConfig
|
||||
from modules.base.model.active_event import ActiveEvent
|
||||
|
||||
|
||||
class EventService:
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: LoggerABC,
|
||||
servers: ServerRepositoryABC,
|
||||
users: UserRepositoryABC,
|
||||
db: DatabaseContextABC,
|
||||
):
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._servers = servers
|
||||
self._users = users
|
||||
self._db = db
|
||||
|
||||
self._active_events = List(ActiveEvent)
|
||||
|
||||
def add_event(self, event: ActiveEvent):
|
||||
if self._active_events.contains(event):
|
||||
return
|
||||
|
||||
self._active_events.add(event)
|
||||
|
||||
def get_active_event(self, event: discord.ScheduledEvent) -> Optional[ActiveEvent]:
|
||||
return self._active_events.where(
|
||||
lambda x: x.event.id == event.id
|
||||
).single_or_default()
|
||||
|
||||
def get_active_event_by_channel_id(self, channel_id: int) -> Optional[ActiveEvent]:
|
||||
return self._active_events.where(
|
||||
lambda x: x.event.channel is not None and x.event.channel.id == channel_id
|
||||
).single_or_default()
|
||||
|
||||
def remove_event(self, event: ActiveEvent):
|
||||
if not self._active_events.contains(event):
|
||||
return
|
||||
|
||||
self._active_events.remove(event)
|
||||
|
||||
def give_xp_for_event_participation(
|
||||
self, member: discord.Member, active_event: ActiveEvent
|
||||
):
|
||||
server = self._servers.get_server_by_discord_id(member.guild.id)
|
||||
user = self._users.get_user_by_discord_id_and_server_id(member.id, server.id)
|
||||
if active_event.participants.any(lambda x: x.id == user.id):
|
||||
self._logger.debug(__name__, f"Module {type(self)} stopped")
|
||||
return
|
||||
|
||||
settings: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{server.discord_id}"
|
||||
)
|
||||
user.xp += settings.xp_per_event_participation
|
||||
self._users.update_user(user)
|
||||
self._db.save_changes()
|
||||
active_event.participants.append(user)
|
211
bot/src/modules/base/service/user_warnings_service.py
Normal file
211
bot/src/modules/base/service/user_warnings_service.py
Normal file
@@ -0,0 +1,211 @@
|
||||
import discord
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.database.context import DatabaseContextABC
|
||||
from cpl_core.logging import LoggerABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from cpl_translation import TranslatePipe
|
||||
|
||||
from bot_core.abc.message_service_abc import MessageServiceABC
|
||||
from bot_data.abc.level_repository_abc import LevelRepositoryABC
|
||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
from bot_data.abc.user_repository_abc import UserRepositoryABC
|
||||
from bot_data.abc.user_warnings_repository_abc import UserWarningsRepositoryABC
|
||||
from bot_data.model.server_config import ServerConfig
|
||||
from bot_data.model.user import User
|
||||
from bot_data.model.user_warnings import UserWarnings
|
||||
from modules.level.service.level_service import LevelService
|
||||
from modules.permission.abc.permission_service_abc import PermissionServiceABC
|
||||
|
||||
|
||||
class UserWarningsService:
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: LoggerABC,
|
||||
db: DatabaseContextABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
warnings: UserWarningsRepositoryABC,
|
||||
users: UserRepositoryABC,
|
||||
servers: ServerRepositoryABC,
|
||||
levels: LevelRepositoryABC,
|
||||
level_service: LevelService,
|
||||
message_service: MessageServiceABC,
|
||||
t: TranslatePipe,
|
||||
permissions: PermissionServiceABC,
|
||||
):
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._db = db
|
||||
self._bot = bot
|
||||
self._warnings = warnings
|
||||
self._users = users
|
||||
self._servers = servers
|
||||
self._levels = levels
|
||||
self._level_service = level_service
|
||||
self._message_service = message_service
|
||||
self._t = t
|
||||
self._permissions = permissions
|
||||
|
||||
async def notify_team(
|
||||
self, member: discord.Member, description: str, removed=False
|
||||
):
|
||||
try:
|
||||
settings: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{member.guild.id}"
|
||||
)
|
||||
channel = member.guild.get_channel(settings.team_channel_id)
|
||||
if removed:
|
||||
translation = self._t.transform(
|
||||
"modules.base.warnings.team_removed"
|
||||
).format(description, member.mention)
|
||||
else:
|
||||
translation = self._t.transform(
|
||||
"modules.base.warnings.team_warned"
|
||||
).format(member.mention, description)
|
||||
|
||||
self._bot.loop.create_task(
|
||||
self._message_service.send_channel_message(
|
||||
channel, translation, is_persistent=True
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
self._logger.error(
|
||||
__name__, f"Team notification for user warning failed!", e
|
||||
)
|
||||
|
||||
async def notify_user(self, member: discord.Member, message: str):
|
||||
try:
|
||||
# run as task to keep the interaction alive
|
||||
self._bot.loop.create_task(
|
||||
self._message_service.send_dm_message(message, member)
|
||||
)
|
||||
except Exception as e:
|
||||
self._logger.error(
|
||||
__name__, f"User notification for user warning failed!", e
|
||||
)
|
||||
|
||||
async def check_for_warnings(self, member: discord.Member, user: User):
|
||||
existing_warnings = self._warnings.get_user_warnings_by_user_id(user.id)
|
||||
|
||||
if existing_warnings.count() == 1:
|
||||
await self.notify_user(
|
||||
member, self._t.transform("modules.base.warnings.first")
|
||||
)
|
||||
|
||||
elif existing_warnings.count() == 2:
|
||||
server = self._servers.get_server_by_discord_id(member.guild.id)
|
||||
user = self._users.get_user_by_discord_id_and_server_id(
|
||||
member.id, server.id
|
||||
)
|
||||
level = self._level_service.get_level(user)
|
||||
levels = self._levels.get_levels_by_server_id(server.id).order_by(
|
||||
lambda l: l.min_xp
|
||||
)
|
||||
|
||||
new_level = levels.where(
|
||||
lambda l: l.min_xp < level.min_xp
|
||||
).last_or_default()
|
||||
if new_level is not None:
|
||||
user.xp = new_level.min_xp
|
||||
self._users.update_user(user)
|
||||
self._db.save_changes()
|
||||
await self.notify_user(
|
||||
member, self._t.transform("modules.base.warnings.second")
|
||||
)
|
||||
|
||||
elif existing_warnings.count() == 3:
|
||||
server = self._servers.get_server_by_discord_id(member.guild.id)
|
||||
user = self._users.get_user_by_discord_id_and_server_id(
|
||||
member.id, server.id
|
||||
)
|
||||
levels = self._levels.get_levels_by_server_id(server.id)
|
||||
|
||||
new_level = (
|
||||
levels.where(lambda l: l.min_xp > 0)
|
||||
.order_by(lambda l: l.min_xp)
|
||||
.last_or_default()
|
||||
)
|
||||
if new_level is not None:
|
||||
user.xp = new_level.min_xp
|
||||
self._users.update_user(user)
|
||||
self._db.save_changes()
|
||||
await self.notify_user(
|
||||
member, self._t.transform("modules.base.warnings.third")
|
||||
)
|
||||
|
||||
elif existing_warnings.count() >= 4:
|
||||
user.xp = 0
|
||||
self._users.update_user(user)
|
||||
self._db.save_changes()
|
||||
await self.notify_team(
|
||||
member,
|
||||
self._t.transform("modules.base.warnings.kick").format(member.mention),
|
||||
)
|
||||
await member.kick()
|
||||
|
||||
async def _notify_after_add(self, member: discord.Member, warning: UserWarnings):
|
||||
server = self._servers.get_server_by_discord_id(member.guild.id)
|
||||
user = self._users.get_user_by_discord_id_and_server_id(member.id, server.id)
|
||||
|
||||
await self.notify_user(
|
||||
member,
|
||||
self._t.transform("modules.base.warnings.warned").format(
|
||||
warning.description
|
||||
),
|
||||
)
|
||||
await self.notify_team(member, warning.description)
|
||||
await self.check_for_warnings(member, user)
|
||||
|
||||
def _add_warnings(
|
||||
self, member: discord.Member, description: str, author_id: int = None
|
||||
):
|
||||
server = self._servers.get_server_by_discord_id(member.guild.id)
|
||||
user = self._users.get_user_by_discord_id_and_server_id(member.id, server.id)
|
||||
|
||||
author = None
|
||||
if author_id is not None:
|
||||
author = self._users.get_user_by_discord_id_and_server_id(
|
||||
author_id, server.id
|
||||
)
|
||||
|
||||
warning = UserWarnings(description, user, author)
|
||||
self._warnings.add_user_warnings(warning)
|
||||
self._db.save_changes()
|
||||
return warning
|
||||
|
||||
def add_warnings(
|
||||
self, member: discord.Member, description: str, author_id: int = None
|
||||
):
|
||||
warning = self._add_warnings(member, description, author_id)
|
||||
self._bot.loop.create_task(self._notify_after_add(member, warning))
|
||||
|
||||
async def add_warnings_async(
|
||||
self, member: discord.Member, description: str, author_id: int = None
|
||||
):
|
||||
warning = self._add_warnings(member, description, author_id)
|
||||
await self._notify_after_add(member, warning)
|
||||
|
||||
async def _notify_after_remove(self, warning: UserWarnings):
|
||||
guild = self._bot.get_guild(warning.user.server.discord_id)
|
||||
member = guild.get_member(warning.user.discord_id)
|
||||
await self.notify_user(
|
||||
member,
|
||||
self._t.transform("modules.base.warnings.removed").format(
|
||||
warning.description
|
||||
),
|
||||
)
|
||||
await self.notify_team(member, warning.description, removed=True)
|
||||
|
||||
def _remove_warnings(self, id: int):
|
||||
warning = self._warnings.get_user_warnings_by_id(id)
|
||||
self._warnings.delete_user_warnings(warning)
|
||||
self._db.save_changes()
|
||||
return warning
|
||||
|
||||
def remove_warnings(self, id: int):
|
||||
warning = self._remove_warnings(id)
|
||||
self._bot.loop.create_task(self._notify_after_remove(warning))
|
||||
|
||||
async def remove_warnings_async(self, id: int):
|
||||
warning = self._remove_warnings(id)
|
||||
await self._notify_after_remove(warning)
|
26
bot/src/modules/base/thread/__init__.py
Normal file
26
bot/src/modules/base/thread/__init__.py
Normal 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.base.thread"
|
||||
__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")
|
26
bot/src/modules/boot_log/__init__.py
Normal file
26
bot/src/modules/boot_log/__init__.py
Normal 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.boot_log"
|
||||
__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")
|
44
bot/src/modules/boot_log/boot-log.json
Normal file
44
bot/src/modules/boot_log/boot-log.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"ProjectSettings": {
|
||||
"Name": "boot-log",
|
||||
"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.4",
|
||||
"PythonPath": {},
|
||||
"Classifiers": []
|
||||
},
|
||||
"BuildSettings": {
|
||||
"ProjectType": "library",
|
||||
"SourcePath": "",
|
||||
"OutputPath": "../../dist",
|
||||
"Main": "boot_log.main",
|
||||
"EntryPoint": "boot-log",
|
||||
"IncludePackageData": false,
|
||||
"Included": [],
|
||||
"Excluded": [
|
||||
"*/__pycache__",
|
||||
"*/logs",
|
||||
"*/tests"
|
||||
],
|
||||
"PackageData": {},
|
||||
"ProjectReferences": []
|
||||
}
|
||||
}
|
24
bot/src/modules/boot_log/boot_log_extension.py
Normal file
24
bot/src/modules/boot_log/boot_log_extension.py
Normal file
@@ -0,0 +1,24 @@
|
||||
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_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
|
||||
|
||||
class BootLogExtension(ApplicationExtensionABC):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
async def run(self, config: ConfigurationABC, services: ServiceProviderABC):
|
||||
feature_flags: FeatureFlagsSettings = config.get_configuration(
|
||||
FeatureFlagsSettings
|
||||
)
|
||||
if not feature_flags.get_flag(FeatureFlagsEnum.boot_log_module):
|
||||
return
|
||||
logger: LoggerABC = services.get_service(LoggerABC)
|
||||
logger.debug(__name__, "BootLog extension started")
|
||||
config.add_configuration("Bot_StartTime", str(datetime.now()))
|
28
bot/src/modules/boot_log/boot_log_module.py
Normal file
28
bot/src/modules/boot_log/boot_log_module.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.dependency_injection import ServiceCollectionABC
|
||||
from cpl_core.environment import ApplicationEnvironmentABC
|
||||
from cpl_discord.discord_event_types_enum import DiscordEventTypesEnum
|
||||
from cpl_discord.service.discord_collection_abc import DiscordCollectionABC
|
||||
|
||||
from bot_core.abc.module_abc import ModuleABC
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from modules.boot_log.boot_log_on_ready_event import BootLogOnReadyEvent
|
||||
|
||||
|
||||
class BootLogModule(ModuleABC):
|
||||
def __init__(self, dc: DiscordCollectionABC):
|
||||
ModuleABC.__init__(self, dc, FeatureFlagsEnum.boot_log_module)
|
||||
|
||||
def configure_configuration(
|
||||
self, config: ConfigurationABC, env: ApplicationEnvironmentABC
|
||||
):
|
||||
pass
|
||||
|
||||
def configure_services(
|
||||
self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC
|
||||
):
|
||||
# commands
|
||||
# events
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_ready.value, BootLogOnReadyEvent
|
||||
)
|
96
bot/src/modules/boot_log/boot_log_on_ready_event.py
Normal file
96
bot/src/modules/boot_log/boot_log_on_ready_event.py
Normal file
@@ -0,0 +1,96 @@
|
||||
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.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
from bot_data.model.server_config import ServerConfig
|
||||
from bot_data.model.technician_config import TechnicianConfig
|
||||
|
||||
|
||||
class BootLogOnReadyEvent(OnReadyABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: LoggerABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
message_service: MessageServiceABC,
|
||||
translate: TranslatePipe,
|
||||
tech_config: TechnicianConfig,
|
||||
):
|
||||
OnReadyABC.__init__(self)
|
||||
self._config = config
|
||||
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._message_service = message_service
|
||||
self._t = translate
|
||||
self._tech_config = tech_config
|
||||
|
||||
self._logger.info(__name__, f"Module {type(self)} loaded")
|
||||
|
||||
async def on_ready(self):
|
||||
self._logger.debug(__name__, f"Module {type(self)} started")
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
self._tech_config.feature_flags, FeatureFlagsEnum.boot_log_module
|
||||
):
|
||||
return
|
||||
|
||||
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 too long to start the bot!")
|
||||
|
||||
# print error if initialisation took way too long
|
||||
elif init_time >= 90:
|
||||
self._logger.error(
|
||||
__name__, "It takes far too long 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_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{g.id}"
|
||||
)
|
||||
if server_config is None:
|
||||
self._logger.error(__name__, f"Config ServerConfig_{g.id} not found!")
|
||||
return
|
||||
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.boot_log_module
|
||||
):
|
||||
continue
|
||||
|
||||
self._bot.loop.create_task(
|
||||
self._message_service.send_channel_message(
|
||||
self._bot.get_channel(server_config.login_message_channel_id),
|
||||
self._t.transform("modules.boot_log.login_message").format(
|
||||
init_time
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
self._config.add_configuration("IS_READY", "true")
|
||||
self._logger.info(__name__, "Bot is ready")
|
||||
self._logger.trace(__name__, f"Module {type(self)} stopped")
|
26
bot/src/modules/config/__init__.py
Normal file
26
bot/src/modules/config/__init__.py
Normal 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.config"
|
||||
__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")
|
46
bot/src/modules/config/config.json
Normal file
46
bot/src/modules/config/config.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"ProjectSettings": {
|
||||
"Name": "config",
|
||||
"Version": {
|
||||
"Major": "1",
|
||||
"Minor": "2",
|
||||
"Micro": "0"
|
||||
},
|
||||
"Author": "",
|
||||
"AuthorEmail": "",
|
||||
"Description": "",
|
||||
"LongDescription": "",
|
||||
"URL": "",
|
||||
"CopyrightDate": "",
|
||||
"CopyrightName": "",
|
||||
"LicenseName": "",
|
||||
"LicenseDescription": "",
|
||||
"Dependencies": [
|
||||
"cpl-core>=1.2.0"
|
||||
],
|
||||
"DevDependencies": [
|
||||
"cpl-cli>=1.2.0"
|
||||
],
|
||||
"PythonVersion": ">=3.10.4",
|
||||
"PythonPath": {
|
||||
"linux": ""
|
||||
},
|
||||
"Classifiers": []
|
||||
},
|
||||
"BuildSettings": {
|
||||
"ProjectType": "library",
|
||||
"SourcePath": "",
|
||||
"OutputPath": "../../dist",
|
||||
"Main": "config.main",
|
||||
"EntryPoint": "config",
|
||||
"IncludePackageData": false,
|
||||
"Included": [],
|
||||
"Excluded": [
|
||||
"*/__pycache__",
|
||||
"*/logs",
|
||||
"*/tests"
|
||||
],
|
||||
"PackageData": {},
|
||||
"ProjectReferences": []
|
||||
}
|
||||
}
|
24
bot/src/modules/config/config_extension.py
Normal file
24
bot/src/modules/config/config_extension.py
Normal file
@@ -0,0 +1,24 @@
|
||||
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_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
from bot_core.service.config_service import ConfigService
|
||||
|
||||
|
||||
class ConfigExtension(ApplicationExtensionABC):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
async def run(self, config: ConfigurationABC, services: ServiceProviderABC):
|
||||
feature_flags: FeatureFlagsSettings = config.get_configuration(
|
||||
FeatureFlagsSettings
|
||||
)
|
||||
if not feature_flags.get_flag(FeatureFlagsEnum.config_module):
|
||||
return
|
||||
logger: LoggerABC = services.get_service(LoggerABC)
|
||||
logger.debug(__name__, "Config extension started")
|
||||
config: ConfigService = services.get_service(ConfigService)
|
||||
config.reload_technician_config()
|
24
bot/src/modules/config/config_module.py
Normal file
24
bot/src/modules/config/config_module.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.dependency_injection import ServiceCollectionABC
|
||||
from cpl_core.environment import ApplicationEnvironmentABC
|
||||
from cpl_discord.discord_event_types_enum import DiscordEventTypesEnum
|
||||
from cpl_discord.service.discord_collection_abc import DiscordCollectionABC
|
||||
|
||||
from bot_core.abc.module_abc import ModuleABC
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from modules.config.events.config_on_ready_event import ConfigOnReadyEvent
|
||||
|
||||
|
||||
class ConfigModule(ModuleABC):
|
||||
def __init__(self, dc: DiscordCollectionABC):
|
||||
ModuleABC.__init__(self, dc, FeatureFlagsEnum.config_module)
|
||||
|
||||
def configure_configuration(
|
||||
self, config: ConfigurationABC, env: ApplicationEnvironmentABC
|
||||
):
|
||||
pass
|
||||
|
||||
def configure_services(
|
||||
self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC
|
||||
):
|
||||
services.add_transient(DiscordEventTypesEnum.on_ready.value, ConfigOnReadyEvent)
|
26
bot/src/modules/config/events/__init__.py
Normal file
26
bot/src/modules/config/events/__init__.py
Normal 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.config.events"
|
||||
__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")
|
31
bot/src/modules/config/events/config_on_ready_event.py
Normal file
31
bot/src/modules/config/events/config_on_ready_event.py
Normal file
@@ -0,0 +1,31 @@
|
||||
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 bot_core.service.config_service import ConfigService
|
||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
|
||||
|
||||
class ConfigOnReadyEvent(OnReadyABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: LoggerABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
servers: ServerRepositoryABC,
|
||||
config_service: ConfigService,
|
||||
):
|
||||
OnReadyABC.__init__(self)
|
||||
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._servers = servers
|
||||
self._config_service = config_service
|
||||
|
||||
async def on_ready(self):
|
||||
for guild in self._bot.guilds:
|
||||
await self._config_service.reload_server_config(
|
||||
self._servers.get_server_by_discord_id(guild.id)
|
||||
)
|
26
bot/src/modules/config/service/__init__.py
Normal file
26
bot/src/modules/config/service/__init__.py
Normal 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.config.service"
|
||||
__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")
|
26
bot/src/modules/database/__init__.py
Normal file
26
bot/src/modules/database/__init__.py
Normal 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.database"
|
||||
__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")
|
44
bot/src/modules/database/database.json
Normal file
44
bot/src/modules/database/database.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"ProjectSettings": {
|
||||
"Name": "database",
|
||||
"Version": {
|
||||
"Major": "1",
|
||||
"Minor": "2",
|
||||
"Micro": "0"
|
||||
},
|
||||
"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.12.0"
|
||||
],
|
||||
"DevDependencies": [
|
||||
"cpl-cli==2022.12.0"
|
||||
],
|
||||
"PythonVersion": ">=3.10.4",
|
||||
"PythonPath": {},
|
||||
"Classifiers": []
|
||||
},
|
||||
"BuildSettings": {
|
||||
"ProjectType": "library",
|
||||
"SourcePath": "",
|
||||
"OutputPath": "../../dist",
|
||||
"Main": "database.main",
|
||||
"EntryPoint": "database",
|
||||
"IncludePackageData": false,
|
||||
"Included": [],
|
||||
"Excluded": [
|
||||
"*/__pycache__",
|
||||
"*/logs",
|
||||
"*/tests"
|
||||
],
|
||||
"PackageData": {},
|
||||
"ProjectReferences": []
|
||||
}
|
||||
}
|
32
bot/src/modules/database/database_extension.py
Normal file
32
bot/src/modules/database/database_extension.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import sys
|
||||
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_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
from bot_core.logging.database_logger import DatabaseLogger
|
||||
from bot_data.service.migration_service import MigrationService
|
||||
|
||||
|
||||
class DatabaseExtension(ApplicationExtensionABC):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
async def run(self, config: ConfigurationABC, services: ServiceProviderABC):
|
||||
feature_flags: FeatureFlagsSettings = config.get_configuration(
|
||||
FeatureFlagsSettings
|
||||
)
|
||||
if not feature_flags.get_flag(FeatureFlagsEnum.data_module):
|
||||
return
|
||||
logger: LoggerABC = services.get_service(DatabaseLogger)
|
||||
logger.debug(__name__, "Database extension started")
|
||||
config.add_configuration("Database_StartTime", str(datetime.now()))
|
||||
migrations: MigrationService = services.get_service(MigrationService)
|
||||
migrations.migrate()
|
||||
if config.get_configuration("MIGRATION_ONLY"):
|
||||
logger.warn(__name__, "Migrations finished. Stopping application...")
|
||||
sys.exit()
|
28
bot/src/modules/database/database_module.py
Normal file
28
bot/src/modules/database/database_module.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.dependency_injection import ServiceCollectionABC
|
||||
from cpl_core.environment import ApplicationEnvironmentABC
|
||||
from cpl_discord.discord_event_types_enum import DiscordEventTypesEnum
|
||||
from cpl_discord.service.discord_collection_abc import DiscordCollectionABC
|
||||
|
||||
from bot_core.abc.module_abc import ModuleABC
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from modules.database.database_on_ready_event import DatabaseOnReadyEvent
|
||||
|
||||
|
||||
class DatabaseModule(ModuleABC):
|
||||
def __init__(self, dc: DiscordCollectionABC):
|
||||
ModuleABC.__init__(self, dc, FeatureFlagsEnum.database_module)
|
||||
|
||||
def configure_configuration(
|
||||
self, config: ConfigurationABC, env: ApplicationEnvironmentABC
|
||||
):
|
||||
pass
|
||||
|
||||
def configure_services(
|
||||
self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC
|
||||
):
|
||||
# commands
|
||||
# events
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_ready.value, DatabaseOnReadyEvent
|
||||
)
|
62
bot/src/modules/database/database_on_ready_event.py
Normal file
62
bot/src/modules/database/database_on_ready_event.py
Normal file
@@ -0,0 +1,62 @@
|
||||
from datetime import datetime
|
||||
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_discord.events import OnReadyABC
|
||||
|
||||
from bot_core.logging.database_logger import DatabaseLogger
|
||||
from bot_core.service.data_integrity_service import DataIntegrityService
|
||||
from bot_data.service.seeder_service import SeederService
|
||||
|
||||
|
||||
class DatabaseOnReadyEvent(OnReadyABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: DatabaseLogger,
|
||||
seeder: SeederService,
|
||||
data_integrity: DataIntegrityService,
|
||||
):
|
||||
self._config = config
|
||||
|
||||
self._logger = logger
|
||||
self._seeder = seeder
|
||||
self._data_integrity = data_integrity
|
||||
|
||||
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 too long to prepare the database!"
|
||||
)
|
||||
|
||||
# print error if initialisation took way too long
|
||||
elif init_time >= 90:
|
||||
self._logger.error(
|
||||
__name__, "It takes far too long to prepare the database!!!"
|
||||
)
|
||||
except Exception as e: #
|
||||
self._logger.error(__name__, "Database init time calculation failed", e)
|
||||
return
|
||||
|
||||
async def on_ready(self):
|
||||
self._logger.debug(__name__, f"Module {type(self)} started")
|
||||
|
||||
await self._data_integrity.check_data_integrity()
|
||||
await self._seeder.seed()
|
||||
|
||||
self._validate_init_time()
|
||||
self._logger.trace(__name__, f"Module {type(self)} stopped")
|
26
bot/src/modules/level/__init__.py
Normal file
26
bot/src/modules/level/__init__.py
Normal 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.level"
|
||||
__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")
|
26
bot/src/modules/level/command/__init__.py
Normal file
26
bot/src/modules/level/command/__init__.py
Normal 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.level.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")
|
676
bot/src/modules/level/command/level_group.py
Normal file
676
bot/src/modules/level/command/level_group.py
Normal file
@@ -0,0 +1,676 @@
|
||||
from typing import List as TList
|
||||
|
||||
import discord
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.database.context import DatabaseContextABC
|
||||
from cpl_discord.command import DiscordCommandABC
|
||||
from cpl_discord.container import Guild, Role
|
||||
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.level_repository_abc import LevelRepositoryABC
|
||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
from bot_data.abc.user_repository_abc import UserRepositoryABC
|
||||
from bot_data.model.level import Level
|
||||
from bot_data.model.server_config import ServerConfig
|
||||
from modules.level.level_seeder import LevelSeeder
|
||||
from modules.level.service.level_service import LevelService
|
||||
from modules.permission.abc.permission_service_abc import PermissionServiceABC
|
||||
|
||||
|
||||
class LevelGroup(DiscordCommandABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: CommandLogger,
|
||||
message_service: MessageServiceABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
client_utils: ClientUtilsABC,
|
||||
permission_service: PermissionServiceABC,
|
||||
translate: TranslatePipe,
|
||||
db: DatabaseContextABC,
|
||||
levels: LevelRepositoryABC,
|
||||
servers: ServerRepositoryABC,
|
||||
users: UserRepositoryABC,
|
||||
level_service: LevelService,
|
||||
level_seeder: LevelSeeder,
|
||||
):
|
||||
DiscordCommandABC.__init__(self)
|
||||
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._message_service = message_service
|
||||
self._bot = bot
|
||||
self._client_utils = client_utils
|
||||
self._permissions = permission_service
|
||||
self._t = translate
|
||||
self._db = db
|
||||
self._levels = levels
|
||||
self._servers = servers
|
||||
self._users = users
|
||||
|
||||
self._level_service = level_service
|
||||
self._level_seeder = level_seeder
|
||||
|
||||
self._colors = [
|
||||
("blue", discord.Colour.blue().to_rgb()),
|
||||
("dark_blue", discord.Colour.dark_blue().to_rgb()),
|
||||
("dark_gold", discord.Colour.dark_gold().to_rgb()),
|
||||
("dark_gray", discord.Colour.dark_gray().to_rgb()),
|
||||
("dark_green", discord.Colour.dark_green().to_rgb()),
|
||||
("dark_grey", discord.Colour.dark_grey().to_rgb()),
|
||||
("dark_magenta", discord.Colour.dark_magenta().to_rgb()),
|
||||
("dark_orange", discord.Colour.dark_orange().to_rgb()),
|
||||
("dark_purple", discord.Colour.dark_purple().to_rgb()),
|
||||
("dark_red", discord.Colour.dark_red().to_rgb()),
|
||||
("dark_teal", discord.Colour.dark_teal().to_rgb()),
|
||||
("default", discord.Colour.default().to_rgb()),
|
||||
("gold", discord.Colour.gold().to_rgb()),
|
||||
("green", discord.Colour.green().to_rgb()),
|
||||
("greyple", discord.Colour.greyple().to_rgb()),
|
||||
("light_grey", discord.Colour.light_grey().to_rgb()),
|
||||
("magenta", discord.Colour.magenta().to_rgb()),
|
||||
("orange", discord.Colour.orange().to_rgb()),
|
||||
("purple", discord.Colour.purple().to_rgb()),
|
||||
("red", discord.Colour.red().to_rgb()),
|
||||
("teal", discord.Colour.teal().to_rgb()),
|
||||
("yellow", discord.Colour.yellow().to_rgb()),
|
||||
]
|
||||
|
||||
self._logger.trace(__name__, f"Loaded command service: {type(self).__name__}")
|
||||
|
||||
async def _seed_levels(self, ctx: Context):
|
||||
# send message to ctx.channel because send_ctx_msg resolves ctx
|
||||
try:
|
||||
start = await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.level.seeding_started"),
|
||||
is_persistent=True,
|
||||
)
|
||||
await self._level_seeder.seed()
|
||||
|
||||
end = await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.level.seeding_finished"),
|
||||
is_persistent=True,
|
||||
)
|
||||
await self._message_service.delete_message(start)
|
||||
await self._message_service.delete_message(end)
|
||||
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Level seeding failed", e)
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.level.seeding_failed")
|
||||
)
|
||||
|
||||
async def _level_auto_complete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
server = self._servers.get_server_by_discord_id(interaction.guild.id)
|
||||
levels = self._levels.get_levels_by_server_id(server.id).select(
|
||||
lambda l: l.name
|
||||
)
|
||||
return [
|
||||
app_commands.Choice(name=level, value=level)
|
||||
for level in self._client_utils.get_auto_complete_list(levels, current)
|
||||
]
|
||||
|
||||
@commands.hybrid_group()
|
||||
@commands.guild_only()
|
||||
async def level(self, ctx: Context):
|
||||
pass
|
||||
|
||||
@level.command(alias="levels")
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def list(self, ctx: Context, wait: int = None):
|
||||
self._logger.debug(__name__, f"Received command level list {ctx}")
|
||||
if ctx.guild is None:
|
||||
return
|
||||
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{ctx.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.level_module
|
||||
):
|
||||
return
|
||||
|
||||
server = self._servers.get_server_by_discord_id(ctx.guild.id)
|
||||
levels = self._levels.get_levels_by_server_id(server.id)
|
||||
if levels.count() < 1:
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.level.error.nothing_found")
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command level list")
|
||||
return
|
||||
|
||||
level_name = ""
|
||||
xp = ""
|
||||
permissions = ""
|
||||
for level in levels:
|
||||
level_name += f"\n{level.name}"
|
||||
xp += f"\n{level.min_xp}"
|
||||
permissions += f"\n{level.permissions}"
|
||||
|
||||
embed = discord.Embed(
|
||||
title=self._t.transform("modules.level.list.title"),
|
||||
description=self._t.transform("modules.level.list.description"),
|
||||
color=int("ef9d0d", 16),
|
||||
)
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.level.list.name"),
|
||||
value=level_name,
|
||||
inline=True,
|
||||
)
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.level.list.min_xp"), value=xp, inline=True
|
||||
)
|
||||
embed.add_field(
|
||||
name=self._t.transform("modules.level.list.permission_int"),
|
||||
value=permissions,
|
||||
inline=True,
|
||||
)
|
||||
await self._message_service.send_ctx_msg(ctx, embed, wait_before_delete=wait)
|
||||
self._logger.trace(__name__, f"Finished command level list")
|
||||
|
||||
@level.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_admin()
|
||||
async def create(
|
||||
self, ctx: Context, name: str, color: str, min_xp: int, permissions: int
|
||||
):
|
||||
self._logger.debug(__name__, f"Received command level create {ctx}")
|
||||
if ctx.guild is None:
|
||||
return
|
||||
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{ctx.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.level_module
|
||||
):
|
||||
return
|
||||
|
||||
try:
|
||||
color = hex(discord.Colour.from_str(color).value)
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Error parsing color {color}", e)
|
||||
return
|
||||
|
||||
try:
|
||||
permissions = discord.Permissions(permissions).value
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Error parsing permissions {permissions}", e)
|
||||
return
|
||||
|
||||
if ctx.guild is None:
|
||||
return
|
||||
|
||||
server = self._servers.get_server_by_discord_id(ctx.guild.id)
|
||||
level = Level(name, color, min_xp, permissions, server)
|
||||
levels = self._levels.get_levels_by_server_id(server.id)
|
||||
|
||||
if levels.where(lambda l: l.name == level.name).first_or_default() is not None:
|
||||
self._logger.debug(__name__, f"Level with name {level.name} already exists")
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform(
|
||||
"modules.level.error.level_with_name_already_exists"
|
||||
).format(level.name),
|
||||
)
|
||||
elif (
|
||||
levels.where(lambda l: l.min_xp == level.min_xp).first_or_default()
|
||||
is not None
|
||||
):
|
||||
self._logger.debug(
|
||||
__name__,
|
||||
f"Level with min_xp {level.min_xp} already exists {level.name}",
|
||||
)
|
||||
found_level = levels.where(
|
||||
lambda l: l.min_xp == level.min_xp
|
||||
).first_or_default()
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform(
|
||||
"modules.level.error.level_with_xp_already_exists"
|
||||
).format(found_level.name, found_level.min_xp),
|
||||
)
|
||||
else:
|
||||
try:
|
||||
self._levels.add_level(level)
|
||||
self._db.save_changes()
|
||||
self._logger.info(
|
||||
__name__,
|
||||
f"Saved level {name} with color {color}, min_xp {min_xp} and permissions {permissions}",
|
||||
)
|
||||
except Exception as e:
|
||||
self._logger.error(
|
||||
__name__,
|
||||
f"Could not save level {name} with color {color}, min_xp {min_xp} and permissions {permissions}",
|
||||
e,
|
||||
)
|
||||
else:
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.level.create.created").format(
|
||||
name, permissions
|
||||
),
|
||||
)
|
||||
await self._seed_levels(ctx)
|
||||
|
||||
self._logger.trace(__name__, f"Finished command level create")
|
||||
|
||||
@create.autocomplete("color")
|
||||
async def create_color_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
# value in rg format see:
|
||||
# https://discordpy.readthedocs.io/en/latest/api.html#discord.Colour.to_rgb
|
||||
return [
|
||||
app_commands.Choice(
|
||||
name=self._t.transform(f"common.colors.{color}"),
|
||||
value=f"rgb({code[0]}, {code[1]}, {code[2]})",
|
||||
)
|
||||
for color, code in self._colors
|
||||
]
|
||||
|
||||
@level.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_admin()
|
||||
async def edit(
|
||||
self,
|
||||
ctx: Context,
|
||||
level: str,
|
||||
name: str = None,
|
||||
color: str = None,
|
||||
min_xp: int = None,
|
||||
permissions: int = None,
|
||||
):
|
||||
self._logger.debug(__name__, f"Received command level edit {ctx}")
|
||||
if ctx.guild is None:
|
||||
return
|
||||
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{ctx.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.level_module
|
||||
):
|
||||
return
|
||||
|
||||
server = self._servers.get_server_by_discord_id(ctx.guild.id)
|
||||
level_from_db = (
|
||||
self._levels.get_levels_by_server_id(server.id)
|
||||
.where(lambda l: l.name == level)
|
||||
.single_or_default()
|
||||
)
|
||||
if level_from_db is None:
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.level.edit.not_found").format(level)
|
||||
)
|
||||
return
|
||||
|
||||
guild: Guild = self._bot.guilds.where(lambda g: g == ctx.guild).single()
|
||||
role: Role = guild.roles.where(lambda r: r.name == level_from_db.name).single()
|
||||
|
||||
if name is not None:
|
||||
level_from_db.name = name
|
||||
|
||||
if color is not None:
|
||||
try:
|
||||
level_from_db.color = hex(discord.Colour.from_str(color).value)
|
||||
except Exception as e:
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.level.edit.color_invalid").format(color),
|
||||
)
|
||||
self._logger.error(__name__, f"Error parsing color {color}", e)
|
||||
return
|
||||
|
||||
if min_xp is not None:
|
||||
level_from_db.min_xp = min_xp
|
||||
|
||||
if permissions is not None:
|
||||
try:
|
||||
level_from_db.permissions = discord.Permissions(permissions).value
|
||||
except Exception as e:
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.level.edit.permission_invalid").format(
|
||||
permissions
|
||||
),
|
||||
)
|
||||
self._logger.error(
|
||||
__name__, f"Error parsing permissions {permissions}", e
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
self._levels.update_level(level_from_db)
|
||||
self._db.save_changes()
|
||||
await role.edit(
|
||||
name=level_from_db.name,
|
||||
permissions=discord.Permissions(level_from_db.permissions),
|
||||
colour=discord.Colour(int(level_from_db.color, 16)),
|
||||
)
|
||||
self._logger.info(
|
||||
__name__,
|
||||
f"Saved level {level_from_db.name} with color {level_from_db.color}, min_xp {level_from_db.min_xp} and permissions {level_from_db.permissions}",
|
||||
)
|
||||
except Exception as e:
|
||||
self._logger.error(
|
||||
__name__,
|
||||
f"Could not save level {level} with color {color}, min_xp {min_xp} and permissions {permissions}",
|
||||
e,
|
||||
)
|
||||
else:
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.level.edit.edited").format(level)
|
||||
)
|
||||
await self._seed_levels(ctx)
|
||||
|
||||
self._logger.trace(__name__, f"Finished command level edit")
|
||||
|
||||
@edit.autocomplete("level")
|
||||
async def edit_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
return await self._level_auto_complete(interaction, current)
|
||||
|
||||
@edit.autocomplete("color")
|
||||
async def edit_color_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
# value in rg format see:
|
||||
# https://discordpy.readthedocs.io/en/latest/api.html#discord.Colour.to_rgb
|
||||
return [
|
||||
app_commands.Choice(
|
||||
name=self._t.transform(f"common.colors.{color}"),
|
||||
value=f"rgb({code[0]}, {code[1]}, {code[2]})",
|
||||
)
|
||||
for color, code in self._colors
|
||||
]
|
||||
|
||||
@level.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_admin()
|
||||
async def remove(self, ctx: Context, level: str):
|
||||
self._logger.debug(__name__, f"Received command level remove {ctx}")
|
||||
if ctx.guild is None:
|
||||
return
|
||||
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{ctx.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.level_module
|
||||
):
|
||||
return
|
||||
|
||||
server = self._servers.get_server_by_discord_id(ctx.guild.id)
|
||||
level_from_db = (
|
||||
self._levels.get_levels_by_server_id(server.id)
|
||||
.where(lambda l: l.name == level)
|
||||
.first_or_default()
|
||||
)
|
||||
if level_from_db is None:
|
||||
self._logger.debug(__name__, f"level {level} not found")
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.level.remove.error.not_found").format(level),
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command level remove")
|
||||
return
|
||||
|
||||
try:
|
||||
self._levels.delete_level(level_from_db)
|
||||
self._db.save_changes()
|
||||
guild: Guild = self._bot.guilds.where(lambda g: g == ctx.guild).single()
|
||||
role: Role = guild.roles.where(
|
||||
lambda r: r.name == level
|
||||
).single_or_default()
|
||||
if role is not None:
|
||||
await role.delete()
|
||||
self._logger.info(__name__, f"Removed level {level}")
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Could not remove level {level}", e)
|
||||
else:
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.level.remove.success").format(level)
|
||||
)
|
||||
await self._seed_levels(ctx)
|
||||
|
||||
self._logger.trace(__name__, f"Finished command level remove")
|
||||
|
||||
@remove.autocomplete("level")
|
||||
async def remove_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
return await self._level_auto_complete(interaction, current)
|
||||
|
||||
@level.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def down(self, ctx: Context, member: discord.Member):
|
||||
self._logger.debug(__name__, f"Received command level down {ctx} {member}")
|
||||
if ctx.guild is None:
|
||||
return
|
||||
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{ctx.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.level_module
|
||||
):
|
||||
return
|
||||
|
||||
if member.bot:
|
||||
return
|
||||
|
||||
server = self._servers.get_server_by_discord_id(ctx.guild.id)
|
||||
user = self._users.get_user_by_discord_id_and_server_id(member.id, server.id)
|
||||
level = self._level_service.get_level(user)
|
||||
levels = self._levels.get_levels_by_server_id(server.id).order_by(
|
||||
lambda l: l.min_xp
|
||||
)
|
||||
|
||||
if level == levels.first():
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.level.down.already_first").format(
|
||||
member.mention
|
||||
),
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command level down")
|
||||
return
|
||||
|
||||
try:
|
||||
new_level = levels.where(lambda l: l.min_xp < level.min_xp).last()
|
||||
user.xp = new_level.min_xp
|
||||
self._users.update_user(user)
|
||||
self._db.save_changes()
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.level.down.success").format(
|
||||
member.mention, new_level.name
|
||||
),
|
||||
)
|
||||
await self._level_service.set_level(user)
|
||||
except Exception as e:
|
||||
self._logger.error(
|
||||
__name__, f"Cannot level down {member.name} with level {level.name}", e
|
||||
)
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.level.down.failed").format(member.mention),
|
||||
)
|
||||
|
||||
self._logger.trace(__name__, f"Finished command level down")
|
||||
|
||||
@level.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def up(self, ctx: Context, member: discord.Member):
|
||||
self._logger.debug(__name__, f"Received command level up {ctx} {member}")
|
||||
if ctx.guild is None:
|
||||
return
|
||||
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{ctx.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.level_module
|
||||
):
|
||||
return
|
||||
|
||||
if member.bot:
|
||||
return
|
||||
|
||||
server = self._servers.get_server_by_discord_id(ctx.guild.id)
|
||||
user = self._users.get_user_by_discord_id_and_server_id(member.id, server.id)
|
||||
level = self._level_service.get_level(user)
|
||||
levels = self._levels.get_levels_by_server_id(server.id).order_by(
|
||||
lambda l: l.min_xp
|
||||
)
|
||||
|
||||
if level.name == levels.last().name:
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.level.up.already_last").format(
|
||||
member.mention
|
||||
),
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command level up")
|
||||
return
|
||||
|
||||
try:
|
||||
new_level = levels.where(lambda l: l.min_xp > level.min_xp).first()
|
||||
user.xp = new_level.min_xp
|
||||
self._users.update_user(user)
|
||||
self._db.save_changes()
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.level.up.success").format(
|
||||
member.mention, new_level.name
|
||||
),
|
||||
)
|
||||
await self._level_service.set_level(user)
|
||||
except Exception as e:
|
||||
self._logger.error(
|
||||
__name__, f"Cannot level up {member.name} with level {level.name}", e
|
||||
)
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.level.up.failed").format(member.mention)
|
||||
)
|
||||
|
||||
self._logger.trace(__name__, f"Finished command level up")
|
||||
|
||||
@level.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def set(self, ctx: Context, member: discord.Member, level: str):
|
||||
self._logger.debug(__name__, f"Received command level up {ctx} {member}")
|
||||
if ctx.guild is None:
|
||||
return
|
||||
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{ctx.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.level_module
|
||||
):
|
||||
return
|
||||
|
||||
if member.bot:
|
||||
return
|
||||
|
||||
server = self._servers.get_server_by_discord_id(ctx.guild.id)
|
||||
user = self._users.get_user_by_discord_id_and_server_id(member.id, server.id)
|
||||
current_level = self._level_service.get_level(user)
|
||||
new_level = (
|
||||
self._levels.get_levels_by_server_id(server.id)
|
||||
.where(lambda l: l.name == level)
|
||||
.single_or_default()
|
||||
)
|
||||
|
||||
if new_level is None:
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx, self._t.transform("modules.level.set.not_found").format(level)
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command level set")
|
||||
return
|
||||
|
||||
if current_level.name == level:
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.level.set.already_level").format(
|
||||
member.mention, level
|
||||
),
|
||||
)
|
||||
self._logger.trace(__name__, f"Finished command level set")
|
||||
return
|
||||
|
||||
try:
|
||||
user.xp = new_level.min_xp
|
||||
self._users.update_user(user)
|
||||
self._db.save_changes()
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.level.set.success").format(
|
||||
member.mention, new_level.name
|
||||
),
|
||||
)
|
||||
await self._level_service.set_level(user)
|
||||
except Exception as e:
|
||||
self._logger.error(
|
||||
__name__, f"Cannot set level {level} for {member.name}", e
|
||||
)
|
||||
await self._message_service.send_ctx_msg(
|
||||
ctx,
|
||||
self._t.transform("modules.level.set.failed").format(member.mention),
|
||||
)
|
||||
|
||||
self._logger.trace(__name__, f"Finished command level set")
|
||||
|
||||
@set.autocomplete("level")
|
||||
async def set_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> TList[app_commands.Choice[str]]:
|
||||
return await self._level_auto_complete(interaction, current)
|
||||
|
||||
@level.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def reload(self, ctx: Context):
|
||||
self._logger.debug(__name__, f"Received command level reload {ctx}")
|
||||
if ctx.guild is None:
|
||||
return
|
||||
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{ctx.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.level_module
|
||||
):
|
||||
return
|
||||
|
||||
await self._seed_levels(ctx)
|
||||
self._logger.trace(__name__, f"Finished command level reload")
|
26
bot/src/modules/level/configuration/__init__.py
Normal file
26
bot/src/modules/level/configuration/__init__.py
Normal 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.level.configuration"
|
||||
__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")
|
@@ -0,0 +1,33 @@
|
||||
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
from cpl_query.extension import List
|
||||
|
||||
from bot_data.model.level import Level
|
||||
|
||||
|
||||
class DefaultLevelSettings(ConfigurationModelABC):
|
||||
def __init__(self, level_header: str = None, levels: list = None):
|
||||
ConfigurationModelABC.__init__(self)
|
||||
|
||||
self._levels = List(Level)
|
||||
self._level_header = level_header
|
||||
|
||||
if levels is None:
|
||||
return
|
||||
for level in levels:
|
||||
self._levels.append(
|
||||
Level(
|
||||
level["Name"],
|
||||
level["Color"],
|
||||
int(level["MinXp"]),
|
||||
int(level["Permissions"]),
|
||||
None,
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def levels(self) -> List[Level]:
|
||||
return self._levels
|
||||
|
||||
@property
|
||||
def level_header(self) -> str:
|
||||
return self._level_header
|
31
bot/src/modules/level/default-level.json
Normal file
31
bot/src/modules/level/default-level.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"DefaultLevel": {
|
||||
"LevelHeader": "~~~ Level ~~~",
|
||||
"Levels": [
|
||||
{
|
||||
"Name": "Newbie",
|
||||
"Color": "0x1abc9c",
|
||||
"MinXp": 0,
|
||||
"Permissions": 968552209984
|
||||
},
|
||||
{
|
||||
"Name": "Keks",
|
||||
"Color": "0x2ecc71",
|
||||
"MinXp": 100,
|
||||
"Permissions": 1002928856640
|
||||
},
|
||||
{
|
||||
"Name": "Doppelkeks",
|
||||
"Color": "0x3498db",
|
||||
"MinXp": 200,
|
||||
"Permissions": 1071849660224
|
||||
},
|
||||
{
|
||||
"Name": "Auror",
|
||||
"Color": "0xf1c40f",
|
||||
"MinXp": 300,
|
||||
"Permissions": 1089042120513
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
26
bot/src/modules/level/events/__init__.py
Normal file
26
bot/src/modules/level/events/__init__.py
Normal 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.level.events"
|
||||
__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")
|
33
bot/src/modules/level/events/level_on_member_join_event.py
Normal file
33
bot/src/modules/level/events/level_on_member_join_event.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import discord
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_discord.events import OnMemberJoinABC
|
||||
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
from bot_core.helper.event_checks import EventChecks
|
||||
from bot_core.logging.message_logger import MessageLogger
|
||||
from bot_data.model.server_config import ServerConfig
|
||||
from modules.level.service.level_service import LevelService
|
||||
|
||||
|
||||
class LevelOnMemberJoinEvent(OnMemberJoinABC):
|
||||
def __init__(
|
||||
self, config: ConfigurationABC, logger: MessageLogger, level: LevelService
|
||||
):
|
||||
OnMemberJoinABC.__init__(self)
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._level = level
|
||||
|
||||
@EventChecks.check_is_ready()
|
||||
async def on_member_join(self, member: discord.Member):
|
||||
self._logger.debug(__name__, f"Module {type(self)} started")
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{member.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.level_module
|
||||
):
|
||||
return
|
||||
|
||||
await self._level.check_level(member)
|
42
bot/src/modules/level/events/level_on_message_event.py
Normal file
42
bot/src/modules/level/events/level_on_message_event.py
Normal file
@@ -0,0 +1,42 @@
|
||||
import discord
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_discord.events import OnMessageABC
|
||||
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
from bot_core.helper.event_checks import EventChecks
|
||||
from bot_core.logging.message_logger import MessageLogger
|
||||
from bot_data.model.server_config import ServerConfig
|
||||
from modules.level.service.level_service import LevelService
|
||||
|
||||
|
||||
class LevelOnMessageEvent(OnMessageABC):
|
||||
def __init__(
|
||||
self, config: ConfigurationABC, logger: MessageLogger, level: LevelService
|
||||
):
|
||||
OnMessageABC.__init__(self)
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._level = level
|
||||
|
||||
@EventChecks.check_is_ready()
|
||||
async def on_message(self, message: discord.Message):
|
||||
self._logger.debug(__name__, f"Module {type(self)} started")
|
||||
if message.guild is None:
|
||||
return
|
||||
|
||||
if message.author.bot:
|
||||
return
|
||||
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{message.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.level_module
|
||||
):
|
||||
return
|
||||
|
||||
try:
|
||||
await self._level.check_level(message.author)
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Level check by message failed", e)
|
@@ -0,0 +1,48 @@
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.logging import LoggerABC
|
||||
from cpl_discord.events.on_raw_reaction_add_abc import OnRawReactionAddABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from discord import RawReactionActionEvent
|
||||
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
from bot_core.helper.event_checks import EventChecks
|
||||
from bot_data.model.server_config import ServerConfig
|
||||
from modules.level.service.level_service import LevelService
|
||||
|
||||
|
||||
class LevelOnRawReactionAddEvent(OnRawReactionAddABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: LoggerABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
level: LevelService,
|
||||
):
|
||||
OnRawReactionAddABC.__init__(self)
|
||||
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._level = level
|
||||
|
||||
@EventChecks.check_is_ready()
|
||||
async def on_raw_reaction_add(self, payload: RawReactionActionEvent):
|
||||
self._logger.debug(__name__, f"Module {type(self)} started")
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{payload.guild_id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.level_module
|
||||
):
|
||||
return
|
||||
|
||||
try:
|
||||
self._logger.trace(__name__, f"Handle reaction {payload} for level")
|
||||
|
||||
guild = self._bot.get_guild(payload.guild_id)
|
||||
member = guild.get_member(payload.user_id)
|
||||
|
||||
await self._level.check_level(member)
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Level check by message failed", e)
|
@@ -0,0 +1,48 @@
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.logging import LoggerABC
|
||||
from cpl_discord.events.on_raw_reaction_remove_abc import OnRawReactionRemoveABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from discord import RawReactionActionEvent
|
||||
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
from bot_core.helper.event_checks import EventChecks
|
||||
from bot_data.model.server_config import ServerConfig
|
||||
from modules.level.service.level_service import LevelService
|
||||
|
||||
|
||||
class LevelOnRawReactionRemoveEvent(OnRawReactionRemoveABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: LoggerABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
level: LevelService,
|
||||
):
|
||||
OnRawReactionRemoveABC.__init__(self)
|
||||
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._level = level
|
||||
|
||||
@EventChecks.check_is_ready()
|
||||
async def on_raw_reaction_remove(self, payload: RawReactionActionEvent):
|
||||
self._logger.debug(__name__, f"Module {type(self)} started")
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{payload.guild_id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.level_module
|
||||
):
|
||||
return
|
||||
|
||||
try:
|
||||
self._logger.trace(__name__, f"Handle reaction {payload} for level")
|
||||
|
||||
guild = self._bot.get_guild(payload.guild_id)
|
||||
member = guild.get_member(payload.user_id)
|
||||
|
||||
await self._level.check_level(member)
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Level check by message failed", e)
|
@@ -0,0 +1,40 @@
|
||||
import discord
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.logging import LoggerABC
|
||||
from cpl_discord.events import OnVoiceStateUpdateABC
|
||||
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
from bot_core.helper.event_checks import EventChecks
|
||||
from bot_data.model.server_config import ServerConfig
|
||||
from modules.level.service.level_service import LevelService
|
||||
|
||||
|
||||
class LevelOnVoiceStateUpdateEvent(OnVoiceStateUpdateABC):
|
||||
def __init__(
|
||||
self, config: ConfigurationABC, logger: LoggerABC, level: LevelService
|
||||
):
|
||||
OnVoiceStateUpdateABC.__init__(self)
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._level = level
|
||||
|
||||
self._logger.info(__name__, f"Module {type(self)} loaded")
|
||||
|
||||
@EventChecks.check_is_ready()
|
||||
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")
|
||||
server_config: ServerConfig = self._config.get_configuration(
|
||||
f"ServerConfig_{member.guild.id}"
|
||||
)
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
server_config.feature_flags, FeatureFlagsEnum.level_module
|
||||
):
|
||||
return
|
||||
|
||||
await self._level.check_level(member)
|
44
bot/src/modules/level/level.json
Normal file
44
bot/src/modules/level/level.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"ProjectSettings": {
|
||||
"Name": "level",
|
||||
"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.4",
|
||||
"PythonPath": {},
|
||||
"Classifiers": []
|
||||
},
|
||||
"BuildSettings": {
|
||||
"ProjectType": "library",
|
||||
"SourcePath": "",
|
||||
"OutputPath": "../../dist",
|
||||
"Main": "level.main",
|
||||
"EntryPoint": "level",
|
||||
"IncludePackageData": false,
|
||||
"Included": [],
|
||||
"Excluded": [
|
||||
"*/__pycache__",
|
||||
"*/logs",
|
||||
"*/tests"
|
||||
],
|
||||
"PackageData": {},
|
||||
"ProjectReferences": []
|
||||
}
|
||||
}
|
66
bot/src/modules/level/level_module.py
Normal file
66
bot/src/modules/level/level_module.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import os
|
||||
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.dependency_injection import ServiceCollectionABC
|
||||
from cpl_core.environment import ApplicationEnvironmentABC
|
||||
from cpl_discord.discord_event_types_enum import DiscordEventTypesEnum
|
||||
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.level.command.level_group import LevelGroup
|
||||
from modules.level.events.level_on_member_join_event import LevelOnMemberJoinEvent
|
||||
from modules.level.events.level_on_message_event import LevelOnMessageEvent
|
||||
from modules.level.events.level_on_raw_reaction_add_event import (
|
||||
LevelOnRawReactionAddEvent,
|
||||
)
|
||||
from modules.level.events.level_on_raw_reaction_remove_event import (
|
||||
LevelOnRawReactionRemoveEvent,
|
||||
)
|
||||
from modules.level.events.level_on_voice_state_update_event import (
|
||||
LevelOnVoiceStateUpdateEvent,
|
||||
)
|
||||
from modules.level.level_seeder import LevelSeeder
|
||||
from modules.level.service.level_service import LevelService
|
||||
|
||||
|
||||
class LevelModule(ModuleABC):
|
||||
def __init__(self, dc: DiscordCollectionABC):
|
||||
ModuleABC.__init__(self, dc, FeatureFlagsEnum.level_module)
|
||||
|
||||
def configure_configuration(
|
||||
self, config: ConfigurationABC, env: ApplicationEnvironmentABC
|
||||
):
|
||||
cwd = env.working_directory
|
||||
env.set_working_directory(os.path.dirname(os.path.realpath(__file__)))
|
||||
config.add_json_file(f"default-level.json", optional=False)
|
||||
env.set_working_directory(cwd)
|
||||
|
||||
def configure_services(
|
||||
self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC
|
||||
):
|
||||
services.add_transient(DataSeederABC, LevelSeeder)
|
||||
services.add_transient(LevelService)
|
||||
|
||||
# commands
|
||||
services.add_transient(LevelGroup)
|
||||
|
||||
# events
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_message.value, LevelOnMessageEvent
|
||||
)
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_voice_state_update.value,
|
||||
LevelOnVoiceStateUpdateEvent,
|
||||
)
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_member_join.value, LevelOnMemberJoinEvent
|
||||
)
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_raw_reaction_add.value, LevelOnRawReactionAddEvent
|
||||
)
|
||||
services.add_transient(
|
||||
DiscordEventTypesEnum.on_raw_reaction_remove.value,
|
||||
LevelOnRawReactionRemoveEvent,
|
||||
)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user