Improved error handling; Introduced i18n for /register, /unregister and /guess

This commit is contained in:
2025-04-29 13:50:38 +02:00
parent 28d6340847
commit 9d39b803f3
8 changed files with 177 additions and 76 deletions

View File

@@ -5,6 +5,7 @@ from typing import Any, Dict, List, Optional
from zoneinfo import ZoneInfo
from bson import ObjectId
from bson.errors import InvalidId
from discord import Attachment, File, Guild, TextChannel, User
from libbot.cache.classes import CacheMemcached, CacheRedis
from libbot.cache.manager import create_cache_client
@@ -12,7 +13,12 @@ from libbot.pycord.classes import PycordBot as LibPycordBot
from typing_extensions import override
from classes import PycordEvent, PycordEventStage, PycordGuild, PycordUser
from classes.errors import EventStageMissingSequenceError
from classes.errors import (
EventStageMissingSequenceError,
GuildNotFoundError,
EventStageNotFoundError,
EventNotFoundError,
)
from modules.database import col_events, col_users
from modules.utils import get_logger
@@ -82,7 +88,12 @@ class PycordBot(LibPycordBot):
# Process each event
for event in events:
guild: Guild = self.get_guild(event.guild_id)
pycord_guild: PycordGuild = await self.find_guild(guild)
try:
pycord_guild: PycordGuild = await self.find_guild(guild)
except (InvalidId, GuildNotFoundError) as exc:
logger.error("Could not find guild %s (%s) due to: %s.", guild, guild.id, exc_info=exc)
continue
if len(event.stage_ids) == 0:
# TODO Make a nice message for management
@@ -172,8 +183,23 @@ class PycordBot(LibPycordBot):
# Process each event
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)
try:
pycord_guild: PycordGuild = await self.find_guild(guild)
except (InvalidId, GuildNotFoundError) as exc:
logger.error("Could not find guild %s (%s) due to: %s.", guild, guild.id, exc_info=exc)
continue
try:
stages: List[PycordEventStage] = await self.get_event_stages(event)
except (InvalidId, EventNotFoundError, EventStageNotFoundError) as exc:
logger.error(
"Could not event stages of the event %s (%s) due to: %s.",
event,
event._id,
exc_info=exc,
)
continue
# TODO Make a nice message
stages_string: str = "\n\n".join(

View File

@@ -1,5 +1,6 @@
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
from bson.errors import InvalidId
from discord import (
ApplicationContext,
CategoryChannel,
@@ -12,6 +13,7 @@ from discord.utils import basic_autocomplete
from libbot.i18n import _, in_every_locale
from classes import PycordGuild
from classes.errors import GuildNotFoundError
from classes.pycord_bot import PycordBot
from modules.utils import autocomplete_timezones, is_operation_confirmed
@@ -58,7 +60,11 @@ class CogConfig(Cog):
channel: TextChannel,
timezone: str,
) -> None:
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
try:
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
except (InvalidId, GuildNotFoundError):
await ctx.respond(self.bot._("unexpected_error", "messages", locale=ctx.locale))
return
try:
timezone_parsed: ZoneInfo = ZoneInfo(timezone)
@@ -94,7 +100,11 @@ class CogConfig(Cog):
if not (await is_operation_confirmed(ctx, confirm)):
return
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
try:
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
except (InvalidId, GuildNotFoundError):
await ctx.respond(self.bot._("unexpected_error", "messages", locale=ctx.locale))
return
await guild.purge(self.bot.cache)
@@ -106,7 +116,11 @@ class CogConfig(Cog):
description_localizations=in_every_locale("description", "commands", "config_show"),
)
async def command_config_show(self, ctx: ApplicationContext) -> None:
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
try:
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
except (InvalidId, GuildNotFoundError):
await ctx.respond(self.bot._("unexpected_error", "messages", locale=ctx.locale))
return
if not guild.is_configured():
await ctx.respond(self.bot._("guild_unconfigured_admin", "messages", locale=ctx.locale))

View File

@@ -14,6 +14,7 @@ from discord.utils import basic_autocomplete
from libbot.i18n import _, in_every_locale
from classes import PycordEvent, PycordEventStage, PycordGuild
from classes.errors import GuildNotFoundError, EventNotFoundError
from classes.pycord_bot import PycordBot
from modules.utils import (
autocomplete_active_events,
@@ -80,7 +81,11 @@ class CogEvent(Cog):
end: str,
thumbnail: Attachment = None,
) -> None:
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
try:
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
except (InvalidId, GuildNotFoundError):
await ctx.respond(self.bot._("unexpected_error", "messages", locale=ctx.locale))
return
if not guild.is_configured():
await ctx.respond(self.bot._("guild_unconfigured_admin", "messages", locale=ctx.locale))
@@ -177,11 +182,15 @@ class CogEvent(Cog):
end: str = None,
thumbnail: Attachment = None,
) -> None:
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
try:
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
except (InvalidId, GuildNotFoundError):
await ctx.respond(self.bot._("unexpected_error", "messages", locale=ctx.locale))
return
try:
pycord_event: PycordEvent = await self.bot.find_event(event_id=event)
except (InvalidId, RuntimeError):
except (InvalidId, EventNotFoundError):
# TODO Make a nice message
await ctx.respond("Event was not found.")
return
@@ -269,11 +278,15 @@ class CogEvent(Cog):
if not (await is_operation_confirmed(ctx, confirm)):
return
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
try:
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
except (InvalidId, GuildNotFoundError):
await ctx.respond(self.bot._("unexpected_error", "messages", locale=ctx.locale))
return
try:
pycord_event: PycordEvent = await self.bot.find_event(event_id=event)
except (InvalidId, RuntimeError):
except (InvalidId, EventNotFoundError):
# TODO Make a nice message
await ctx.respond("Event was not found.")
return
@@ -317,11 +330,9 @@ class CogEvent(Cog):
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):
except (InvalidId, EventNotFoundError):
# TODO Make a nice message
await ctx.respond("Event was not found.")
return

View File

@@ -6,6 +6,7 @@ from discord import ApplicationContext, Cog, File, option, slash_command
from libbot.i18n import _, in_every_locale
from classes import PycordEvent, PycordEventStage, PycordGuild, PycordUser
from classes.errors import EventNotFoundError, EventStageNotFoundError, GuildNotFoundError
from classes.pycord_bot import PycordBot
@@ -26,7 +27,11 @@ class CogGuess(Cog):
description_localizations=in_every_locale("description", "commands", "guess", "options", "answer"),
)
async def command_guess(self, ctx: ApplicationContext, answer: str) -> None:
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
try:
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
except (InvalidId, GuildNotFoundError):
await ctx.respond(self.bot._("unexpected_error", "messages", locale=ctx.locale))
return
if not guild.is_configured():
await ctx.respond(self.bot._("guild_unconfigured", "messages", locale=ctx.locale))
@@ -35,30 +40,24 @@ class CogGuess(Cog):
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(self.bot._("jailed_error", "messages", locale=ctx.locale))
return
if user.current_event_id is None or user.current_stage_id is None:
# TODO Make a nice message
await ctx.respond(
"You have no ongoing events. You can register for events using the `/register` command."
)
await ctx.respond(self.bot._("guess_unregistered", "messages", locale=ctx.locale))
return
try:
event: PycordEvent = await self.bot.find_event(event_id=user.current_event_id)
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, contact the administrator.")
except (InvalidId, EventNotFoundError, EventStageNotFoundError):
await ctx.respond(self.bot._("guess_incorrect_event", "messages", locale=ctx.locale))
return
if ctx.channel_id != user.event_channels[str(event._id)]:
# TODO Make a nice message
await ctx.respond("Usage outside own event channel is not allowed.", ephemeral=True)
await ctx.respond(
self.bot._("guess_incorrect_channel", "messages", locale=ctx.locale), ephemeral=True
)
return
if answer.lower() != stage.answer.lower():
@@ -73,7 +72,6 @@ class CogGuess(Cog):
)
if next_stage_id is None:
# TODO Make a nice message
user.completed_event_ids.append(event._id)
await user._set(
@@ -83,12 +81,14 @@ class CogGuess(Cog):
completed_event_ids=user.completed_event_ids,
)
await ctx.respond("Congratulations! You have completed the event!")
await ctx.respond(self.bot._("guess_completed_event", "messages", locale=ctx.locale))
await self.bot.notify_admins(
ctx.guild,
guild,
f"User **{ctx.author.display_name}** ({ctx.author.mention}) has completed the event",
self.bot._("admin_user_completed_event", "messages", locale=ctx.locale).format(
display_name=ctx.author.display_name, mention=ctx.author.mention
),
)
return
@@ -108,7 +108,12 @@ class CogGuess(Cog):
await self.bot.notify_admins(
ctx.guild,
guild,
f"User **{ctx.author.display_name}** ({ctx.author.mention}) has completed the stage {stage.sequence+1} of the event **{event.name}**.",
self.bot._("admin_user_completed_stage", "messages", locale=ctx.locale).format(
display_name=ctx.author.display_name,
mention=ctx.author.mention,
stage_sequence=stage.sequence + 1,
event_name=event.name,
),
)

View File

@@ -10,6 +10,7 @@ from discord.utils import basic_autocomplete
from libbot.i18n import _, in_every_locale
from classes import PycordEvent, PycordEventStage, PycordGuild, PycordUser
from classes.errors import EventNotFoundError, GuildNotFoundError
from classes.pycord_bot import PycordBot
from modules.utils import autocomplete_active_events, get_logger, get_unix_timestamp
@@ -22,7 +23,6 @@ class CogRegister(Cog):
def __init__(self, bot: PycordBot):
self.bot: PycordBot = bot
# TODO Introduce i18n
@slash_command(
name="register",
description=_("description", "commands", "register"),
@@ -37,13 +37,16 @@ class CogRegister(Cog):
autocomplete=basic_autocomplete(autocomplete_active_events),
)
async def command_register(self, ctx: ApplicationContext, event: str) -> None:
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
try:
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
except (InvalidId, GuildNotFoundError):
await ctx.respond(self.bot._("unexpected_error", "messages", locale=ctx.locale))
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.")
except (InvalidId, EventNotFoundError):
await ctx.respond(self.bot._("event_not_found", "messages", locale=ctx.locale))
return
if not guild.is_configured():
@@ -53,25 +56,33 @@ class CogRegister(Cog):
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(self.bot._("jailed_error", "messages", locale=ctx.locale))
return
if pycord_event._id in user.registered_event_ids:
# TODO Make a nice message
await ctx.respond("You are already registered for this event.")
await ctx.respond(self.bot._("register_already_registered", "messages", locale=ctx.locale))
return
await user.event_register(pycord_event._id, cache=self.bot.cache)
# 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 <t:{get_unix_timestamp(pycord_event.starts, to_utc=True)}:R>. Good luck!"
event_ongoing: bool = pycord_event.starts.replace(tzinfo=ZoneInfo("UTC")) < datetime.now(
tz=ZoneInfo("UTC")
)
if pycord_event.starts.replace(tzinfo=ZoneInfo("UTC")) < datetime.now(tz=ZoneInfo("UTC")):
registered_message: str = (
self.bot._("register_success_ongoing", "messages", locale=ctx.locale).format(
event_name=pycord_event.name
)
if event_ongoing
else self.bot._("register_success_scheduled", "messages", locale=ctx.locale).format(
event_name=pycord_event.name,
event_starts=get_unix_timestamp(pycord_event.starts, to_utc=True),
)
)
await ctx.respond(registered_message)
if event_ongoing:
await user.set_event(pycord_event._id, cache=self.bot.cache)
user_channel: TextChannel = await user.setup_event_channel(
@@ -89,7 +100,11 @@ class CogRegister(Cog):
await self.bot.notify_admins(
ctx.guild,
guild,
f"Event channel could not be created for user **{ctx.author.display_name}** ({ctx.author.mention}) and event **{pycord_event.name}**.",
self.bot._("admin_user_channel_creation_failed", "messages", locale=ctx.locale).format(
display_name=ctx.author.display_name,
mention=ctx.author.mention,
event_name=pycord_event.name,
),
)
return
@@ -100,9 +115,10 @@ class CogRegister(Cog):
else File(Path(f"data/{pycord_event.thumbnail['id']}"), pycord_event.thumbnail["filename"])
)
# TODO Make a nice message
await user_channel.send(
f"Event **{pycord_event.name}** has already started!\n\nUse slash command `/guess` to suggest your answers to each event stage.",
self.bot._("register_already_started", "messages", locale=ctx.locale).format(
event_name=pycord_event.name
),
file=thumbnail,
)

View File

@@ -7,6 +7,7 @@ from discord.utils import basic_autocomplete
from libbot.i18n import _, in_every_locale
from classes import PycordEvent, PycordEventStage, PycordGuild
from classes.errors import GuildNotFoundError, EventStageNotFoundError, EventNotFoundError
from classes.pycord_bot import PycordBot
from modules.utils import (
autocomplete_active_events,
@@ -76,7 +77,11 @@ class CogStage(Cog):
answer: str,
media: Attachment = None,
) -> None:
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
try:
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
except (InvalidId, GuildNotFoundError):
await ctx.respond(self.bot._("unexpected_error", "messages", locale=ctx.locale))
return
if not guild.is_configured():
await ctx.respond(self.bot._("guild_unconfigured_admin", "messages", locale=ctx.locale))
@@ -84,7 +89,7 @@ class CogStage(Cog):
try:
pycord_event: PycordEvent = await self.bot.find_event(event_id=event)
except (InvalidId, RuntimeError):
except (InvalidId, EventStageNotFoundError):
# TODO Make a nice message
await ctx.respond("Event was not found.")
return
@@ -186,7 +191,11 @@ class CogStage(Cog):
media: Attachment = None,
remove_media: bool = False,
) -> None:
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
try:
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
except (InvalidId, GuildNotFoundError):
await ctx.respond(self.bot._("unexpected_error", "messages", locale=ctx.locale))
return
if not guild.is_configured():
await ctx.respond(self.bot._("guild_unconfigured_admin", "messages", locale=ctx.locale))
@@ -194,7 +203,7 @@ class CogStage(Cog):
try:
pycord_event: PycordEvent = await self.bot.find_event(event_id=event)
except (InvalidId, RuntimeError):
except (InvalidId, EventNotFoundError):
# TODO Make a nice message
await ctx.respond("Event was not found.")
return
@@ -204,7 +213,7 @@ class CogStage(Cog):
try:
event_stage: PycordEventStage = await self.bot.find_event_stage(stage)
except (InvalidId, RuntimeError):
except (InvalidId, EventStageNotFoundError):
# TODO Make a nice message
await ctx.respond("Event stage was not found.")
return
@@ -267,7 +276,11 @@ class CogStage(Cog):
if not (await is_operation_confirmed(ctx, confirm)):
return
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
try:
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
except (InvalidId, GuildNotFoundError):
await ctx.respond(self.bot._("unexpected_error", "messages", locale=ctx.locale))
return
if not guild.is_configured():
await ctx.respond(self.bot._("guild_unconfigured_admin", "messages", locale=ctx.locale))
@@ -275,7 +288,7 @@ class CogStage(Cog):
try:
pycord_event: PycordEvent = await self.bot.find_event(event_id=event)
except (InvalidId, RuntimeError):
except (InvalidId, EventNotFoundError):
# TODO Make a nice message
await ctx.respond("Event was not found.")
return
@@ -285,7 +298,7 @@ class CogStage(Cog):
try:
event_stage: PycordEventStage = await self.bot.find_event_stage(stage)
except (InvalidId, RuntimeError):
except (InvalidId, EventStageNotFoundError):
# TODO Make a nice message
await ctx.respond("Event stage was not found.")
return

View File

@@ -4,6 +4,7 @@ from discord.utils import basic_autocomplete
from libbot.i18n import _, in_every_locale
from classes import PycordEvent, PycordGuild, PycordUser
from classes.errors import EventNotFoundError, GuildNotFoundError
from classes.pycord_bot import PycordBot
from modules.utils import autocomplete_user_registered_events, is_operation_confirmed
@@ -39,13 +40,16 @@ class CogUnregister(Cog):
if not (await is_operation_confirmed(ctx, confirm)):
return
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
try:
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
except (InvalidId, GuildNotFoundError):
await ctx.respond(self.bot._("unexpected_error", "messages", locale=ctx.locale))
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.")
except (InvalidId, EventNotFoundError):
await ctx.respond(self.bot._("event_not_found", "messages", locale=ctx.locale))
return
if not guild.is_configured():
@@ -55,22 +59,18 @@ class CogUnregister(Cog):
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(self.bot._("jailed_error", "messages", locale=ctx.locale))
return
if pycord_event._id not in user.registered_event_ids:
# TODO Make a nice message
await ctx.respond("You are not registered for this event.")
await ctx.respond(self.bot._("unregister_not_registered", "messages", locale=ctx.locale))
return
await user.event_unregister(pycord_event._id, cache=self.bot.cache)
# TODO Text channel must be locked and updated
await ctx.respond("You are no longer registered for this event.")
await ctx.respond(self.bot._("unregister_unregistered", "messages", locale=ctx.locale))
def setup(bot: PycordBot) -> None:

View File

@@ -1,14 +1,30 @@
{
"messages": {
"operation_unconfirmed": "Operation not confirmed.",
"admin_user_channel_creation_failed": "Event channel could not be created for user **{display_name}** ({mention}) and event **{event_name}**.",
"admin_user_completed_event": "User **{display_name}** ({mention}) has completed the event",
"admin_user_completed_stage": "User **{display_name}** ({mention}) has completed the stage {stage_sequence} of the event **{event_name}**.",
"config_reset": "Configuration has been reset. You can update it using `/config set`, otherwise no events can be held.",
"config_set": "Configuration has been updated. You can review it anytime using `/config show`.",
"config_show": "**Guild config**\n\nChannel: <#{channel_id}>\nCategory: <#{category_id}>\nTimezone: `{timezone}`",
"event_not_found": "Event was not found.",
"guess_completed_event": "Congratulations! You have completed the event!",
"guess_incorrect_channel": "Usage outside own event channel is not allowed.",
"guess_incorrect_event": "Your event could not be found. Please, contact the administrator.",
"guess_unregistered": "You have no ongoing events. You can register for events using the `/register` command.",
"guild_unconfigured": "Guild is not configured. Please, report this to the administrator.",
"guild_unconfigured_admin": "Guild is not configured. Please, configure it using `/config set`.",
"timezone_invalid": "Timezone **{timezone}** was not found. Please, select one of the timezones provided by the autocompletion.",
"config_set": "Configuration has been updated. You can review it anytime using `/config show`.",
"config_reset": "Configuration has been reset. You can update it using `/config set`, otherwise no events can be held.",
"config_show": "**Guild config**\n\nChannel: <#{channel_id}>\nCategory: <#{category_id}>\nTimezone: `{timezone}`",
"jailed_error": "You are jailed and cannot interact with events. Please, contact the administrator.",
"operation_unconfirmed": "Operation not confirmed.",
"register_already_registered": "You are already registered for this event.",
"register_already_started": "Event **{event_name}** has already started!\n\nUse slash command `/guess` to suggest your answers to each event stage.",
"register_success_ongoing": "You are now registered for the event **{event_name}**.\n\nNew channel has been created for you and further instructions will are provided in it. Good luck!",
"register_success_scheduled": "You are now registered for the event **{event_name}**.\n\nNew channel will be created for you and further instructions will be provided as soon as the event starts <t:{event_starts}:R>. Good luck!",
"status": "**QuizBot** v{version}\n\nUptime: since <t:{start_time}>",
"status_git": "**QuizBot** v{version} (`{commit}`)\n\nUptime: up since <t:{start_time}>"
"status_git": "**QuizBot** v{version} (`{commit}`)\n\nUptime: up since <t:{start_time}>",
"timezone_invalid": "Timezone **{timezone}** was not found. Please, select one of the timezones provided by the autocompletion.",
"unexpected_error": "An unexpected error has occurred. Please, contact the administrator.",
"unregister_not_registered": "You are not registered for this event.",
"unregister_unregistered": "You are no longer registered for this event."
},
"commands": {
"config": {