Improved some messages and prepared them for i18n

This commit is contained in:
2025-04-26 18:21:09 +02:00
parent a17b1cd768
commit ca1e47b55a
16 changed files with 167 additions and 172 deletions

View File

@@ -1,17 +1,17 @@
from datetime import datetime from datetime import datetime
from logging import Logger from logging import Logger
from pathlib import Path from pathlib import Path
from typing import Any, override, List, Dict from typing import Any, Dict, List, override
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
from bson import ObjectId 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.classes import CacheMemcached, CacheRedis
from libbot.cache.manager import create_cache_client from libbot.cache.manager import create_cache_client
from libbot.pycord.classes import PycordBot as LibPycordBot from libbot.pycord.classes import PycordBot as LibPycordBot
from classes import PycordEvent, PycordEventStage, PycordGuild, PycordUser 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 from modules.utils import get_logger
logger: Logger = get_logger(__name__) logger: Logger = get_logger(__name__)
@@ -53,9 +53,7 @@ class PycordBot(LibPycordBot):
await super().close(**kwargs) await super().close(**kwargs)
async def _schedule_tasks(self) -> None: async def _schedule_tasks(self) -> None:
self.scheduler.add_job( self.scheduler.add_job(self._execute_event_controller, trigger="cron", minute="*/1", id="event_controller")
self._execute_event_controller, trigger="cron", minute="*/1", id="event_controller"
)
async def _execute_event_controller(self) -> None: async def _execute_event_controller(self) -> None:
await self._process_events_start() await self._process_events_start()
@@ -105,9 +103,7 @@ class PycordBot(LibPycordBot):
first_stage_files: List[File] | None = first_stage.get_media_files() first_stage_files: List[File] | None = first_stage.get_media_files()
await user_channel.send( await user_channel.send(f"First stage...\n\n{first_stage.question}", files=first_stage_files)
f"First stage...\n\n{first_stage.question}", files=first_stage_files
)
# TODO Make a nice message # TODO Make a nice message
await self.notify_admins( await self.notify_admins(
@@ -126,7 +122,7 @@ class PycordBot(LibPycordBot):
for event in events: for event in events:
guild: Guild = self.get_guild(event.guild_id) guild: Guild = self.get_guild(event.guild_id)
pycord_guild: PycordGuild = await self.find_guild(guild) 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 # TODO Make a nice message
stages_string: str = "\n\n".join( stages_string: str = "\n\n".join(
@@ -174,7 +170,8 @@ class PycordBot(LibPycordBot):
return users 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] return [(await self.find_event_stage(stage_id)) for stage_id in event.stage_ids]
# TODO Add documentation # TODO Add documentation
@@ -254,9 +251,7 @@ class PycordBot(LibPycordBot):
return event_stage return event_stage
# TODO Document this method # TODO Document this method
async def find_event( async def find_event(self, event_id: str | ObjectId | None = None, event_name: str | None = None) -> PycordEvent:
self, event_id: str | ObjectId | None = None, event_name: str | None = None
) -> PycordEvent:
if event_id is None and event_name is None: if event_id is None and event_name is None:
raise AttributeError("Either event's ID or name must be provided!") raise AttributeError("Either event's ID or name must be provided!")

View File

@@ -286,9 +286,7 @@ class PycordEvent:
stage_index: int = self.stage_ids.index(event_stage_id) stage_index: int = self.stage_ids.index(event_stage_id)
old_stage_index: int = old_stage_ids.index(event_stage_id) old_stage_index: int = old_stage_ids.index(event_stage_id)
logger.debug( logger.debug("Indexes for %s: was %s and is now %s", event_stage_id, old_stage_index, stage_index)
"Indexes for %s: was %s and is now %s", event_stage_id, old_stage_index, stage_index
)
if stage_index != old_stage_index: if stage_index != old_stage_index:
await (await bot.find_event_stage(event_stage_id)).update(cache, sequence=stage_index) await (await bot.find_event_stage(event_stage_id)).update(cache, sequence=stage_index)

View File

@@ -4,7 +4,15 @@ from logging import Logger
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
from bson import ObjectId 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 discord.abc import GuildChannel
from libbot.cache.classes import Cache from libbot.cache.classes import Cache
from pymongo.results import InsertOneResult from pymongo.results import InsertOneResult
@@ -106,12 +114,8 @@ class PycordUser:
"guild_id": self.guild_id, "guild_id": self.guild_id,
"event_channels": self.event_channels, "event_channels": self.event_channels,
"is_jailed": self.is_jailed, "is_jailed": self.is_jailed,
"current_event_id": ( "current_event_id": (self.current_event_id if not json_compatible else str(self.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_stage_id": (
self.current_stage_id if not json_compatible else str(self.current_stage_id)
),
"registered_event_ids": ( "registered_event_ids": (
self.registered_event_ids self.registered_event_ids
if not json_compatible if not json_compatible
@@ -367,9 +371,7 @@ class PycordUser:
# TODO Add documentation # TODO Add documentation
async def set_event_stage(self, stage_id: str | ObjectId | None, cache: Optional[Cache] = None) -> None: async def set_event_stage(self, stage_id: str | ObjectId | None, cache: Optional[Cache] = None) -> None:
await self._set( await self._set(cache, current_stage_id=stage_id if isinstance(stage_id, str) else ObjectId(stage_id))
cache, current_stage_id=stage_id if isinstance(stage_id, str) else ObjectId(stage_id)
)
async def jail(self, cache: Optional[Cache] = None) -> None: async def jail(self, cache: Optional[Cache] = None) -> None:
await self._set(cache, is_jailed=True) await self._set(cache, is_jailed=True)

View File

@@ -9,14 +9,14 @@ from discord import (
) )
from discord.ext.commands import Cog from discord.ext.commands import Cog
from discord.utils import basic_autocomplete 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 import PycordGuild
from classes.pycord_bot import PycordBot 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.""" """Cog with guild configuration commands."""
def __init__(self, bot: PycordBot): def __init__(self, bot: PycordBot):
@@ -36,18 +36,14 @@ class Config(Cog):
@option( @option(
"category", "category",
description=_("description", "commands", "config_set", "options", "category"), description=_("description", "commands", "config_set", "options", "category"),
description_localizations=in_every_locale( description_localizations=in_every_locale("description", "commands", "config_set", "options", "category"),
"description", "commands", "config_set", "options", "category"
),
required=True, required=True,
) )
@option("channel", description="Text channel for admin notifications", required=True) @option("channel", description="Text channel for admin notifications", required=True)
@option( @option(
"timezone", "timezone",
description=_("description", "commands", "config_set", "options", "timezone"), description=_("description", "commands", "config_set", "options", "timezone"),
description_localizations=in_every_locale( description_localizations=in_every_locale("description", "commands", "config_set", "options", "timezone"),
"description", "commands", "config_set", "options", "timezone"
),
autocomplete=basic_autocomplete(autocomplete_timezones), autocomplete=basic_autocomplete(autocomplete_timezones),
required=True, required=True,
) )
@@ -63,9 +59,7 @@ class Config(Cog):
try: try:
timezone_parsed: ZoneInfo = ZoneInfo(timezone) timezone_parsed: ZoneInfo = ZoneInfo(timezone)
except ZoneInfoNotFoundError: except ZoneInfoNotFoundError:
await ctx.respond( await ctx.respond(self.bot._("timezone_invalid", "messages", locale=ctx.locale).format(timezone=timezone))
self.bot._("timezone_invalid", "messages", locale=ctx.locale).format(timezone=timezone)
)
return return
await guild.update( await guild.update(
@@ -85,14 +79,11 @@ class Config(Cog):
@option( @option(
"confirm", "confirm",
description=_("description", "commands", "config_reset", "options", "confirm"), description=_("description", "commands", "config_reset", "options", "confirm"),
description_localizations=in_every_locale( description_localizations=in_every_locale("description", "commands", "config_reset", "options", "confirm"),
"description", "commands", "config_reset", "options", "confirm"
),
required=False, required=False,
) )
async def command_config_reset(self, ctx: ApplicationContext, confirm: bool = False) -> None: async def command_config_reset(self, ctx: ApplicationContext, confirm: bool = False) -> None:
if confirm is None or not confirm: if not (await is_operation_confirmed(ctx, confirm)):
await ctx.respond(self.bot._("operation_unconfirmed", "messages", locale=ctx.locale))
return return
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id) guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
@@ -123,4 +114,4 @@ class Config(Cog):
def setup(bot: PycordBot) -> None: def setup(bot: PycordBot) -> None:
bot.add_cog(Config(bot)) bot.add_cog(CogConfig(bot))

View File

@@ -1,5 +1,5 @@
from datetime import datetime from datetime import datetime
from typing import Dict, List, Any from typing import Any, Dict, List
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
from bson.errors import InvalidId from bson.errors import InvalidId
@@ -12,12 +12,17 @@ from discord import (
from discord.ext.commands import Cog from discord.ext.commands import Cog
from discord.utils import basic_autocomplete from discord.utils import basic_autocomplete
from classes import PycordEvent, PycordGuild from classes import PycordEvent, PycordEventStage, PycordGuild
from classes.pycord_bot import PycordBot 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.""" """Cog with event management commands."""
def __init__(self, bot: PycordBot): def __init__(self, bot: PycordBot):
@@ -46,8 +51,7 @@ class Event(Cog):
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id) guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
if not guild.is_configured(): if not guild.is_configured():
# TODO Make a nice message await ctx.respond(self.bot._("guild_unconfigured_admin", "messages", locale=ctx.locale))
await ctx.respond("Guild is not configured.")
return return
guild_timezone: ZoneInfo = ZoneInfo(guild.timezone) guild_timezone: ZoneInfo = ZoneInfo(guild.timezone)
@@ -59,8 +63,10 @@ class Event(Cog):
start_date = start_date.replace(tzinfo=guild_timezone) start_date = start_date.replace(tzinfo=guild_timezone)
end_date = end_date.replace(tzinfo=guild_timezone) end_date = end_date.replace(tzinfo=guild_timezone)
except ValueError: except ValueError:
# TODO Make a nice message # TODO Introduce i18n
await ctx.respond("Could not parse start and end dates.") await ctx.respond(
"Could not parse start and end dates. Please, make sure these are provided in `DD.MM.YYYY HH:MM` format."
)
return return
await validate_event_validity(ctx, name, start_date, end_date, guild_timezone) 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, thumbnail=processed_media[0] if thumbnail else None,
) )
# TODO Make a nice message # TODO Introduce i18n
await ctx.respond("Event has been created.") await ctx.respond(
f"Event **{event.name}** has been created and will take place <t:{get_unix_timestamp(event.starts)}:R>."
)
# TODO Introduce i18n # TODO Introduce i18n
@command_group.command( @command_group.command(
@@ -115,8 +123,7 @@ class Event(Cog):
return return
if not guild.is_configured(): if not guild.is_configured():
# TODO Make a nice message await ctx.respond(self.bot._("guild_unconfigured_admin", "messages", locale=ctx.locale))
await ctx.respond("Guild is not configured.")
return return
guild_timezone: ZoneInfo = ZoneInfo(guild.timezone) guild_timezone: ZoneInfo = ZoneInfo(guild.timezone)
@@ -132,9 +139,7 @@ class Event(Cog):
return return
try: try:
end_date: datetime = ( end_date: datetime = pycord_event.ends if end is None else datetime.strptime(end, "%d.%m.%Y %H:%M")
pycord_event.ends if end is None else datetime.strptime(end, "%d.%m.%Y %H:%M")
)
end_date = end_date.replace(tzinfo=guild_timezone) end_date = end_date.replace(tzinfo=guild_timezone)
except ValueError: except ValueError:
# TODO Make a nice message # TODO Make a nice message
@@ -155,8 +160,12 @@ class Event(Cog):
thumbnail=pycord_event.thumbnail if thumbnail is None else processed_media[0], thumbnail=pycord_event.thumbnail if thumbnail is None else processed_media[0],
) )
# TODO Notify participants about time changes
# TODO Make a nice message # 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 <t:{get_unix_timestamp(pycord_event.starts)}:R>."
)
# TODO Introduce i18n # TODO Introduce i18n
@command_group.command( @command_group.command(
@@ -176,9 +185,7 @@ class Event(Cog):
event: str, event: str,
confirm: bool = False, confirm: bool = False,
) -> None: ) -> None:
if confirm is None or not confirm: if not (await is_operation_confirmed(ctx, confirm)):
# TODO Make a nice message
await ctx.respond("Operation not confirmed.")
return return
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id) guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
@@ -191,8 +198,7 @@ class Event(Cog):
return return
if not guild.is_configured(): if not guild.is_configured():
# TODO Make a nice message await ctx.respond(self.bot._("guild_unconfigured_admin", "messages", locale=ctx.locale))
await ctx.respond("Guild is not configured.")
return return
start_date: datetime = pycord_event.starts.replace(tzinfo=ZoneInfo("UTC")) start_date: datetime = pycord_event.starts.replace(tzinfo=ZoneInfo("UTC"))
@@ -210,8 +216,10 @@ class Event(Cog):
await pycord_event.cancel() await pycord_event.cancel()
# TODO Notify participants about cancellation
# TODO Make a nice message # TODO Make a nice message
await ctx.respond("Event was cancelled.") await ctx.respond(f"Event **{pycord_event.name}** was cancelled.")
# TODO Introduce i18n # TODO Introduce i18n
@command_group.command( @command_group.command(
@@ -237,11 +245,21 @@ class Event(Cog):
starts_date: datetime = pycord_event.starts.replace(tzinfo=ZoneInfo("UTC")) starts_date: datetime = pycord_event.starts.replace(tzinfo=ZoneInfo("UTC"))
ends_date: datetime = pycord_event.ends.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 # 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( await ctx.respond(
f"**Event details**\n\nName: {pycord_event.name}\nStarts: <t:{int(starts_date.timestamp())}>\nEnds: <t:{int(ends_date.timestamp())}>" f"**Event details**\n\nName: {pycord_event.name}\nStarts: <t:{get_unix_timestamp(starts_date)}>\nEnds: <t:{get_unix_timestamp(ends_date)}>\n\nStages:\n{stages_string}"
) )
def setup(bot: PycordBot) -> None: def setup(bot: PycordBot) -> None:
bot.add_cog(Event(bot)) bot.add_cog(CogEvent(bot))

View File

@@ -2,13 +2,13 @@ from typing import List
from bson import ObjectId from bson import ObjectId
from bson.errors import InvalidId 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 from classes.pycord_bot import PycordBot
class Guess(Cog): class CogGuess(Cog):
"""Cog with the guessing command.""" """Cog with the guessing command."""
def __init__(self, bot: PycordBot): def __init__(self, bot: PycordBot):
@@ -24,17 +24,14 @@ class Guess(Cog):
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id) guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
if not guild.is_configured(): if not guild.is_configured():
# TODO Make a nice message await ctx.respond(self.bot._("guild_unconfigured", "messages", locale=ctx.locale))
await ctx.respond("Guild is not configured.")
return return
user: PycordUser = await self.bot.find_user(ctx.author, ctx.guild) user: PycordUser = await self.bot.find_user(ctx.author, ctx.guild)
if user.is_jailed: if user.is_jailed:
# TODO Make a nice message # TODO Make a nice message
await ctx.respond( await ctx.respond("You are jailed and cannot interact with events. Please, contact the administrator.")
"You are jailed and cannot interact with events. Please, contact the administrator."
)
return return
if user.current_event_id is None or user.current_stage_id is None: 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) stage: PycordEventStage = await self.bot.find_event_stage(user.current_stage_id)
except (InvalidId, RuntimeError): except (InvalidId, RuntimeError):
# TODO Make a nice message # TODO Make a nice message
await ctx.respond( await ctx.respond("Your event could not be found. Please, report this issue to the event's management.")
"Your event could not be found. Please, report this issue to the event's management."
)
return return
if ctx.channel_id != user.event_channels[str(event._id)]: if ctx.channel_id != user.event_channels[str(event._id)]:
@@ -107,4 +102,4 @@ class Guess(Cog):
def setup(bot: PycordBot) -> None: def setup(bot: PycordBot) -> None:
bot.add_cog(Guess(bot)) bot.add_cog(CogGuess(bot))

View File

@@ -1,15 +1,13 @@
from zoneinfo import ZoneInfo
from bson.errors import InvalidId from bson.errors import InvalidId
from discord import ApplicationContext, Cog, option, slash_command from discord import ApplicationContext, Cog, option, slash_command
from discord.utils import basic_autocomplete from discord.utils import basic_autocomplete
from classes import PycordEvent, PycordGuild, PycordUser from classes import PycordEvent, PycordGuild, PycordUser
from classes.pycord_bot import PycordBot 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.""" """Cog with the event registration command."""
def __init__(self, bot: PycordBot): def __init__(self, bot: PycordBot):
@@ -36,17 +34,14 @@ class Register(Cog):
return return
if not guild.is_configured(): if not guild.is_configured():
# TODO Make a nice message await ctx.respond(self.bot._("guild_unconfigured", "messages", locale=ctx.locale))
await ctx.respond("Guild is not configured.")
return return
user: PycordUser = await self.bot.find_user(ctx.author, ctx.guild) user: PycordUser = await self.bot.find_user(ctx.author, ctx.guild)
if user.is_jailed: if user.is_jailed:
# TODO Make a nice message # TODO Make a nice message
await ctx.respond( await ctx.respond("You are jailed and cannot interact with events. Please, contact the administrator.")
"You are jailed and cannot interact with events. Please, contact the administrator."
)
return return
if pycord_event._id in user.registered_event_ids: if pycord_event._id in user.registered_event_ids:
@@ -58,9 +53,9 @@ class Register(Cog):
# TODO Make a nice message # TODO Make a nice message
await ctx.respond( 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 <t:{int((pycord_event.starts.replace(tzinfo=ZoneInfo('UTC'))).timestamp())}:R>. 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 <t:{get_unix_timestamp(pycord_event.starts)}:R>. Good luck!"
) )
def setup(bot: PycordBot) -> None: def setup(bot: PycordBot) -> None:
bot.add_cog(Register(bot)) bot.add_cog(CogRegister(bot))

View File

@@ -1,6 +1,4 @@
from datetime import datetime from typing import Any, Dict, List
from typing import List, Dict, Any
from zoneinfo import ZoneInfo
from bson.errors import InvalidId from bson.errors import InvalidId
from discord import ApplicationContext, Attachment, SlashCommandGroup, option 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 import PycordEvent, PycordEventStage, PycordGuild
from classes.pycord_bot import PycordBot 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( class CogStage(Cog):
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.""" """Cog with event stage management commands."""
def __init__(self, bot: PycordBot): def __init__(self, bot: PycordBot):
@@ -67,8 +49,7 @@ class Stage(Cog):
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id) guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
if not guild.is_configured(): if not guild.is_configured():
# TODO Make a nice message await ctx.respond(self.bot._("guild_unconfigured_admin", "messages", locale=ctx.locale))
await ctx.respond("Guild is not configured.")
return return
try: try:
@@ -78,12 +59,10 @@ class Stage(Cog):
await ctx.respond("Event was not found.") await ctx.respond("Event was not found.")
return return
if not (await validate_event_status(ctx, pycord_event)): if not (await is_event_status_valid(ctx, pycord_event)):
return return
processed_media: List[Dict[str, Any]] = ( processed_media: List[Dict[str, Any]] = [] if media is None else await self.bot.process_attachments([media])
[] if media is None else await self.bot.process_attachments([media])
)
event_stage: PycordEventStage = await self.bot.create_event_stage( event_stage: PycordEventStage = await self.bot.create_event_stage(
event=pycord_event, event=pycord_event,
@@ -137,8 +116,7 @@ class Stage(Cog):
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id) guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
if not guild.is_configured(): if not guild.is_configured():
# TODO Make a nice message await ctx.respond(self.bot._("guild_unconfigured_admin", "messages", locale=ctx.locale))
await ctx.respond("Guild is not configured.")
return return
try: try:
@@ -148,7 +126,7 @@ class Stage(Cog):
await ctx.respond("Event was not found.") await ctx.respond("Event was not found.")
return return
if not (await validate_event_status(ctx, pycord_event)): if not (await is_event_status_valid(ctx, pycord_event)):
return return
try: try:
@@ -163,9 +141,7 @@ class Stage(Cog):
await ctx.respond("Stage sequence out of range.") await ctx.respond("Stage sequence out of range.")
return return
processed_media: List[Dict[str, Any]] = ( processed_media: List[Dict[str, Any]] = [] if media is None else await self.bot.process_attachments([media])
[] 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): if not (question is None and answer is None and media is None and remove_media is False):
await event_stage.update( await event_stage.update(
@@ -201,16 +177,13 @@ class Stage(Cog):
async def command_stage_delete( async def command_stage_delete(
self, ctx: ApplicationContext, event: str, stage: str, confirm: bool = False self, ctx: ApplicationContext, event: str, stage: str, confirm: bool = False
) -> None: ) -> None:
if confirm is None or not confirm: if not (await is_operation_confirmed(ctx, confirm)):
# TODO Make a nice message
await ctx.respond("Operation not confirmed.")
return return
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id) guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
if not guild.is_configured(): if not guild.is_configured():
# TODO Make a nice message await ctx.respond(self.bot._("guild_unconfigured_admin", "messages", locale=ctx.locale))
await ctx.respond("Guild is not configured.")
return return
try: try:
@@ -220,7 +193,7 @@ class Stage(Cog):
await ctx.respond("Event was not found.") await ctx.respond("Event was not found.")
return return
if not (await validate_event_status(ctx, pycord_event)): if not (await is_event_status_valid(ctx, pycord_event)):
return return
try: try:
@@ -238,4 +211,4 @@ class Stage(Cog):
def setup(bot: PycordBot) -> None: def setup(bot: PycordBot) -> None:
bot.add_cog(Stage(bot)) bot.add_cog(CogStage(bot))

View File

@@ -4,10 +4,10 @@ from discord.utils import basic_autocomplete
from classes import PycordEvent, PycordGuild, PycordUser from classes import PycordEvent, PycordGuild, PycordUser
from classes.pycord_bot import PycordBot 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.""" """Cog with the event unregistration command."""
def __init__(self, bot: PycordBot): def __init__(self, bot: PycordBot):
@@ -25,9 +25,7 @@ class Unregister(Cog):
) )
@option("confirm", description="Confirmation of the operation", required=False) @option("confirm", description="Confirmation of the operation", required=False)
async def command_unregister(self, ctx: ApplicationContext, event: str, confirm: bool = False) -> None: async def command_unregister(self, ctx: ApplicationContext, event: str, confirm: bool = False) -> None:
if confirm is None or not confirm: if not (await is_operation_confirmed(ctx, confirm)):
# TODO Make a nice message
await ctx.respond("Operation not confirmed.")
return return
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id) guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
@@ -40,17 +38,14 @@ class Unregister(Cog):
return return
if not guild.is_configured(): if not guild.is_configured():
# TODO Make a nice message await ctx.respond(self.bot._("guild_unconfigured", "messages", locale=ctx.locale))
await ctx.respond("Guild is not configured.")
return return
user: PycordUser = await self.bot.find_user(ctx.author, ctx.guild) user: PycordUser = await self.bot.find_user(ctx.author, ctx.guild)
if user.is_jailed: if user.is_jailed:
# TODO Make a nice message # TODO Make a nice message
await ctx.respond( await ctx.respond("You are jailed and cannot interact with events. Please, contact the administrator.")
"You are jailed and cannot interact with events. Please, contact the administrator."
)
return return
if pycord_event._id not in user.registered_event_ids: if pycord_event._id not in user.registered_event_ids:
@@ -66,4 +61,4 @@ class Unregister(Cog):
def setup(bot: PycordBot) -> None: def setup(bot: PycordBot) -> None:
bot.add_cog(Unregister(bot)) bot.add_cog(CogUnregister(bot))

View File

@@ -8,9 +8,10 @@ from discord.ext.commands import Cog
from classes import PycordUser from classes import PycordUser
from classes.pycord_bot import PycordBot from classes.pycord_bot import PycordBot
from modules.utils import is_operation_confirmed
class User(Cog): class CogUser(Cog):
"""Cog with user management commands.""" """Cog with user management commands."""
def __init__(self, bot: PycordBot): def __init__(self, bot: PycordBot):
@@ -53,9 +54,7 @@ class User(Cog):
description="Selected user", description="Selected user",
) )
@option("confirm", description="Confirmation of the operation", required=False) @option("confirm", description="Confirmation of the operation", required=False)
async def command_user_delete_channel( async def command_user_delete_channel(self, ctx: ApplicationContext, user: User, confirm: bool = False) -> None:
self, ctx: ApplicationContext, user: User, confirm: bool = False
) -> None:
await ctx.respond("Not implemented.") await ctx.respond("Not implemented.")
# TODO Introduce i18n # TODO Introduce i18n
@@ -69,23 +68,20 @@ class User(Cog):
) )
@option("confirm", description="Confirmation of the operation", required=False) @option("confirm", description="Confirmation of the operation", required=False)
async def command_user_jail(self, ctx: ApplicationContext, user: User, confirm: bool = False) -> None: async def command_user_jail(self, ctx: ApplicationContext, user: User, confirm: bool = False) -> None:
if confirm is None or not confirm: if not (await is_operation_confirmed(ctx, confirm)):
await ctx.respond(self.bot._("operation_unconfirmed", "messages", locale=ctx.locale))
return return
pycord_user: PycordUser = await self.bot.find_user(user, ctx.guild) pycord_user: PycordUser = await self.bot.find_user(user, ctx.guild)
if pycord_user.is_jailed: if pycord_user.is_jailed:
# TODO Make a nice message # TODO Introduce i18n
await ctx.respond(f"User **{user.display_name}** is already jailed.") await ctx.respond(f"User **{user.display_name}** is already jailed.")
return return
await pycord_user.jail(self.bot.cache) await pycord_user.jail(self.bot.cache)
# TODO Make a nice message # TODO Introduce i18n
await ctx.respond( await ctx.respond(f"User **{user.display_name}** has been jailed and cannot interact with events anymore.")
f"User **{user.display_name}** has been jailed and cannot interact with events anymore."
)
# TODO Introduce i18n # TODO Introduce i18n
@command_group.command( @command_group.command(
@@ -98,24 +94,21 @@ class User(Cog):
) )
@option("confirm", description="Confirmation of the operation", required=False) @option("confirm", description="Confirmation of the operation", required=False)
async def command_user_unjail(self, ctx: ApplicationContext, user: User, confirm: bool = False) -> None: async def command_user_unjail(self, ctx: ApplicationContext, user: User, confirm: bool = False) -> None:
if confirm is None or not confirm: if not (await is_operation_confirmed(ctx, confirm)):
await ctx.respond(self.bot._("operation_unconfirmed", "messages", locale=ctx.locale))
return return
pycord_user: PycordUser = await self.bot.find_user(user, ctx.guild) pycord_user: PycordUser = await self.bot.find_user(user, ctx.guild)
if not pycord_user.is_jailed: 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.") await ctx.respond(f"User **{user.display_name}** is not jailed.")
return return
await pycord_user.unjail(self.bot.cache) await pycord_user.unjail(self.bot.cache)
# TODO Make a nice message # TODO Introduce i18n
await ctx.respond( await ctx.respond(f"User **{user.display_name}** has been unjailed and can interact with events again.")
f"User **{user.display_name}** has been unjailed and can interact with events again."
)
def setup(bot: PycordBot) -> None: def setup(bot: PycordBot) -> None:
bot.add_cog(User(bot)) bot.add_cog(CogUser(bot))

View File

@@ -6,7 +6,7 @@ from os import makedirs
from pathlib import Path from pathlib import Path
from sys import exit from sys import exit
from discord import LoginFailure, Intents from discord import Intents, LoginFailure
from libbot.utils import config_get from libbot.utils import config_get
from classes.pycord_bot import PycordBot from classes.pycord_bot import PycordBot

View File

@@ -7,5 +7,7 @@ from .autocomplete_utils import (
autocomplete_user_registered_events, autocomplete_user_registered_events,
) )
from .cache_utils import restore_from_cache from .cache_utils import restore_from_cache
from .datetime_utils import get_unix_timestamp
from .event_utils import validate_event_validity from .event_utils import validate_event_validity
from .logging_utils import get_logger, get_logging_config from .logging_utils import get_logger, get_logging_config
from .validation_utils import is_event_status_valid, is_operation_confirmed

View File

@@ -92,8 +92,6 @@ async def autocomplete_event_stages(ctx: AutocompleteContext) -> List[OptionChoi
event_stages: List[OptionChoice] = [] event_stages: List[OptionChoice] = []
async for result in col_stages.find(query).sort([("sequence", ASCENDING)]): async for result in col_stages.find(query).sort([("sequence", ASCENDING)]):
event_stages.append( event_stages.append(OptionChoice(f"{result['sequence']+1} ({result['question']})", str(result["_id"])))
OptionChoice(f"{result['sequence']+1} ({result['question']})", str(result["_id"]))
)
return event_stages return event_stages

View File

@@ -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())

View File

@@ -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

View File

@@ -5,7 +5,7 @@ readme = "README.md"
requires-python = ">=3.11" requires-python = ">=3.11"
[tool.black] [tool.black]
line-length = 108 line-length = 118
target-version = ["py311", "py312", "py313"] target-version = ["py311", "py312", "py313"]
[tool.isort] [tool.isort]