diff --git a/kdb-bot/src/bot/bot.json b/kdb-bot/src/bot/bot.json index a0297264..e5666f6a 100644 --- a/kdb-bot/src/bot/bot.json +++ b/kdb-bot/src/bot/bot.json @@ -19,7 +19,6 @@ "cpl-core==2022.12.1.post3", "cpl-translation==2022.12.1", "cpl-query==2022.12.2.post1", - "cpl-discord==2022.12.1.post2", "Flask==2.2.2", "Flask-Classful==0.14.2", "Flask-Cors==3.0.10", @@ -29,7 +28,8 @@ "eventlet==0.33.3", "requests-oauthlib==1.3.1", "icmplib==3.0.3", - "ariadne==0.17.1" + "ariadne==0.17.1", + "cpl-discord==2022.12.2" ], "DevDependencies": [ "cpl-cli==2022.12.1.post3" diff --git a/kdb-bot/src/bot/config b/kdb-bot/src/bot/config index b0ae8762..ac704682 160000 --- a/kdb-bot/src/bot/config +++ b/kdb-bot/src/bot/config @@ -1 +1 @@ -Subproject commit b0ae87621bbe54fd9c5650071ec8c1c9ec32df48 +Subproject commit ac7046820f3410f55e779797126d9410c6bcdc9f diff --git a/kdb-bot/src/modules/base/base_module.py b/kdb-bot/src/modules/base/base_module.py index 3c470bbb..492a5ee4 100644 --- a/kdb-bot/src/modules/base/base_module.py +++ b/kdb-bot/src/modules/base/base_module.py @@ -25,14 +25,19 @@ from modules.base.events.base_on_message_delete_event import BaseOnMessageDelete 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.helper.base_reaction_handler import BaseReactionHandler from modules.base.service.base_helper_service import BaseHelperService +from modules.base.service.event_service import EventService class BaseModule(ModuleABC): @@ -45,6 +50,8 @@ class BaseModule(ModuleABC): def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): services.add_transient(BaseHelperABC, BaseHelperService) services.add_transient(BaseReactionHandler) + services.add_singleton(EventService) + # commands self._dc.add_command(AFKCommand) self._dc.add_command(HelpCommand) @@ -77,3 +84,11 @@ class BaseModule(ModuleABC): DiscordEventTypesEnum.on_voice_state_update.value, BaseOnVoiceStateUpdateEventHelpChannel, ) + self._dc.add_event( + DiscordEventTypesEnum.on_voice_state_update.value, + BaseOnVoiceStateUpdateEventScheduledEventBonus, + ) + self._dc.add_event( + DiscordEventTypesEnum.on_scheduled_event_update.value, + BaseOnScheduledEventUpdateEvent, + ) diff --git a/kdb-bot/src/modules/base/configuration/base_server_settings.py b/kdb-bot/src/modules/base/configuration/base_server_settings.py index 531e44c9..c6a7e9c4 100644 --- a/kdb-bot/src/modules/base/configuration/base_server_settings.py +++ b/kdb-bot/src/modules/base/configuration/base_server_settings.py @@ -15,6 +15,7 @@ class BaseServerSettings(ConfigurationModelABC): self._xp_per_reaction: int = 0 self._max_message_xp_per_hour: int = 0 self._xp_per_ontime_hour: int = 0 + self._xp_per_event_participation: int = 0 self._afk_channel_ids: List[int] = List(int) self._afk_command_channel_id: int = 0 self._help_command_reference_url: str = "" @@ -45,6 +46,10 @@ class BaseServerSettings(ConfigurationModelABC): def xp_per_ontime_hour(self) -> int: return self._xp_per_ontime_hour + @property + def xp_per_event_participation(self) -> int: + return self._xp_per_event_participation + @property def afk_channel_ids(self) -> List[int]: return self._afk_channel_ids @@ -73,6 +78,9 @@ class BaseServerSettings(ConfigurationModelABC): self._xp_per_reaction = int(settings["XpPerReaction"]) self._max_message_xp_per_hour = int(settings["MaxMessageXpPerHour"]) self._xp_per_ontime_hour = int(settings["XpPerOntimeHour"]) + self._xp_per_event_participation = ( + 0 if "XpPerEventParticipation" not in settings else settings["XpPerEventParticipation"] + ) for index in settings["AFKChannelIds"]: self._afk_channel_ids.append(int(index)) self._afk_command_channel_id = settings["AFKCommandChannelId"] diff --git a/kdb-bot/src/modules/base/events/base_on_scheduled_event_update_event.py b/kdb-bot/src/modules/base/events/base_on_scheduled_event_update_event.py new file mode 100644 index 00000000..5b9cf901 --- /dev/null +++ b/kdb-bot/src/modules/base/events/base_on_scheduled_event_update_event.py @@ -0,0 +1,39 @@ +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: + self._events.add_event(ActiveEvent(after)) + # 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") diff --git a/kdb-bot/src/modules/base/events/base_on_voice_state_update_event_scheduled_event_bonus.py b/kdb-bot/src/modules/base/events/base_on_voice_state_update_event_scheduled_event_bonus.py new file mode 100644 index 00000000..54050851 --- /dev/null +++ b/kdb-bot/src/modules/base/events/base_on_voice_state_update_event_scheduled_event_bonus.py @@ -0,0 +1,66 @@ +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.abc.base_helper_abc import BaseHelperABC +from modules.base.configuration.base_server_settings import BaseServerSettings +from modules.base.service.event_service import EventService + + +class BaseOnVoiceStateUpdateEventScheduledEventBonus(OnVoiceStateUpdateABC): + def __init__( + self, + config: ConfigurationABC, + logger: LoggerABC, + base_helper: BaseHelperABC, + servers: ServerRepositoryABC, + users: UserRepositoryABC, + events: EventService, + db: DatabaseContextABC, + ): + OnVoiceStateUpdateABC.__init__(self) + self._config = config + self._logger = logger + self._base_helper = base_helper + 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 + + 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.server_id) + if active_event.participants.any(lambda x: x.user_id == user.user_id): + self._logger.debug(__name__, f"Module {type(self)} stopped") + return + + settings: BaseServerSettings = self._base_helper.get_config(server.discord_server_id) + user.xp += settings.xp_per_event_participation + self._users.update_user(user) + self._db.save_changes() + active_event.participants.append(user) + + self._logger.debug(__name__, f"Module {type(self)} stopped") diff --git a/kdb-bot/src/modules/base/model/__init__.py b/kdb-bot/src/modules/base/model/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/kdb-bot/src/modules/base/model/active_event.py b/kdb-bot/src/modules/base/model/active_event.py new file mode 100644 index 00000000..63861e0c --- /dev/null +++ b/kdb-bot/src/modules/base/model/active_event.py @@ -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 diff --git a/kdb-bot/src/modules/base/service/event_service.py b/kdb-bot/src/modules/base/service/event_service.py new file mode 100644 index 00000000..8073b3d3 --- /dev/null +++ b/kdb-bot/src/modules/base/service/event_service.py @@ -0,0 +1,31 @@ +from typing import Optional + +import discord +from cpl_query.extension import List + +from modules.base.model.active_event import ActiveEvent + + +class EventService: + def __init__(self): + 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)