from datetime import datetime from logging import Logger from pathlib import Path from typing import Any, Dict, List from zoneinfo import ZoneInfo from bson import ObjectId from bson.errors import InvalidId from discord import ( ApplicationContext, File, SlashCommandGroup, TextChannel, User, option, ) from discord.ext.commands import Cog from libbot.i18n import _, in_every_locale from classes import PycordEvent, PycordGuild, PycordUser from classes.errors import GuildNotFoundError from classes.pycord_bot import PycordBot from modules.database import col_users from modules.utils import get_logger, is_operation_confirmed, get_utc_now logger: Logger = get_logger(__name__) class CogUser(Cog): """Cog with user management commands.""" def __init__(self, bot: PycordBot): self.bot: PycordBot = bot command_group: SlashCommandGroup = SlashCommandGroup( "user", description=_("description", "commands", "user"), description_localizations=in_every_locale("description", "commands", "user"), ) @command_group.command( name="update_channels", description=_("description", "commands", "user_update_channels"), description_localizations=in_every_locale("description", "commands", "user_update_channels"), ) @option( "user", description=_("description", "commands", "user_update_channels", "options", "user"), description_localizations=in_every_locale( "description", "commands", "user_update_channels", "options", "user" ), ) async def command_user_update_channels(self, ctx: ApplicationContext, user: User) -> None: 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), ephemeral=True) return pycord_user: PycordUser = await self.bot.find_user(user.id, ctx.guild.id) events: List[PycordEvent] = [] utc_now: datetime = get_utc_now() pipeline: List[Dict[str, Any]] = [ {"$match": {"id": pycord_user.id}}, { "$lookup": { "from": "events", "let": {"event_ids": "$registered_event_ids"}, "pipeline": [ { "$match": { "$expr": { "$and": [ {"$in": ["$_id", "$$event_ids"]}, {"$eq": ["$ended", None]}, {"$gt": ["$ends", utc_now]}, {"$lt": ["$starts", utc_now]}, {"$eq": ["$is_cancelled", False]}, ] } } } ], "as": "registered_events", } }, {"$match": {"registered_events.0": {"$exists": True}}}, ] async with await col_users.aggregate(pipeline) as cursor: async for result in cursor: for registered_event in result["registered_events"]: events.append(PycordEvent(**registered_event)) for event in events: if pycord_user.current_event_id is not None and pycord_user.current_event_id != event._id: continue if pycord_user.current_event_id is None: await pycord_user.set_event(event._id, cache=self.bot.cache) channel: TextChannel | None = await pycord_user.fix_event_channel( self.bot, ctx.guild, guild, event, cache=self.bot.cache ) try: await self.bot.notify_admins( ctx.guild, guild, self.bot._("admin_user_channel_fixed", "messages", locale=ctx.locale).format( display_name=user.display_name, mention=user.mention, event_name=event.name ), ) except Exception as exc: logger.error( "Could not notify admins that user %s got their event channel for %s fixed due to: %s", user.id, event._id, exc, exc_info=exc, ) if channel is None: continue thumbnail: File | None = ( None if event.thumbnail is None else File(Path(f"data/{event.thumbnail['id']}"), event.thumbnail["filename"]) ) await channel.send( self.bot._("notice_event_already_started", "messages").format(event_name=event.name), file=thumbnail, ) stage_id: ObjectId = ( event.stage_ids[0] if pycord_user.current_stage_id is None else pycord_user.current_stage_id ) await pycord_user.set_event_stage(stage_id, cache=self.bot.cache) await self.bot.send_stage_question(channel, event, await self.bot.find_event_stage(stage_id)) await ctx.respond( self.bot._("user_channels_updated", "messages", locale=ctx.locale).format( display_name=user.display_name ) ) # TODO Implement the command # @command_group.command( # name="create_channel", # description="Create channel for the user", # ) # @option( # "user", # description="Selected user", # ) # async def command_user_create_channel(self, ctx: ApplicationContext, user: User) -> None: # await ctx.respond("Not implemented.") # TODO Implement the command # @command_group.command( # name="delete_channel", # description="Delete user's channel", # ) # @option( # "user", # 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: # await ctx.respond("Not implemented.") @command_group.command( name="jail", description=_("description", "commands", "user_jail"), description_localizations=in_every_locale("description", "commands", "user_jail"), ) @option( "user", description=_("description", "commands", "user_jail", "options", "user"), description_localizations=in_every_locale( "description", "commands", "user_jail", "options", "user" ), ) @option( "confirm", description=_("description", "commands", "user_jail", "options", "confirm"), description_localizations=in_every_locale( "description", "commands", "user_jail", "options", "confirm" ), required=False, ) async def command_user_jail(self, ctx: ApplicationContext, user: User, confirm: bool = False) -> None: 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: await ctx.respond( self.bot._("user_jail_already_jailed", "messages", locale=ctx.locale).format( display_name=user.display_name ), ephemeral=True, ) return await pycord_user.jail(self.bot.cache) await ctx.respond( self.bot._("user_jail_successful", "messages", locale=ctx.locale).format( display_name=user.display_name ) ) @command_group.command( name="unjail", description=_("description", "commands", "user_unjail"), description_localizations=in_every_locale("description", "commands", "user_unjail"), ) @option( "user", description=_("description", "commands", "user_unjail", "options", "user"), description_localizations=in_every_locale( "description", "commands", "user_unjail", "options", "user" ), ) @option( "confirm", description=_("description", "commands", "user_unjail", "options", "confirm"), description_localizations=in_every_locale( "description", "commands", "user_unjail", "options", "confirm" ), required=False, ) async def command_user_unjail(self, ctx: ApplicationContext, user: User, confirm: bool = False) -> None: 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: await ctx.respond( self.bot._("user_unjail_not_jailed", "messages", locale=ctx.locale).format( display_name=user.display_name ), ephemeral=True, ) return await pycord_user.unjail(self.bot.cache) await ctx.respond( self.bot._("user_unjail_successful", "messages", locale=ctx.locale).format( display_name=user.display_name ) ) def setup(bot: PycordBot) -> None: bot.add_cog(CogUser(bot))