diff --git a/bot/src/bot/extension/clean_logs_extension.py b/bot/src/bot/extension/clean_logs_extension.py new file mode 100644 index 00000000..4493f0fe --- /dev/null +++ b/bot/src/bot/extension/clean_logs_extension.py @@ -0,0 +1,22 @@ +import os +import shutil +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_query.extension import List + + +class CleanLogsExtension(ApplicationExtensionABC): + def __init__(self): + pass + + async def run(self, config: ConfigurationABC, services: ServiceProviderABC): + ( + List(str, os.listdir("logs/")) + .where(lambda x: os.path.isdir(f"logs/{x}")) + .order_by() + .where(lambda x: (datetime.now() - datetime.strptime(x, "%Y-%m-%d")).days >= 7) + .for_each(lambda x: shutil.rmtree(f"logs/{x}")) + ) diff --git a/bot/src/bot/main.py b/bot/src/bot/main.py index 1efe7d63..bef4964a 100644 --- a/bot/src/bot/main.py +++ b/bot/src/bot/main.py @@ -6,6 +6,7 @@ from cpl_core.application import ApplicationBuilder from cpl_core.console import Console from bot.application import Application +from bot.extension.clean_logs_extension import CleanLogsExtension from bot.extension.init_bot_extension import InitBotExtension from bot.startup import Startup from bot.startup_discord_extension import StartupDiscordExtension @@ -31,6 +32,7 @@ class Program: .use_extension(StartupDiscordExtension) .use_extension(StartupModuleExtension) .use_extension(StartupMigrationExtension) + .use_extension(CleanLogsExtension) .use_extension(DatabaseExtension) .use_extension(ConfigExtension) .use_extension(InitBotExtension) diff --git a/bot/src/modules/base/base_module.py b/bot/src/modules/base/base_module.py index b0608515..ca6b6356 100644 --- a/bot/src/modules/base/base_module.py +++ b/bot/src/modules/base/base_module.py @@ -7,6 +7,7 @@ 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.command.scheduled_events_group import ScheduledEventsCommand from modules.base.tasks.birthday_watcher import BirthdayWatcher from modules.base.command.afk_command import AFKCommand from modules.base.command.game_server_group import GameServerGroup @@ -83,6 +84,7 @@ class BaseModule(ModuleABC): services.add_transient(RegisterGroup) services.add_transient(UnregisterGroup) services.add_transient(GameServerGroup) + services.add_transient(ScheduledEventsCommand) # events services.add_transient(DiscordEventTypesEnum.on_command.value, BaseOnCommandEvent) services.add_transient(DiscordEventTypesEnum.on_command_error.value, BaseOnCommandErrorEvent) diff --git a/bot/src/modules/base/command/scheduled_events_group.py b/bot/src/modules/base/command/scheduled_events_group.py new file mode 100644 index 00000000..e7b6bd85 --- /dev/null +++ b/bot/src/modules/base/command/scheduled_events_group.py @@ -0,0 +1,37 @@ +from cpl_discord.command import DiscordCommandABC +from discord.ext import commands +from discord.ext.commands import Context + +from bot_core.helper.command_checks import CommandChecks +from bot_core.logging.command_logger import CommandLogger +from modules.base.service.event_service import EventService + + +class ScheduledEventsCommand(DiscordCommandABC): + def __init__( + self, + logger: CommandLogger, + events: EventService, + ): + DiscordCommandABC.__init__(self) + + self._logger = logger + self._events = events + + self._logger.trace(__name__, f"Loaded command service: {type(self).__name__}") + + @commands.group(name="scheduled-events") + @commands.guild_only() + async def scheduled_events(self, ctx: Context): + pass + + @scheduled_events.command() + @commands.guild_only() + @CommandChecks.check_is_ready() + @CommandChecks.check_is_member_moderator() + async def reload(self, ctx: Context): + self._logger.debug(__name__, "Running scheduled-events reload") + try: + await self._events.check_and_create_scheduled_events(ctx.guild) + except Exception as e: + self._logger.error(__name__, f"Reloading scheduled events failed", e) diff --git a/bot/src/modules/base/events/base_on_scheduled_event_update_event.py b/bot/src/modules/base/events/base_on_scheduled_event_update_event.py index 25cd22fb..511e19a4 100644 --- a/bot/src/modules/base/events/base_on_scheduled_event_update_event.py +++ b/bot/src/modules/base/events/base_on_scheduled_event_update_event.py @@ -41,5 +41,6 @@ class BaseOnScheduledEventUpdateEvent(OnScheduledEventUpdateABC): if event is None: return self._events.remove_event(event) + await self._events.check_and_create_scheduled_events(before.guild) self._logger.debug(__name__, f"Module {type(self)} stopped") diff --git a/bot/src/modules/base/service/event_service.py b/bot/src/modules/base/service/event_service.py index 30b19d96..72e70a15 100644 --- a/bot/src/modules/base/service/event_service.py +++ b/bot/src/modules/base/service/event_service.py @@ -1,13 +1,22 @@ +import calendar +from datetime import datetime, timedelta from typing import Optional +from zoneinfo import ZoneInfo import discord from cpl_core.configuration import ConfigurationABC from cpl_core.database.context import DatabaseContextABC from cpl_core.logging import LoggerABC +from cpl_discord.container import Guild from cpl_query.extension import List +from discord import PrivacyLevel +from discord.scheduled_event import ScheduledEvent as DiscordEvent +from bot_data.abc.scheduled_event_repository_abc import ScheduledEventRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC from bot_data.abc.user_repository_abc import UserRepositoryABC +from bot_data.model.scheduled_event import ScheduledEvent +from bot_data.model.scheduled_event_interval_enum import ScheduledEventIntervalEnum from bot_data.model.server_config import ServerConfig from modules.base.model.active_event import ActiveEvent @@ -20,12 +29,14 @@ class EventService: servers: ServerRepositoryABC, users: UserRepositoryABC, db: DatabaseContextABC, + events: ScheduledEventRepositoryABC, ): self._config = config self._logger = logger self._servers = servers self._users = users self._db = db + self._events = events self._active_events = List(ActiveEvent) @@ -61,3 +72,69 @@ class EventService: self._users.update_user(user) self._db.save_changes() active_event.participants.append(user) + + def _append_interval(self, interval: ScheduledEventIntervalEnum, ts: datetime) -> datetime: + if ts >= datetime.now(): + return ts + + if interval == ScheduledEventIntervalEnum.daily: + ts = ts + timedelta(days=1) + + elif interval == ScheduledEventIntervalEnum.weekly: + ts = ts + timedelta(weeks=1) + + elif interval == ScheduledEventIntervalEnum.monthly: + days_in_month = calendar.monthrange(ts.year, ts.month + 1)[1] + ts = ts + timedelta(days=days_in_month) + + elif interval == ScheduledEventIntervalEnum.yearly: + ts = ts + timedelta(days=365) + + while ts < datetime.now(): + ts = self._append_interval(interval, ts) + + return ts + + async def check_and_create_scheduled_events(self, guild: Guild): + server = self._servers.get_server_by_discord_id(guild.id) + scheduled_events_from_db = self._events.get_scheduled_events_by_server_id(server.id) + for scheduled_event in scheduled_events_from_db: + scheduled_event: ScheduledEvent = scheduled_event + from_guild = List(DiscordEvent, guild.scheduled_events).where( + lambda x: x.name == scheduled_event.name + and x.description == scheduled_event.description + and x.entity_type == scheduled_event.entity_type + ) + if from_guild.count() != 0: + continue + + kwargs = {"name": scheduled_event.name, "description": scheduled_event.description} + + if scheduled_event.channel_id is not None: + kwargs["channel"] = guild.get_channel(scheduled_event.channel_id) + + if scheduled_event.start_time is not None: + scheduled_event.start_time = self._append_interval(scheduled_event.interval, scheduled_event.start_time) + + start_time = scheduled_event.start_time.replace(tzinfo=ZoneInfo("Europe/Berlin")) + + kwargs["start_time"] = start_time + + if scheduled_event.end_time is not None: + scheduled_event.end_time = self._append_interval(scheduled_event.interval, scheduled_event.end_time) + end_time = scheduled_event.end_time.replace(tzinfo=ZoneInfo("Europe/Berlin")) + kwargs["end_time"] = end_time + + kwargs["entity_type"] = scheduled_event.entity_type + if scheduled_event.location is not None: + kwargs["location"] = scheduled_event.location + + kwargs["privacy_level"] = PrivacyLevel.guild_only + + try: + self._logger.debug(__name__, f"Try to create scheduled event for guild {guild.name}") + await guild.create_scheduled_event(**kwargs) + self._events.update_scheduled_event(scheduled_event) + self._db.save_changes() + except Exception as e: + self._logger.error(__name__, f"Watching scheduled events failed", e) diff --git a/bot/src/modules/base/tasks/scheduled_events_watcher.py b/bot/src/modules/base/tasks/scheduled_events_watcher.py index 13ee83ae..e692d163 100644 --- a/bot/src/modules/base/tasks/scheduled_events_watcher.py +++ b/bot/src/modules/base/tasks/scheduled_events_watcher.py @@ -1,23 +1,14 @@ -import calendar -from datetime import datetime, timedelta -from zoneinfo import ZoneInfo - from cpl_core.configuration import ConfigurationABC from cpl_core.database.context import DatabaseContextABC from cpl_discord.service import DiscordBotServiceABC -from cpl_query.extension import List from cpl_translation import TranslatePipe -from discord import Guild, PrivacyLevel from discord.ext import tasks -from discord.scheduled_event import ScheduledEvent as DiscordEvent 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.scheduled_event_repository_abc import ScheduledEventRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC -from bot_data.model.scheduled_event import ScheduledEvent -from bot_data.model.scheduled_event_interval_enum import ScheduledEventIntervalEnum +from modules.base.service.event_service import EventService class ScheduledEventsWatcher(TaskABC): @@ -28,7 +19,7 @@ class ScheduledEventsWatcher(TaskABC): bot: DiscordBotServiceABC, db: DatabaseContextABC, servers: ServerRepositoryABC, - events: ScheduledEventRepositoryABC, + events: EventService, message_service: MessageService, t: TranslatePipe, ): @@ -46,80 +37,12 @@ class ScheduledEventsWatcher(TaskABC): if not self._is_maintenance(): self.watch.start() - def _append_interval(self, interval: ScheduledEventIntervalEnum, ts: datetime) -> datetime: - now = datetime.now() - if ts >= now: - return ts - - if interval == ScheduledEventIntervalEnum.daily: - ts = ts + timedelta(days=1) - - elif interval == ScheduledEventIntervalEnum.weekly: - ts = ts + timedelta(weeks=1) - - elif interval == ScheduledEventIntervalEnum.monthly: - days_in_month = calendar.monthrange(ts.year, ts.month + 1)[1] - ts = ts + timedelta(days=days_in_month) - - elif interval == ScheduledEventIntervalEnum.yearly: - ts = ts + timedelta(days=365) - - if ts < now: - return self._append_interval(interval, ts) - return ts - - @tasks.loop(hours=24) + @tasks.loop(hours=12) async def watch(self): self._logger.info(__name__, "Watching scheduled events") try: for guild in self._bot.guilds: - guild: Guild = guild - server = self._servers.get_server_by_discord_id(guild.id) - scheduled_events_from_guild = self._events.get_scheduled_events_by_server_id(server.id) - for scheduled_event in scheduled_events_from_guild: - scheduled_event: ScheduledEvent = scheduled_event - from_guild = List(DiscordEvent, guild.scheduled_events).where( - lambda x: x.name == scheduled_event.name - and x.description == scheduled_event.description - and x.entity_type == scheduled_event.entity_type - ) - if from_guild.count() != 0: - continue - - kwargs = {"name": scheduled_event.name, "description": scheduled_event.description} - - if scheduled_event.channel_id is not None: - kwargs["channel"] = guild.get_channel(scheduled_event.channel_id) - - if scheduled_event.start_time is not None: - scheduled_event.start_time = self._append_interval( - scheduled_event.interval, scheduled_event.start_time - ) - - start_time = scheduled_event.start_time.replace(tzinfo=ZoneInfo("Europe/Berlin")) - - kwargs["start_time"] = start_time - - if scheduled_event.end_time is not None: - scheduled_event.end_time = self._append_interval( - scheduled_event.interval, scheduled_event.end_time - ) - end_time = scheduled_event.end_time.replace(tzinfo=ZoneInfo("Europe/Berlin")) - kwargs["end_time"] = end_time - - kwargs["entity_type"] = scheduled_event.entity_type - if scheduled_event.location is not None: - kwargs["location"] = scheduled_event.location - - kwargs["privacy_level"] = PrivacyLevel.guild_only - - try: - self._logger.debug(__name__, f"Try to create scheduled event for guild {guild.name}") - await guild.create_scheduled_event(**kwargs) - self._events.update_scheduled_event(scheduled_event) - self._db.save_changes() - except Exception as e: - self._logger.error(__name__, f"Watching scheduled events failed", e) + await self._events.check_and_create_scheduled_events(guild) except Exception as e: self._logger.error(__name__, f"Watching scheduled events failed", e)