from datetime import datetime from typing import List, Dict, Any 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, autocomplete_event_stages async def validate_event_status( ctx: ApplicationContext, event: PycordEvent, ) -> bool: if event.is_cancelled: # TODO Make a nice message await ctx.respond("This event was cancelled.") return False if ( event.starts.replace(tzinfo=ZoneInfo("UTC")) <= datetime.now(tz=ZoneInfo("UTC")) <= event.ends.replace(tzinfo=ZoneInfo("UTC")) ): # TODO Make a nice message await ctx.respond("Ongoing events cannot be modified.") return False return True class Stage(Cog): """Cog with event stage management commands.""" def __init__(self, bot: PycordBot): self.bot: PycordBot = bot command_group: SlashCommandGroup = SlashCommandGroup("stage", "Event stage management") # TODO Introduce i18n # TODO Maybe add an option for order? @command_group.command( name="add", description="Add new event stage", ) @option( "event", description="Name of the event", autocomplete=basic_autocomplete(autocomplete_active_events), required=True, ) @option("question", description="Question to be answered", required=True) @option("answer", description="Answer to the stage's question", required=True) @option("media", description="Media file to be attached", required=False) async def command_stage_add( self, ctx: ApplicationContext, event: str, question: str, answer: str, media: 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 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 (await validate_event_status(ctx, pycord_event)): return processed_media: List[Dict[str, Any]] = ( [] if media is None else await self.bot.process_attachments([media]) ) event_stage: PycordEventStage = await self.bot.create_event_stage( event=pycord_event, event_id=pycord_event._id, guild_id=guild.id, sequence=len(pycord_event.stage_ids), creator_id=ctx.author.id, question=question, answer=answer, media=[] if media is None else processed_media, ) # TODO Make a nice message await ctx.respond("Event stage has been created.") # TODO Implement the command # /stage edit @command_group.command( name="edit", description="Edit the event stage", ) @option( "event", description="Name of the event", autocomplete=basic_autocomplete(autocomplete_active_events), required=True, ) # TODO Add autofill @option( "stage", description="Stage to edit", autocomplete=basic_autocomplete(autocomplete_event_stages), required=True, ) @option("order", description="Number in the event stages' order", min_value=1, required=False) @option("question", description="Question to be answered", required=False) @option("answer", description="Answer to the stage's question", required=False) @option("media", description="Media file to be attached", required=False) @option("remove_media", description="Remove attached media", required=False) async def command_stage_edit( self, ctx: ApplicationContext, event: str, stage: str, order: int, question: str, answer: str, media: Attachment = None, remove_media: bool = False, ) -> 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 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 (await validate_event_status(ctx, pycord_event)): return try: event_stage: PycordEventStage = await self.bot.find_event_stage(stage) except (InvalidId, RuntimeError): # TODO Make a nice message await ctx.respond("Event stage was not found.") return if order is not None and order > len(pycord_event.stage_ids): # TODO Make a nice message await ctx.respond("Stage sequence out of range.") return processed_media: List[Dict[str, Any]] = ( [] if media is None else await self.bot.process_attachments([media]) ) if not (question is None and answer is None and media is None and remove_media is False): await event_stage.update( question=event_stage.question if question is None else question, answer=event_stage.answer if answer is None else answer, media=([] if remove_media else (event_stage.media if media is None else processed_media)), ) if order is not None and order - 1 != event_stage.sequence: await pycord_event.reorder_stage(self.bot, event_stage._id, order - 1, cache=self.bot.cache) await ctx.respond("Event stage has been updated.") # TODO Implement the command # /stage delete @command_group.command( name="delete", description="Delete the event stage", ) @option( "event", description="Name of the event", autocomplete=basic_autocomplete(autocomplete_active_events), required=True, ) @option( "stage", description="Stage to delete", autocomplete=basic_autocomplete(autocomplete_event_stages), required=True, ) @option("confirm", description="Confirmation of the operation", required=False) async def command_stage_delete( self, ctx: ApplicationContext, event: str, stage: 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) if not guild.is_configured(): # TODO Make a nice message await ctx.respond("Guild is not configured.") return 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 (await validate_event_status(ctx, pycord_event)): return try: event_stage: PycordEventStage = await self.bot.find_event_stage(stage) except (InvalidId, RuntimeError): # TODO Make a nice message await ctx.respond("Event stage was not found.") return await pycord_event.remove_stage(self.bot, event_stage._id, cache=self.bot.cache) await event_stage.purge(cache=self.bot.cache) # TODO Make a nice message await ctx.respond("Okay.") def setup(bot: PycordBot) -> None: bot.add_cog(Stage(bot))