import logging from typing import Union from discord import Cog, Member, TextChannel, Thread from discord.abc import GuildChannel from classes.errors import ( CheckAlreadyAssignedError, CheckNotFoundError, GuildChallengesEmptyError, GuildNotFoundError, ) from classes.pycord_bot import PycordBot from classes.pycord_challenge import PycordChallenge from classes.pycord_check import PycordCheck from classes.pycord_guild import PycordGuild from classes.pycord_member import PycordMember logger = logging.getLogger(__name__) class CogMemberJoin(Cog): def __init__(self, client: PycordBot) -> None: super().__init__() self.client: PycordBot = client @Cog.listener() async def on_member_join(self, member: Member): """Process the event of new or returning member joining the guild. This handler must take care of the new users, returning verified as well as returning banned users. Guild must exist and be set up correctly in order to use this handler and process the input. ### Args: * member (`Member`): Member that has joined the guild """ # Check whether the guild exists in the database # We should not create it here when it does not exist. Config will be empty try: pycord_guild: PycordGuild = await self.client.find_guild(member.guild.id) except GuildNotFoundError: # Let's notify guild admins about this logger.error( "Guild %s is not set up properly: guild is missing from the database.", member.guild.id, ) return # Get the member and create if it does not exist pycord_member: PycordMember = await self.client.find_member( member.id, pycord_guild._id, create=True ) # Check whether guild is set up properly if not pycord_guild.is_valid() or pycord_guild.channels.captcha is None: # Let's notify guild admins about this logger.error( "Guild %s is not set up properly: check %s in database for details.", member.guild.id, pycord_guild._id, ) return # Check whether member already has an active check try: await pycord_member.get_check() # Let's notify guild admins and user about this logger.error( "Member %s of guild %s could not get a check: user already has an active check.", member.id, member.guild.id, ) return except CheckNotFoundError: logger.info( "Member %s of guild %s does bot have a check yet.", member.id, member.guild.id, ) captcha_channel: Union[GuildChannel, None] = member.guild.get_channel( pycord_guild.channels.captcha ) # Check whether the channel exists and is a text channel if captcha_channel is None or not isinstance(captcha_channel, TextChannel): logger.error( "Captcha channel of the guild %s either does not exist or is incorrect." ) return # Try creating a thread try: captcha_thread: Thread = await captcha_channel.create_thread( name=f"Verification - @{member.name}", invitable=False, reason=f"Verification - @{member.name} ({member.id})", ) except Exception as exc: logger.error( "Could not create captcha thread for the channel %s in guild %s (%s) due to %s", captcha_channel.id, pycord_guild.id, pycord_guild._id, exc, ) return try: check: PycordCheck = await pycord_member.new_check(captcha_thread.id) challenge: PycordChallenge = await check.get_challenge() except GuildChallengesEmptyError: logger.error( "Guild %s (%s) has no active challenges, check is aborted.", pycord_guild.id, pycord_guild._id, ) await captcha_thread.delete() return except CheckAlreadyAssignedError: logger.error( "Member %s of the guild %s (%s) already has an active check, check is aborted.", member.id, pycord_guild.id, pycord_guild._id, ) await captcha_thread.delete() return try: await captcha_thread.send( f"Welcome, {member.mention}!\n\nTo keep this Discord server safe, we need to verify that you are a human.\n\nPlease, complete the sentence below:\n`{challenge.challenge} ...`\n\nUse the command `/verify` and the verification answer as its argument to pass the check." ) except Exception as exc: logger.error( "Could not send the challenge to the thread %s in guild %s (%s) due to %s", captcha_thread.id, pycord_guild.id, pycord_guild._id, exc, ) return try: await captcha_channel.send( f"Welcome, {member.mention}! Please proceed to {captcha_thread.mention} in order to complete the verification.", delete_after=180, ) except Exception as exc: logger.error( "Could not send a notification about the challenge to the channel %s in guild %s (%s) due to %s", captcha_channel.id, pycord_guild.id, pycord_guild._id, exc, ) return def setup(client: PycordBot): client.add_cog(CogMemberJoin(client))