from datetime import datetime from typing import Dict, Any from zoneinfo import ZoneInfo from bson import ObjectId from discord import ( ApplicationContext, Attachment, SlashCommandGroup, option, ) from discord.ext.commands import Cog from discord.utils import basic_autocomplete from classes import PycordEvent, PycordGuild from classes.pycord_bot import PycordBot from modules.database import col_events from modules.utils import autocomplete_active_events # TODO Move to staticmethod or to a separate module async def validate_event_validity( ctx: ApplicationContext, name: str, start_date: datetime | None, finish_date: datetime | None, guild_timezone: ZoneInfo, event_id: ObjectId | None = None, ) -> None: if start_date > finish_date: # TODO Make a nice message await ctx.respond("Start date must be before finish date") return elif start_date < datetime.now(tz=guild_timezone): # TODO Make a nice message await ctx.respond("Start date must not be in the past") return query: Dict[str, Any] = { "name": name, "ended": None, "ends": {"$gt": datetime.now(tz=ZoneInfo("UTC"))}, "cancelled": {"$ne": True}, } if event_id is not None: query["_id"] = {"$ne": event_id} if (await col_events.find_one(query)) is not None: # TODO Make a nice message await ctx.respond("There can only be one active event with the same name") return class Event(Cog): """Cog with event management commands.""" def __init__(self, bot: PycordBot): self.bot: PycordBot = bot # TODO Introduce i18n command_group: SlashCommandGroup = SlashCommandGroup("event", "Event management") # TODO Introduce i18n @command_group.command( name="create", description="Create new event", ) @option("name", description="Name of the event", required=True) @option("start", description="Date when the event starts (DD.MM.YYYY HH:MM)", required=True) @option("end", description="Date when the event ends (DD.MM.YYYY HH:MM)", required=True) @option("thumbnail", description="Thumbnail of the event", required=False) async def command_event_create( self, ctx: ApplicationContext, name: str, start: str, end: str, thumbnail: Attachment = None, ) -> None: guild: PycordGuild = await self.bot.find_guild(ctx.guild.id) if not guild.is_configured(): # TODO Make a nice message await ctx.respond("Guild is not configured.") return guild_timezone: ZoneInfo = ZoneInfo(guild.timezone) try: start_date: datetime = datetime.strptime(start, "%d.%m.%Y %H:%M") end_date: datetime = datetime.strptime(end, "%d.%m.%Y %H:%M") start_date = start_date.replace(tzinfo=guild_timezone) end_date = end_date.replace(tzinfo=guild_timezone) except ValueError: # TODO Make a nice message await ctx.respond("Could not parse start and end dates.") return await validate_event_validity(ctx, name, start_date, end_date, guild_timezone) event: PycordEvent = await self.bot.create_event( name=name, guild_id=guild.id, creator_id=ctx.author.id, starts=start_date.astimezone(ZoneInfo("UTC")), ends=end_date.astimezone(ZoneInfo("UTC")), thumbnail_id=thumbnail.id if thumbnail else None, ) # TODO Make a nice message await ctx.respond("Event has been created.") # TODO Introduce i18n @command_group.command( name="edit", description="Edit event", ) @option( "event", description="Name of the event", autocomplete=basic_autocomplete(autocomplete_active_events), required=True, ) @option("name", description="New name of the event", required=False) @option("start", description="Date when the event starts (DD.MM.YYYY HH:MM)", required=False) @option("end", description="Date when the event ends (DD.MM.YYYY HH:MM)", required=False) @option("thumbnail", description="Thumbnail of the event", required=False) async def command_event_edit( self, ctx: ApplicationContext, event: str, name: str = None, start: str = None, end: str = None, thumbnail: Attachment = None, ) -> None: guild: PycordGuild = await self.bot.find_guild(ctx.guild.id) pycord_event: PycordEvent = await self.bot.find_event(event_id=event) if pycord_event is None: # TODO Make a nice message await ctx.respond("Event was not found.") return if not guild.is_configured(): # TODO Make a nice message await ctx.respond("Guild is not configured.") return guild_timezone: ZoneInfo = ZoneInfo(guild.timezone) try: start_date: datetime = ( pycord_event.starts if start is None else datetime.strptime(start, "%d.%m.%Y %H:%M") ) start_date = start_date.replace(tzinfo=guild_timezone) except ValueError: # TODO Make a nice message await ctx.respond("Could not parse the start date.") return try: end_date: datetime = ( pycord_event.ends if end is None else datetime.strptime(end, "%d.%m.%Y %H:%M") ) end_date = end_date.replace(tzinfo=guild_timezone) except ValueError: # TODO Make a nice message await ctx.respond("Could not parse the end date.") return await validate_event_validity(ctx, name, start_date, end_date, guild_timezone) await pycord_event.update( self.bot.cache, starts=start_date, ends=end_date, name=pycord_event.name if name is None else name, thumbnail_id=pycord_event.thumbnail_id if thumbnail is None else thumbnail.id, ) # TODO Make a nice message await ctx.respond("Event has been updated.") # TODO Introduce i18n @command_group.command( name="cancel", description="Cancel event", ) @option( "event", description="Name of the event", autocomplete=basic_autocomplete(autocomplete_active_events), required=True, ) @option("confirm", description="Confirmation of the operation", required=False) async def command_event_cancel( self, ctx: ApplicationContext, event: str, confirm: bool = False, ) -> None: if confirm is None or not confirm: # TODO Make a nice message await ctx.respond("Operation not confirmed.") return guild: PycordGuild = await self.bot.find_guild(ctx.guild.id) pycord_event: PycordEvent = await self.bot.find_event(event_id=event) if pycord_event is None: # TODO Make a nice message await ctx.respond("Event was not found.") return if not guild.is_configured(): # TODO Make a nice message await ctx.respond("Guild is not configured.") return start_date: datetime = pycord_event.starts.replace(tzinfo=ZoneInfo("UTC")) end_date: datetime = pycord_event.ends.replace(tzinfo=ZoneInfo("UTC")) # TODO Make ongoing events cancellable if ( pycord_event.ended is not None or end_date <= datetime.now(tz=ZoneInfo("UTC")) or start_date <= datetime.now(tz=ZoneInfo("UTC")) ): # TODO Make a nice message await ctx.respond("Finished or ongoing events cannot be cancelled.") return await pycord_event.cancel() # TODO Make a nice message await ctx.respond("Event was cancelled.") # TODO Introduce i18n @command_group.command( name="show", description="Show the details about certain event", ) @option( "event", description="Name of the event", autocomplete=basic_autocomplete(autocomplete_active_events), required=True, ) async def command_event_show(self, ctx: ApplicationContext, event: str) -> None: guild: PycordGuild = await self.bot.find_guild(ctx.guild.id) pycord_event: PycordEvent = await self.bot.find_event(event_id=event) if pycord_event is None: # TODO Make a nice message await ctx.respond("Event was not found.") return starts_date: datetime = pycord_event.starts.replace(tzinfo=ZoneInfo("UTC")) ends_date: datetime = pycord_event.ends.replace(tzinfo=ZoneInfo("UTC")) # TODO Make a nice message await ctx.respond( f"**Event details**\n\nName: {pycord_event.name}\nStarts: \nEnds: " ) def setup(bot: PycordBot) -> None: bot.add_cog(Event(bot))