async def punish_member( self, original_message: discord.Message, member: Member, internal_guild: Guild, user_message, guild_message, is_kick: bool, user_delete_after: int = None, channel_delete_after: int = None, ): # pragma: no cover guild = original_message.guild author = original_message.author # Check we have perms to punish perms = guild.me.guild_permissions if not perms.kick_members and is_kick: member._in_guild = True member.kick_count -= 1 raise MissingGuildPermissions( f"I need kick perms to punish someone in {guild.name}") elif not perms.ban_members and not is_kick: member._in_guild = True member.kick_count -= 1 raise MissingGuildPermissions( f"I need ban perms to punish someone in {guild.name}") # We also check they don't own the guild, since ya know... elif guild.owner_id == member.id: member._in_guild = True member.kick_count -= 1 # TODO Make this consistent across libs await self.send_guild_log( guild=internal_guild, message= f"I am failing to punish {original_message.author.display_name} because they own this guild.", delete_after_time=channel_delete_after, original_channel=original_message.channel, ) raise MissingGuildPermissions( f"I cannot punish {author.display_name}({author.id}) " f"because they own this guild. ({guild.name})") # Ensure we can actually punish the user, for this # we just check our top role is higher then them elif guild.me.top_role.position < author.top_role.position: log.warning( "I might not be able to punish %s(%s) in Guild: %s(%s) " "because they are higher then me, which means I could lack the ability to kick/ban them.", author.display_name, member.id, guild.name, guild.id, ) sent_message: Optional[discord.Message] = None try: if isinstance(user_message, discord.Embed): sent_message = await author.send( embed=user_message, delete_after=user_delete_after) else: sent_message = await author.send( user_message, delete_after=user_delete_after) except discord.HTTPException: await self.send_guild_log( guild=internal_guild, message= f"Sending a message to {author.mention} about their {'kick' if is_kick else 'ban'} failed.", delete_after_time=channel_delete_after, original_channel=original_message.channel, ) log.warning( f"Failed to message Member(id=%s) about {'kick' if is_kick else 'ban'}", author.id, ) # Even if we can't tell them they are being punished # We still need to punish them, so try that try: if is_kick: await guild.kick( member, reason="Automated punishment from DPY Anti-Spam.") log.info("Kicked Member(id=%s)", member.id) else: await guild.ban( member, reason="Automated punishment from DPY Anti-Spam.") log.info("Banned Member(id=%s)", member.id) except discord.Forbidden as e: # In theory we send the failed punishment method # here, although we check first so I think its fine # to remove it from this part raise e from None except discord.HTTPException: member._in_guild = True member.kick_count -= 1 await self.send_guild_log( guild=internal_guild, message= f"An error occurred trying to {'kick' if is_kick else 'ban'}: <@{member.id}>", delete_after_time=channel_delete_after, original_channel=original_message.channel, ) log.warning( "An error occurred trying to %s: Member(id=%s)", {"kick" if is_kick else "ban"}, member.id, ) if sent_message is not None: if is_kick: user_failed_message = self.transform_message( self.handler.options.member_failed_kick_message, original_message, member.warn_count, member.kick_count, ) else: user_failed_message = self.transform_message( self.handler.options.member_failed_ban_message, original_message, member.warn_count, member.kick_count, ) await self.send_guild_log( internal_guild, user_failed_message, channel_delete_after, original_message.channel, ) await sent_message.delete() else: await self.send_guild_log( guild=internal_guild, message=guild_message, delete_after_time=channel_delete_after, original_channel=original_message.channel, ) member._in_guild = True await self.handler.cache.set_member(member)
async def punish_member( self, original_message: messages.Message, member: Member, internal_guild: Guild, user_message, guild_message, is_kick: bool, user_delete_after: int = None, channel_delete_after: int = None, ): # pragma: no cover guild = self.handler.bot.cache.get_guild(original_message.guild_id) author = original_message.author channel = await self.handler.bot.rest.fetch_channel( original_message.channel_id) # Check we have perms to punish perms = await self._get_perms(guild.get_my_member()) if not perms.KICK_MEMBERS and is_kick: member._in_guild = True member.kick_count -= 1 raise MissingGuildPermissions( f"I need kick perms to punish someone in {guild.name}") elif not perms.BAN_MEMBERS and not is_kick: member._in_guild = True member.kick_count -= 1 raise MissingGuildPermissions( f"I need ban perms to punish someone in {guild.name}") # We also check they don't own the guild, since ya know... elif guild.owner_id == member.id: member._in_guild = True member.kick_count -= 1 raise MissingGuildPermissions( f"I cannot punish {author.username}({author.id}) " f"because they own this guild. ({guild.name})") sent_message: Optional[messages.Message] = None try: if isinstance(user_message, embeds.Embed): sent_message = await author.send(embed=user_message) else: sent_message = await author.send(user_message) if user_delete_after: await asyncio.sleep(user_delete_after) await sent_message.delete() except InternalServerError: await self.send_guild_log( guild=internal_guild, message= f"Sending a message to {author.mention} about their {'kick' if is_kick else 'ban'} failed.", delete_after_time=channel_delete_after, original_channel=channel, ) log.warning( f"Failed to message Member(id=%s) about {'kick' if is_kick else 'ban'}", author.id, ) # Even if we can't tell them they are being punished # We still need to punish them, so try that try: if is_kick: await guild.kick( author, reason="Automated punishment from DPY Anti-Spam.") log.info("Kicked Member(id=%s)", member.id) else: await guild.ban( author, reason="Automated punishment from DPY Anti-Spam.") log.info("Banned Member(id=%s)", member.id) except InternalServerError: member._in_guild = True member.kick_count -= 1 await self.send_guild_log( guild=internal_guild, message= f"An error occurred trying to {'kick' if is_kick else 'ban'}: <@{member.id}>", delete_after_time=channel_delete_after, original_channel=channel, ) log.warning( "An error occurred trying to %s: Member(id=%s)", {"kick" if is_kick else "ban"}, member.id, ) if sent_message is not None: if is_kick: user_failed_message = await self.transform_message( self.handler.options.member_failed_kick_message, original_message, member.warn_count, member.kick_count, ) else: user_failed_message = await self.transform_message( self.handler.options.member_failed_ban_message, original_message, member.warn_count, member.kick_count, ) await self.send_guild_log(internal_guild, user_failed_message, channel_delete_after, channel) await sent_message.delete() else: await self.send_guild_log( guild=internal_guild, message=guild_message, delete_after_time=channel_delete_after, original_channel=channel, ) member._in_guild = True await self.handler.cache.set_member(member)
async def propagate_user(self, original_message, guild: Guild) -> CorePayload: """ The internal representation of core functionality. Please see and use :meth:`discord.ext.antispam.AntiSpamHandler.propagate` """ try: if original_message.author.id in guild.members: member = guild.members[original_message.author.id] else: member: Member = await self.cache.get_member( member_id=original_message.author.id, guild_id=await self.handler.lib_handler.get_guild_id( original_message ), ) if not member._in_guild: return CorePayload( member_status="Bypassing message check since the member isn't seen to be in a guild" ) except MemberNotFound: # Create a use-able member member = Member( id=original_message.author.id, guild_id=await self.handler.lib_handler.get_guild_id(original_message), ) guild.members[member.id] = member await self.cache.set_guild(guild=guild) await self.clean_up( member=member, current_time=get_aware_time(), channel_id=await self.handler.lib_handler.get_channel_id(original_message), ) message: Message = await self.handler.lib_handler.create_message( original_message ) self._calculate_ratios(message, member) # Check again since in theory the above could take awhile if not member._in_guild: return CorePayload( member_status="Bypassing message check since the member isn't seen to be in a guild" ) member.messages.append(message) log.info( "Created Message(%s) on Member(id=%s) in Guild(id=%s)", message.id, member.id, member.guild_id, ) if ( self._get_duplicate_count(member, channel_id=message.channel_id) < self.options.message_duplicate_count ): return CorePayload() # We need to punish the member with something log.debug( "Message(%s) on Member(id=%s) in Guild(id=%s) requires some form of punishment", message.id, member.id, member.guild_id, ) # We need to punish the member with something return_payload = CorePayload(member_should_be_punished_this_message=True) if self.options.no_punish: # User will handle punishments themselves return CorePayload( member_should_be_punished_this_message=True, member_status="Member should be punished, however, was not due to no_punish being True", ) if ( self.options.warn_only or self._get_duplicate_count(member, message.channel_id) >= self.options.warn_threshold and member.warn_count < self.options.kick_threshold and member.kick_count < self.options.ban_threshold ): """ WARN The member has yet to reach the warn threshold, after the warn threshold is reached this will then become a kick and so on """ log.debug( "Attempting to warn Member(id=%s) in Guild(id=%s)", message.author_id, message.guild_id, ) member.warn_count += 1 channel = await self.handler.lib_handler.get_channel_from_message( original_message ) member_message = await self.handler.lib_handler.transform_message( self.options.member_warn_message, original_message, member.warn_count, member.kick_count, ) guild_message = await self.handler.lib_handler.transform_message( self.options.guild_log_warn_message, original_message, member.warn_count, member.kick_count, ) try: await self.handler.lib_handler.send_message_to_( channel, member_message, original_message.author.mention, self.options.member_warn_message_delete_after, ) except Exception as e: member.warn_count -= 1 raise e # Log this within guild log channels await self.handler.lib_handler.send_guild_log( guild=guild, message=guild_message, original_channel=await self.handler.lib_handler.get_channel_from_message( original_message ), delete_after_time=self.options.guild_log_warn_message_delete_after, ) return_payload.member_was_warned = True return_payload.member_status = "Member was warned" elif ( member.warn_count >= self.options.kick_threshold and member.kick_count < self.options.ban_threshold ): # KICK # Set this to False here to stop processing other messages, we can revert on failure member._in_guild = False member.kick_count += 1 log.debug( "Attempting to kick Member(id=%s) from Guild(id=%s)", message.author_id, message.guild_id, ) guild_message = await self.handler.lib_handler.transform_message( self.options.guild_log_kick_message, original_message, member.warn_count, member.kick_count, ) user_message = await self.handler.lib_handler.transform_message( self.options.member_kick_message, original_message, member.warn_count, member.kick_count, ) await self.handler.lib_handler.punish_member( original_message, member, guild, user_message, guild_message, True, self.options.member_kick_message_delete_after, self.options.guild_log_kick_message_delete_after, ) return_payload.member_was_kicked = True return_payload.member_status = "Member was kicked" elif member.kick_count >= self.options.ban_threshold: # BAN # Set this to False here to stop processing other messages, we can revert on failure member._in_guild = False member.kick_count += 1 log.debug( "Attempting to ban Member(id=%s) from Guild(id=%s)", message.author_id, message.guild_id, ) guild_message = await self.handler.lib_handler.transform_message( self.options.guild_log_ban_message, original_message, member.warn_count, member.kick_count, ) user_message = await self.handler.lib_handler.transform_message( self.options.member_ban_message, original_message, member.warn_count, member.kick_count, ) await self.handler.lib_handler.punish_member( original_message, member, guild, user_message, guild_message, False, self.options.member_ban_message_delete_after, self.options.guild_log_ban_message_delete_after, ) return_payload.member_was_banned = True return_payload.member_status = "Member was banned" else: # Supports backwards compat? # Not sure if this is required tbh raise LogicError # Store the updated values await self.cache.set_member(member) # Delete the message if wanted if self.options.delete_spam is True and self.options.no_punish is False: await self.handler.lib_handler.delete_message(original_message) await self.handler.lib_handler.delete_member_messages(member) # Finish payload and return return_payload.member_warn_count = member.warn_count return_payload.member_kick_count = member.kick_count return_payload.member_duplicate_count = ( self._get_duplicate_count( member=member, channel_id=message.channel_id, ) - 1 ) return return_payload
async def punish_member( self, original_message: objects.UserMessage, member: Member, internal_guild: Guild, user_message, guild_message, is_kick: bool, user_delete_after: int = None, channel_delete_after: int = None, ): guild: objects.Guild = await objects.Guild.from_id( self.bot, original_message.guild_id ) author = original_message.author guild_me: objects.GuildMember = await objects.GuildMember.from_id( self.bot, original_message.guild_id, self.bot.bot.id ) guild_member: objects.GuildMember = await objects.GuildMember.from_id( self.bot, original_message.guild_id, original_message.author.id ) async def get_hoisted(member: objects.GuildMember) -> int: if not member.roles: # TODO return guild's default role pass roles = list(map(int, member.roles)) return max(roles) my_top_role: objects.Role = await objects.Role.from_id( self.bot, original_message.guild_id, await get_hoisted(guild_me) ) author_top_role: objects.Role = await objects.Role.from_id( self.bot, original_message.guild_id, await get_hoisted(guild_member) ) my_top_role_pos: int = my_top_role.position author_top_role_pos: int = author_top_role.position # Check we have perms to punish perms: int = await self.get_guild_member_perms( original_message.guild_id, self.bot.bot.id ) kick_members = bool(perms << 1) ban_members = bool(perms << 2) if not kick_members and is_kick: member._in_guild = True member.kick_count -= 1 raise MissingGuildPermissions( f"I need kick perms to punish someone in {guild.name}" ) elif not ban_members and not is_kick: member._in_guild = True member.kick_count -= 1 raise MissingGuildPermissions( f"I need ban perms to punish someone in {guild.name}" ) # We also check they don't own the guild, since ya know... elif guild.owner_id == member.id: member._in_guild = True member.kick_count -= 1 raise MissingGuildPermissions( f"I cannot punish {author.username}({author.id}) " f"because they own this guild. ({guild.name})" ) elif my_top_role_pos < author_top_role_pos: log.warning( "I might not be able to punish %s(%s) in Guild: %s(%s) " "because they are higher then me, which means I could lack the ability to kick/ban them.", author.username, member.id, guild.name, guild.id, ) sent_message: Optional[objects.UserMessage] = None try: await self.send_message_to_( target=author, message=user_message, delete_after_time=user_delete_after, mention=original_message.author.mention, ) except pincer.exceptions.PincerError: channel: objects.Channel = await objects.Channel.from_id( self.bot, original_message.channel_id ) await self.send_guild_log( guild=internal_guild, message=f"Sending a message to {author.mention} about their {'kick' if is_kick else 'ban'} failed.", delete_after_time=channel_delete_after, original_channel=channel, ) log.warning( f"Failed to message Member(id=%s) about {'kick' if is_kick else 'ban'}", author.id, ) # Even if we can't tell them they are being punished # We still need to punish them, so try that try: if is_kick: await guild.kick( member.id, reason="Automated punishment from DPY Anti-Spam." ) log.info("Kicked Member(id=%s)", member.id) else: await guild.ban( member.id, reason="Automated punishment from DPY Anti-Spam." ) log.info("Banned Member(id=%s)", member.id) except pincer.exceptions.ForbiddenError as e: # In theory we send the failed punishment method # here, although we check first so I think its fine # to remove it from this part raise e from None except pincer.exceptions.PincerError: channel: objects.Channel = await self.bot.get_channel( original_message.channel_id ) member._in_guild = True member.kick_count -= 1 await self.send_guild_log( guild=internal_guild, message=f"An error occurred trying to {'kick' if is_kick else 'ban'}: <@{member.id}>", delete_after_time=channel_delete_after, original_channel=channel, ) log.warning( "An error occurred trying to %s: Member(id=%s)", {"kick" if is_kick else "ban"}, member.id, ) if sent_message is not None: if is_kick: user_failed_message = await self.transform_message( self.handler.options.member_failed_kick_message, original_message, member.warn_count, member.kick_count, ) else: user_failed_message = await self.transform_message( self.handler.options.member_failed_ban_message, original_message, member.warn_count, member.kick_count, ) await self.send_guild_log( internal_guild, user_failed_message, channel_delete_after, channel, ) await sent_message.delete() else: channel: objects.Channel = await self.bot.get_channel( original_message.channel_id ) await self.send_guild_log( guild=internal_guild, message=guild_message, delete_after_time=channel_delete_after, original_channel=channel, ) member._in_guild = True await self.handler.cache.set_member(member)