from datetime import datetime from typing import Any, Dict, List from zoneinfo import ZoneInfo from bson.errors import InvalidId from discord import ( ApplicationContext, Attachment, SlashCommandGroup, option, ) from discord.ext.commands import Cog from discord.utils import basic_autocomplete from classes import PycordEvent, PycordEventStage, PycordGuild from classes.pycord_bot import PycordBot from modules.utils import ( autocomplete_active_events, get_unix_timestamp, is_operation_confirmed, validate_event_validity, ) class CogEvent(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(): await ctx.respond(self.bot._("guild_unconfigured_admin", "messages", locale=ctx.locale)) 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 Introduce i18n await ctx.respond( "Could not parse start and end dates. Please, make sure these are provided in `DD.MM.YYYY HH:MM` format." ) return await validate_event_validity(ctx, name, start_date, end_date, guild_timezone) processed_media: List[Dict[str, Any]] = ( [] if thumbnail is None else await self.bot.process_attachments([thumbnail]) ) 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=processed_media[0] if thumbnail else None, ) # TODO Introduce i18n await ctx.respond( f"Event **{event.name}** has been created and will take place ." ) # 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) try: pycord_event: PycordEvent = await self.bot.find_event(event_id=event) except (InvalidId, RuntimeError): # TODO Make a nice message await ctx.respond("Event was not found.") return if not guild.is_configured(): await ctx.respond(self.bot._("guild_unconfigured_admin", "messages", locale=ctx.locale)) 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) processed_media: List[Dict[str, Any]] = ( [] if thumbnail is None else await self.bot.process_attachments([thumbnail]) ) await pycord_event.update( self.bot.cache, starts=start_date, ends=end_date, name=pycord_event.name if name is None else name, thumbnail=pycord_event.thumbnail if thumbnail is None else processed_media[0], ) # TODO Notify participants about time changes # TODO Make a nice message await ctx.respond( f"Event **{pycord_event.name}** has been updated and will take place ." ) # 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 not (await is_operation_confirmed(ctx, confirm)): return guild: PycordGuild = await self.bot.find_guild(ctx.guild.id) try: pycord_event: PycordEvent = await self.bot.find_event(event_id=event) except (InvalidId, RuntimeError): # TODO Make a nice message await ctx.respond("Event was not found.") return if not guild.is_configured(): await ctx.respond(self.bot._("guild_unconfigured_admin", "messages", locale=ctx.locale)) 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 Notify participants about cancellation # TODO Make a nice message await ctx.respond(f"Event **{pycord_event.name}** 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) try: pycord_event: PycordEvent = await self.bot.find_event(event_id=event) except (InvalidId, RuntimeError): # 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")) stages: List[PycordEventStage] = await self.bot.get_event_stages(pycord_event) # TODO Make a nice message stages_string: str = "\n\n".join( f"**Stage {stage.sequence+1}**\nQuestion: {stage.question}\nAnswer: ||{stage.answer}||" for stage in stages ) # TODO Show users registered for the event # TODO Introduce i18n await ctx.respond( f"**Event details**\n\nName: {pycord_event.name}\nStarts: \nEnds: \n\nStages:\n{stages_string}" ) def setup(bot: PycordBot) -> None: bot.add_cog(CogEvent(bot))