Exemple #1
0
    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)
Exemple #2
0
    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)
Exemple #3
0
    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
Exemple #4
0
    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)