diff --git a/classes/pycord_bot.py b/classes/pycord_bot.py index d1aa614..df4219b 100644 --- a/classes/pycord_bot.py +++ b/classes/pycord_bot.py @@ -1,17 +1,17 @@ from datetime import datetime from logging import Logger from pathlib import Path -from typing import Any, override, List, Dict +from typing import Any, Dict, List, override from zoneinfo import ZoneInfo from bson import ObjectId -from discord import Guild, User, TextChannel, Attachment, File +from discord import Attachment, File, Guild, TextChannel, User from libbot.cache.classes import CacheMemcached, CacheRedis from libbot.cache.manager import create_cache_client from libbot.pycord.classes import PycordBot as LibPycordBot from classes import PycordEvent, PycordEventStage, PycordGuild, PycordUser -from modules.database import col_users, col_events +from modules.database import col_events, col_users from modules.utils import get_logger logger: Logger = get_logger(__name__) @@ -53,9 +53,7 @@ class PycordBot(LibPycordBot): await super().close(**kwargs) async def _schedule_tasks(self) -> None: - self.scheduler.add_job( - self._execute_event_controller, trigger="cron", minute="*/1", id="event_controller" - ) + self.scheduler.add_job(self._execute_event_controller, trigger="cron", minute="*/1", id="event_controller") async def _execute_event_controller(self) -> None: await self._process_events_start() @@ -105,9 +103,7 @@ class PycordBot(LibPycordBot): first_stage_files: List[File] | None = first_stage.get_media_files() - await user_channel.send( - f"First stage...\n\n{first_stage.question}", files=first_stage_files - ) + await user_channel.send(f"First stage...\n\n{first_stage.question}", files=first_stage_files) # TODO Make a nice message await self.notify_admins( @@ -126,7 +122,7 @@ class PycordBot(LibPycordBot): for event in events: guild: Guild = self.get_guild(event.guild_id) pycord_guild: PycordGuild = await self.find_guild(guild) - stages: List[PycordEventStage] = await self._get_event_stages(event) + stages: List[PycordEventStage] = await self.get_event_stages(event) # TODO Make a nice message stages_string: str = "\n\n".join( @@ -174,7 +170,8 @@ class PycordBot(LibPycordBot): return users - async def _get_event_stages(self, event: PycordEvent) -> List[PycordEventStage]: + # TODO Add documentation + async def get_event_stages(self, event: PycordEvent) -> List[PycordEventStage]: return [(await self.find_event_stage(stage_id)) for stage_id in event.stage_ids] # TODO Add documentation @@ -254,9 +251,7 @@ class PycordBot(LibPycordBot): return event_stage # TODO Document this method - async def find_event( - self, event_id: str | ObjectId | None = None, event_name: str | None = None - ) -> PycordEvent: + async def find_event(self, event_id: str | ObjectId | None = None, event_name: str | None = None) -> PycordEvent: if event_id is None and event_name is None: raise AttributeError("Either event's ID or name must be provided!") diff --git a/classes/pycord_event.py b/classes/pycord_event.py index 7404be1..8878945 100644 --- a/classes/pycord_event.py +++ b/classes/pycord_event.py @@ -286,9 +286,7 @@ class PycordEvent: stage_index: int = self.stage_ids.index(event_stage_id) old_stage_index: int = old_stage_ids.index(event_stage_id) - logger.debug( - "Indexes for %s: was %s and is now %s", event_stage_id, old_stage_index, stage_index - ) + logger.debug("Indexes for %s: was %s and is now %s", event_stage_id, old_stage_index, stage_index) if stage_index != old_stage_index: await (await bot.find_event_stage(event_stage_id)).update(cache, sequence=stage_index) diff --git a/classes/pycord_user.py b/classes/pycord_user.py index 356dc25..011b985 100644 --- a/classes/pycord_user.py +++ b/classes/pycord_user.py @@ -4,7 +4,15 @@ from logging import Logger from typing import Any, Dict, List, Optional from bson import ObjectId -from discord import Bot, Guild, Member, PermissionOverwrite, TextChannel, Forbidden, Role +from discord import ( + Bot, + Forbidden, + Guild, + Member, + PermissionOverwrite, + Role, + TextChannel, +) from discord.abc import GuildChannel from libbot.cache.classes import Cache from pymongo.results import InsertOneResult @@ -106,12 +114,8 @@ class PycordUser: "guild_id": self.guild_id, "event_channels": self.event_channels, "is_jailed": self.is_jailed, - "current_event_id": ( - self.current_event_id if not json_compatible else str(self.current_event_id) - ), - "current_stage_id": ( - self.current_stage_id if not json_compatible else str(self.current_stage_id) - ), + "current_event_id": (self.current_event_id if not json_compatible else str(self.current_event_id)), + "current_stage_id": (self.current_stage_id if not json_compatible else str(self.current_stage_id)), "registered_event_ids": ( self.registered_event_ids if not json_compatible @@ -367,9 +371,7 @@ class PycordUser: # TODO Add documentation async def set_event_stage(self, stage_id: str | ObjectId | None, cache: Optional[Cache] = None) -> None: - await self._set( - cache, current_stage_id=stage_id if isinstance(stage_id, str) else ObjectId(stage_id) - ) + await self._set(cache, current_stage_id=stage_id if isinstance(stage_id, str) else ObjectId(stage_id)) async def jail(self, cache: Optional[Cache] = None) -> None: await self._set(cache, is_jailed=True) diff --git a/cogs/config.py b/cogs/cog_config.py similarity index 80% rename from cogs/config.py rename to cogs/cog_config.py index 65426b2..c5b8cd7 100644 --- a/cogs/config.py +++ b/cogs/cog_config.py @@ -9,14 +9,14 @@ from discord import ( ) from discord.ext.commands import Cog from discord.utils import basic_autocomplete -from libbot.i18n import in_every_locale, _ +from libbot.i18n import _, in_every_locale from classes import PycordGuild from classes.pycord_bot import PycordBot -from modules.utils import autocomplete_timezones +from modules.utils import autocomplete_timezones, is_operation_confirmed -class Config(Cog): +class CogConfig(Cog): """Cog with guild configuration commands.""" def __init__(self, bot: PycordBot): @@ -36,18 +36,14 @@ class Config(Cog): @option( "category", description=_("description", "commands", "config_set", "options", "category"), - description_localizations=in_every_locale( - "description", "commands", "config_set", "options", "category" - ), + description_localizations=in_every_locale("description", "commands", "config_set", "options", "category"), required=True, ) @option("channel", description="Text channel for admin notifications", required=True) @option( "timezone", description=_("description", "commands", "config_set", "options", "timezone"), - description_localizations=in_every_locale( - "description", "commands", "config_set", "options", "timezone" - ), + description_localizations=in_every_locale("description", "commands", "config_set", "options", "timezone"), autocomplete=basic_autocomplete(autocomplete_timezones), required=True, ) @@ -63,9 +59,7 @@ class Config(Cog): try: timezone_parsed: ZoneInfo = ZoneInfo(timezone) except ZoneInfoNotFoundError: - await ctx.respond( - self.bot._("timezone_invalid", "messages", locale=ctx.locale).format(timezone=timezone) - ) + await ctx.respond(self.bot._("timezone_invalid", "messages", locale=ctx.locale).format(timezone=timezone)) return await guild.update( @@ -85,14 +79,11 @@ class Config(Cog): @option( "confirm", description=_("description", "commands", "config_reset", "options", "confirm"), - description_localizations=in_every_locale( - "description", "commands", "config_reset", "options", "confirm" - ), + description_localizations=in_every_locale("description", "commands", "config_reset", "options", "confirm"), required=False, ) async def command_config_reset(self, ctx: ApplicationContext, confirm: bool = False) -> None: - if confirm is None or not confirm: - await ctx.respond(self.bot._("operation_unconfirmed", "messages", locale=ctx.locale)) + if not (await is_operation_confirmed(ctx, confirm)): return guild: PycordGuild = await self.bot.find_guild(ctx.guild.id) @@ -123,4 +114,4 @@ class Config(Cog): def setup(bot: PycordBot) -> None: - bot.add_cog(Config(bot)) + bot.add_cog(CogConfig(bot)) diff --git a/cogs/event.py b/cogs/cog_event.py similarity index 78% rename from cogs/event.py rename to cogs/cog_event.py index 1aa8c40..744fca8 100644 --- a/cogs/event.py +++ b/cogs/cog_event.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Dict, List, Any +from typing import Any, Dict, List from zoneinfo import ZoneInfo from bson.errors import InvalidId @@ -12,12 +12,17 @@ from discord import ( from discord.ext.commands import Cog from discord.utils import basic_autocomplete -from classes import PycordEvent, PycordGuild +from classes import PycordEvent, PycordEventStage, PycordGuild from classes.pycord_bot import PycordBot -from modules.utils import autocomplete_active_events, validate_event_validity +from modules.utils import ( + autocomplete_active_events, + get_unix_timestamp, + is_operation_confirmed, + validate_event_validity, +) -class Event(Cog): +class CogEvent(Cog): """Cog with event management commands.""" def __init__(self, bot: PycordBot): @@ -46,8 +51,7 @@ class Event(Cog): 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.") + await ctx.respond(self.bot._("guild_unconfigured_admin", "messages", locale=ctx.locale)) return guild_timezone: ZoneInfo = ZoneInfo(guild.timezone) @@ -59,8 +63,10 @@ class Event(Cog): 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.") + # 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) @@ -78,8 +84,10 @@ class Event(Cog): thumbnail=processed_media[0] if thumbnail else None, ) - # TODO Make a nice message - await ctx.respond("Event has been created.") + # TODO Introduce i18n + await ctx.respond( + f"Event **{event.name}** has been created and will take place ." + ) # TODO Introduce i18n @command_group.command( @@ -115,8 +123,7 @@ class Event(Cog): return if not guild.is_configured(): - # TODO Make a nice message - await ctx.respond("Guild is not configured.") + await ctx.respond(self.bot._("guild_unconfigured_admin", "messages", locale=ctx.locale)) return guild_timezone: ZoneInfo = ZoneInfo(guild.timezone) @@ -132,9 +139,7 @@ class Event(Cog): return try: - end_date: datetime = ( - pycord_event.ends if end is None else datetime.strptime(end, "%d.%m.%Y %H:%M") - ) + 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 @@ -155,8 +160,12 @@ class Event(Cog): 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("Event has been updated.") + await ctx.respond( + f"Event **{pycord_event.name}** has been updated and will take place ." + ) # TODO Introduce i18n @command_group.command( @@ -176,9 +185,7 @@ class Event(Cog): event: str, confirm: bool = False, ) -> None: - if confirm is None or not confirm: - # TODO Make a nice message - await ctx.respond("Operation not confirmed.") + if not (await is_operation_confirmed(ctx, confirm)): return guild: PycordGuild = await self.bot.find_guild(ctx.guild.id) @@ -191,8 +198,7 @@ class Event(Cog): return if not guild.is_configured(): - # TODO Make a nice message - await ctx.respond("Guild is not configured.") + await ctx.respond(self.bot._("guild_unconfigured_admin", "messages", locale=ctx.locale)) return start_date: datetime = pycord_event.starts.replace(tzinfo=ZoneInfo("UTC")) @@ -210,8 +216,10 @@ class Event(Cog): await pycord_event.cancel() + # TODO Notify participants about cancellation + # TODO Make a nice message - await ctx.respond("Event was cancelled.") + await ctx.respond(f"Event **{pycord_event.name}** was cancelled.") # TODO Introduce i18n @command_group.command( @@ -237,11 +245,21 @@ class Event(Cog): 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: " + f"**Event details**\n\nName: {pycord_event.name}\nStarts: \nEnds: \n\nStages:\n{stages_string}" ) def setup(bot: PycordBot) -> None: - bot.add_cog(Event(bot)) + bot.add_cog(CogEvent(bot)) diff --git a/cogs/guess.py b/cogs/cog_guess.py similarity index 84% rename from cogs/guess.py rename to cogs/cog_guess.py index 7a89f52..7ce17b2 100644 --- a/cogs/guess.py +++ b/cogs/cog_guess.py @@ -2,13 +2,13 @@ from typing import List from bson import ObjectId from bson.errors import InvalidId -from discord import ApplicationContext, Cog, option, slash_command, File +from discord import ApplicationContext, Cog, File, option, slash_command -from classes import PycordEvent, PycordUser, PycordGuild, PycordEventStage +from classes import PycordEvent, PycordEventStage, PycordGuild, PycordUser from classes.pycord_bot import PycordBot -class Guess(Cog): +class CogGuess(Cog): """Cog with the guessing command.""" def __init__(self, bot: PycordBot): @@ -24,17 +24,14 @@ class Guess(Cog): 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.") + await ctx.respond(self.bot._("guild_unconfigured", "messages", locale=ctx.locale)) return user: PycordUser = await self.bot.find_user(ctx.author, ctx.guild) if user.is_jailed: # TODO Make a nice message - await ctx.respond( - "You are jailed and cannot interact with events. Please, contact the administrator." - ) + await ctx.respond("You are jailed and cannot interact with events. Please, contact the administrator.") return if user.current_event_id is None or user.current_stage_id is None: @@ -47,9 +44,7 @@ class Guess(Cog): stage: PycordEventStage = await self.bot.find_event_stage(user.current_stage_id) except (InvalidId, RuntimeError): # TODO Make a nice message - await ctx.respond( - "Your event could not be found. Please, report this issue to the event's management." - ) + await ctx.respond("Your event could not be found. Please, report this issue to the event's management.") return if ctx.channel_id != user.event_channels[str(event._id)]: @@ -107,4 +102,4 @@ class Guess(Cog): def setup(bot: PycordBot) -> None: - bot.add_cog(Guess(bot)) + bot.add_cog(CogGuess(bot)) diff --git a/cogs/register.py b/cogs/cog_register.py similarity index 77% rename from cogs/register.py rename to cogs/cog_register.py index 9c75dfa..c61a77a 100644 --- a/cogs/register.py +++ b/cogs/cog_register.py @@ -1,15 +1,13 @@ -from zoneinfo import ZoneInfo - from bson.errors import InvalidId from discord import ApplicationContext, Cog, option, slash_command from discord.utils import basic_autocomplete from classes import PycordEvent, PycordGuild, PycordUser from classes.pycord_bot import PycordBot -from modules.utils import autocomplete_active_events +from modules.utils import autocomplete_active_events, get_unix_timestamp -class Register(Cog): +class CogRegister(Cog): """Cog with the event registration command.""" def __init__(self, bot: PycordBot): @@ -36,17 +34,14 @@ class Register(Cog): return if not guild.is_configured(): - # TODO Make a nice message - await ctx.respond("Guild is not configured.") + await ctx.respond(self.bot._("guild_unconfigured", "messages", locale=ctx.locale)) return user: PycordUser = await self.bot.find_user(ctx.author, ctx.guild) if user.is_jailed: # TODO Make a nice message - await ctx.respond( - "You are jailed and cannot interact with events. Please, contact the administrator." - ) + await ctx.respond("You are jailed and cannot interact with events. Please, contact the administrator.") return if pycord_event._id in user.registered_event_ids: @@ -58,9 +53,9 @@ class Register(Cog): # TODO Make a nice message await ctx.respond( - f"You are now registered for the event **{pycord_event.name}**.\n\nNew channel will be created for you and further instructions will be provided as soon as the event starts . Good luck!" + f"You are now registered for the event **{pycord_event.name}**.\n\nNew channel will be created for you and further instructions will be provided as soon as the event starts . Good luck!" ) def setup(bot: PycordBot) -> None: - bot.add_cog(Register(bot)) + bot.add_cog(CogRegister(bot)) diff --git a/cogs/stage.py b/cogs/cog_stage.py similarity index 79% rename from cogs/stage.py rename to cogs/cog_stage.py index bbfad5e..8096607 100644 --- a/cogs/stage.py +++ b/cogs/cog_stage.py @@ -1,6 +1,4 @@ -from datetime import datetime -from typing import List, Dict, Any -from zoneinfo import ZoneInfo +from typing import Any, Dict, List from bson.errors import InvalidId from discord import ApplicationContext, Attachment, SlashCommandGroup, option @@ -9,31 +7,15 @@ 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 +from modules.utils import ( + autocomplete_active_events, + autocomplete_event_stages, + is_event_status_valid, + is_operation_confirmed, +) -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): +class CogStage(Cog): """Cog with event stage management commands.""" def __init__(self, bot: PycordBot): @@ -67,8 +49,7 @@ class Stage(Cog): 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.") + await ctx.respond(self.bot._("guild_unconfigured_admin", "messages", locale=ctx.locale)) return try: @@ -78,12 +59,10 @@ class Stage(Cog): await ctx.respond("Event was not found.") return - if not (await validate_event_status(ctx, pycord_event)): + if not (await is_event_status_valid(ctx, pycord_event)): return - processed_media: List[Dict[str, Any]] = ( - [] if media is None else await self.bot.process_attachments([media]) - ) + 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, @@ -137,8 +116,7 @@ class Stage(Cog): 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.") + await ctx.respond(self.bot._("guild_unconfigured_admin", "messages", locale=ctx.locale)) return try: @@ -148,7 +126,7 @@ class Stage(Cog): await ctx.respond("Event was not found.") return - if not (await validate_event_status(ctx, pycord_event)): + if not (await is_event_status_valid(ctx, pycord_event)): return try: @@ -163,9 +141,7 @@ class Stage(Cog): 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]) - ) + 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( @@ -201,16 +177,13 @@ class Stage(Cog): 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.") + if not (await is_operation_confirmed(ctx, confirm)): 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.") + await ctx.respond(self.bot._("guild_unconfigured_admin", "messages", locale=ctx.locale)) return try: @@ -220,7 +193,7 @@ class Stage(Cog): await ctx.respond("Event was not found.") return - if not (await validate_event_status(ctx, pycord_event)): + if not (await is_event_status_valid(ctx, pycord_event)): return try: @@ -238,4 +211,4 @@ class Stage(Cog): def setup(bot: PycordBot) -> None: - bot.add_cog(Stage(bot)) + bot.add_cog(CogStage(bot)) diff --git a/cogs/unregister.py b/cogs/cog_unregister.py similarity index 78% rename from cogs/unregister.py rename to cogs/cog_unregister.py index be4d521..65681e2 100644 --- a/cogs/unregister.py +++ b/cogs/cog_unregister.py @@ -4,10 +4,10 @@ from discord.utils import basic_autocomplete from classes import PycordEvent, PycordGuild, PycordUser from classes.pycord_bot import PycordBot -from modules.utils import autocomplete_user_registered_events +from modules.utils import autocomplete_user_registered_events, is_operation_confirmed -class Unregister(Cog): +class CogUnregister(Cog): """Cog with the event unregistration command.""" def __init__(self, bot: PycordBot): @@ -25,9 +25,7 @@ class Unregister(Cog): ) @option("confirm", description="Confirmation of the operation", required=False) async def command_unregister(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.") + if not (await is_operation_confirmed(ctx, confirm)): return guild: PycordGuild = await self.bot.find_guild(ctx.guild.id) @@ -40,17 +38,14 @@ class Unregister(Cog): return if not guild.is_configured(): - # TODO Make a nice message - await ctx.respond("Guild is not configured.") + await ctx.respond(self.bot._("guild_unconfigured", "messages", locale=ctx.locale)) return user: PycordUser = await self.bot.find_user(ctx.author, ctx.guild) if user.is_jailed: # TODO Make a nice message - await ctx.respond( - "You are jailed and cannot interact with events. Please, contact the administrator." - ) + await ctx.respond("You are jailed and cannot interact with events. Please, contact the administrator.") return if pycord_event._id not in user.registered_event_ids: @@ -66,4 +61,4 @@ class Unregister(Cog): def setup(bot: PycordBot) -> None: - bot.add_cog(Unregister(bot)) + bot.add_cog(CogUnregister(bot)) diff --git a/cogs/user.py b/cogs/cog_user.py similarity index 76% rename from cogs/user.py rename to cogs/cog_user.py index 2d24fe4..cbeeb40 100644 --- a/cogs/user.py +++ b/cogs/cog_user.py @@ -8,9 +8,10 @@ from discord.ext.commands import Cog from classes import PycordUser from classes.pycord_bot import PycordBot +from modules.utils import is_operation_confirmed -class User(Cog): +class CogUser(Cog): """Cog with user management commands.""" def __init__(self, bot: PycordBot): @@ -53,9 +54,7 @@ class User(Cog): description="Selected user", ) @option("confirm", description="Confirmation of the operation", required=False) - async def command_user_delete_channel( - self, ctx: ApplicationContext, user: User, confirm: bool = False - ) -> None: + async def command_user_delete_channel(self, ctx: ApplicationContext, user: User, confirm: bool = False) -> None: await ctx.respond("Not implemented.") # TODO Introduce i18n @@ -69,23 +68,20 @@ class User(Cog): ) @option("confirm", description="Confirmation of the operation", required=False) async def command_user_jail(self, ctx: ApplicationContext, user: User, confirm: bool = False) -> None: - if confirm is None or not confirm: - await ctx.respond(self.bot._("operation_unconfirmed", "messages", locale=ctx.locale)) + if not (await is_operation_confirmed(ctx, confirm)): return pycord_user: PycordUser = await self.bot.find_user(user, ctx.guild) if pycord_user.is_jailed: - # TODO Make a nice message + # TODO Introduce i18n await ctx.respond(f"User **{user.display_name}** is already jailed.") return await pycord_user.jail(self.bot.cache) - # TODO Make a nice message - await ctx.respond( - f"User **{user.display_name}** has been jailed and cannot interact with events anymore." - ) + # TODO Introduce i18n + await ctx.respond(f"User **{user.display_name}** has been jailed and cannot interact with events anymore.") # TODO Introduce i18n @command_group.command( @@ -98,24 +94,21 @@ class User(Cog): ) @option("confirm", description="Confirmation of the operation", required=False) async def command_user_unjail(self, ctx: ApplicationContext, user: User, confirm: bool = False) -> None: - if confirm is None or not confirm: - await ctx.respond(self.bot._("operation_unconfirmed", "messages", locale=ctx.locale)) + if not (await is_operation_confirmed(ctx, confirm)): return pycord_user: PycordUser = await self.bot.find_user(user, ctx.guild) if not pycord_user.is_jailed: - # TODO Make a nice message + # TODO Introduce i18n await ctx.respond(f"User **{user.display_name}** is not jailed.") return await pycord_user.unjail(self.bot.cache) - # TODO Make a nice message - await ctx.respond( - f"User **{user.display_name}** has been unjailed and can interact with events again." - ) + # TODO Introduce i18n + await ctx.respond(f"User **{user.display_name}** has been unjailed and can interact with events again.") def setup(bot: PycordBot) -> None: - bot.add_cog(User(bot)) + bot.add_cog(CogUser(bot)) diff --git a/main.py b/main.py index 58f70ed..d86b1ed 100644 --- a/main.py +++ b/main.py @@ -6,7 +6,7 @@ from os import makedirs from pathlib import Path from sys import exit -from discord import LoginFailure, Intents +from discord import Intents, LoginFailure from libbot.utils import config_get from classes.pycord_bot import PycordBot diff --git a/modules/utils/__init__.py b/modules/utils/__init__.py index 12b22d3..c3f0ed2 100644 --- a/modules/utils/__init__.py +++ b/modules/utils/__init__.py @@ -7,5 +7,7 @@ from .autocomplete_utils import ( autocomplete_user_registered_events, ) from .cache_utils import restore_from_cache +from .datetime_utils import get_unix_timestamp from .event_utils import validate_event_validity from .logging_utils import get_logger, get_logging_config +from .validation_utils import is_event_status_valid, is_operation_confirmed diff --git a/modules/utils/autocomplete_utils.py b/modules/utils/autocomplete_utils.py index 06454a2..502244f 100644 --- a/modules/utils/autocomplete_utils.py +++ b/modules/utils/autocomplete_utils.py @@ -92,8 +92,6 @@ async def autocomplete_event_stages(ctx: AutocompleteContext) -> List[OptionChoi event_stages: List[OptionChoice] = [] async for result in col_stages.find(query).sort([("sequence", ASCENDING)]): - event_stages.append( - OptionChoice(f"{result['sequence']+1} ({result['question']})", str(result["_id"])) - ) + event_stages.append(OptionChoice(f"{result['sequence']+1} ({result['question']})", str(result["_id"]))) return event_stages diff --git a/modules/utils/datetime_utils.py b/modules/utils/datetime_utils.py new file mode 100644 index 0000000..61b384c --- /dev/null +++ b/modules/utils/datetime_utils.py @@ -0,0 +1,7 @@ +from datetime import datetime +from zoneinfo import ZoneInfo + + +# TODO Add documentation +def get_unix_timestamp(date: datetime) -> int: + return int((date.replace(tzinfo=ZoneInfo("UTC"))).timestamp()) diff --git a/modules/utils/validation_utils.py b/modules/utils/validation_utils.py new file mode 100644 index 0000000..f7f330f --- /dev/null +++ b/modules/utils/validation_utils.py @@ -0,0 +1,33 @@ +from datetime import datetime +from zoneinfo import ZoneInfo + +from discord import ApplicationContext + + +async def is_operation_confirmed(ctx: ApplicationContext, confirm: bool) -> bool: + if confirm is None or not confirm: + await ctx.respond(ctx.bot._("operation_unconfirmed", "messages", locale=ctx.locale)) + return False + + return True + + +async def is_event_status_valid( + 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 diff --git a/pyproject.toml b/pyproject.toml index 8285e6a..0b2a344 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ readme = "README.md" requires-python = ">=3.11" [tool.black] -line-length = 108 +line-length = 118 target-version = ["py311", "py312", "py313"] [tool.isort]