Exemple #1
0
    def load_from_dict(bot, user_data):
        """
        Loads a new user obj from a dict

        Parameters
        ----------
        bot : commands.Bot
            The bot
        user_data : dict
            The data to load state from

        Returns
        -------
        User

        """
        user = User(
            bot=bot,
            id=user_data["id"],
            guild_id=user_data["guild_id"],
            options=deepcopy(user_data["options"]),
        )
        user.in_guild = user_data["is_in_guild"]
        user.warn_count = user_data["warn_count"]
        user.kick_count = user_data["kick_count"]
        user.duplicate_counter = user_data["duplicate_count"]
        user.duplicate_channel_counter_dict = deepcopy(
            user_data["duplicate_channel_counter_dict"]
        )

        for message_data in user_data["messages"]:
            # Do this to save overhead in the message object
            # and keep it as small as possible
            message = Message(
                id=message_data["id"],
                content=message_data["content"],
                guild_id=message_data["guild_id"],
                author_id=message_data["author_id"],
                channel_id=message_data["channel_id"],
            )
            message.is_duplicate = message_data["is_duplicate"]
            message._creation_time = datetime.datetime.strptime(
                message_data["creation_time"], "%f:%S:%M:%H:%d:%Y"
            )

            user.messages = message
            log.debug(f"Created Message ({message.id}) from saved state")

        log.debug(f"Created User ({user.id}) from saved state")

        return user
Exemple #2
0
    async def propagate(self, value: discord.Message):
        """
        This method handles a message object and then adds it to
        the relevant member

        Parameters
        ==========
        value : discord.Message
            The message that needs to be propagated out
        """
        if not isinstance(value, discord.Message) and not isinstance(
                value, MagicMock):
            raise ValueError(
                "Expected message of ignore_type: discord.Message")

        # Setup our return values for the end user to use
        return_data = {
            "was_punished_this_message": False,
            "was_warned": False,
            "was_kicked": False,
            "was_banned": False,
            "status": "Unknown",
        }

        # Here we just check if the user is still in the guild by checking if the in_guild attribute is False.
        # Because if its False then we don't need to process the message.
        if not self.in_guild:
            return

        self.clean_up(datetime.datetime.now(datetime.timezone.utc))

        # No point saving empty messages, although discord shouldn't allow them anyway
        if not bool(value.content and value.content.strip()):
            if not value.embeds:
                return

            embed = value.embeds[0]
            if not isinstance(embed, discord.Embed):
                return

            if embed.type.lower() != "rich":
                return

            content = embed_to_string(embed)
        else:
            content = value.clean_content

        message = Message(
            value.id,
            content,
            value.author.id,
            value.channel.id,
            value.guild.id,
        )

        for message_obj in self.messages:
            # This calculates the relation to each other
            if message == message_obj:
                raise DuplicateObject

            elif (fuzz.token_sort_ratio(message.content, message_obj.content)
                  >= self.options["message_duplicate_accuracy"]):
                """
                The handler works off an internal message duplicate counter 
                so just increment that and then let our logic process it
                """
                self.duplicate_counter += 1
                message.is_duplicate = True

                if self.duplicate_counter >= self.options[
                        "message_duplicate_count"]:
                    break

        # We check this again, because theoretically the above can take awhile to process etc
        if not self.in_guild:
            return

        self.messages = message
        logging.info(f"Created Message: {message.id}")

        if self.duplicate_counter >= self.options["message_duplicate_count"]:
            logging.debug(
                f"Message: ({message.id}) requires some form of punishment")
            # We need to punish the member with something
            return_data["was_punished_this_message"] = True
            punish_bypass = False

            if self.options["warn_only"]:
                punish_bypass = True

            if (self.duplicate_counter >= self.options["warn_threshold"]
                    and self.warn_count < self.options["kick_threshold"]
                    and self.kick_count < self.options["ban_threshold"]
                    or punish_bypass):
                logging.debug(f"Attempting to warn: {message.author_id}")
                """
                The member has yet to reach the warn threshold,
                after the warn threshold is reached this will
                then become a kick and so on
                """
                # We are still in the warning area
                self.warn_count += 1
                channel = value.channel
                guild_message = transform_message(
                    self.options["guild_warn_message"],
                    value,
                    {
                        "warn_count": self.warn_count,
                        "kick_count": self.kick_count
                    },
                )
                try:
                    await send_to_obj(channel, guild_message)
                except Exception as e:
                    self.warn_count -= 1
                    raise e

                return_data["was_warned"] = True
                return_data["status"] = "User was warned."

            elif (self.warn_count >= self.options["kick_threshold"]
                  and self.kick_count < self.options["ban_threshold"]):
                # Set this to False here to stop processing other messages, we can revert on failure
                self.in_guild = False
                self.kick_count += 1

                logging.debug(f"Attempting to kick: {message.author_id}")
                # We should kick the member
                guild_message = transform_message(
                    self.options["guild_kick_message"],
                    value,
                    {
                        "warn_count": self.warn_count,
                        "kick_count": self.kick_count
                    },
                )
                user_message = transform_message(
                    self.options["user_kick_message"],
                    value,
                    {
                        "warn_count": self.warn_count,
                        "kick_count": self.kick_count
                    },
                )
                await self._punish_user(
                    value,
                    user_message,
                    guild_message,
                    Static.KICK,
                )
                return_data["was_kicked"] = True
                return_data["status"] = "User was kicked."

            elif self.kick_count >= self.options["ban_threshold"]:
                # Set this to False here to stop processing other messages, we can revert on failure
                self.in_guild = False
                self.kick_count += 1

                logging.debug(f"Attempting to ban: {message.author_id}")
                # We should ban the member
                guild_message = transform_message(
                    self.options["guild_ban_message"],
                    value,
                    {
                        "warn_count": self.warn_count,
                        "kick_count": self.kick_count
                    },
                )
                user_message = transform_message(
                    self.options["user_ban_message"],
                    value,
                    {
                        "warn_count": self.warn_count,
                        "kick_count": self.kick_count
                    },
                )
                await self._punish_user(
                    value,
                    user_message,
                    guild_message,
                    Static.BAN,
                )

                return_data["was_banned"] = True
                return_data["status"] = "User was banned."

            else:
                raise LogicError

        return_data["warn_count"] = self.warn_count
        return_data["kick_count"] = self.kick_count
        return_data["duplicate_counter"] = self.get_correct_duplicate_count()
        return return_data
Exemple #3
0
    async def propagate(self, value: discord.Message):
        """
        This method handles a message object and then adds it to
        the relevant member

        Parameters
        ==========
        value : discord.Message
            The message that needs to be propagated out

        Warnings
        ========
        Calling this method yourself will bypass all checks
        """
        if not isinstance(value, (discord.Message, AsyncMock)):
            raise ValueError("Expected message of ignore_type: discord.Message")

        # Setup our return values for the end user to use
        return_data = {
            "should_be_punished_this_message": False,
            "was_warned": False,
            "was_kicked": False,
            "was_banned": False,
            "status": "Unknown",
        }

        # Here we just check if the user is still in the guild by checking if the in_guild attribute is False.
        # Because if its False then we don't need to process the message.
        if not self.in_guild:
            return {"status": "Bypassing message check since the user isnt in a guild"}

        self.clean_up(datetime.datetime.now(datetime.timezone.utc))

        # No point saving empty messages, although discord shouldn't allow them anyway
        if not bool(value.content and value.content.strip()):
            if not value.embeds:
                return {"status": "No point saving empty messages"}

            embed = value.embeds[0]
            if not isinstance(embed, discord.Embed):
                return {"status": "No point saving empty messages"}

            if embed.type.lower() != "rich":
                return {"status": "No point saving empty messages"}

            content = embed_to_string(embed)
        else:
            content = value.clean_content

        message = Message(
            value.id,
            content,
            value.author.id,
            value.channel.id,
            value.guild.id,
        )

        for message_obj in self.messages:
            # This calculates the relation to each other
            if message == message_obj:
                raise DuplicateObject

            elif (
                self.options.get("per_channel_spam")
                and message.channel_id != message_obj.channel_id
            ):
                # This user's spam should only be counted per channel
                # and these messages are in different channel
                continue

            elif (
                fuzz.token_sort_ratio(message.content, message_obj.content)
                >= self.options["message_duplicate_accuracy"]
            ):
                """
                The handler works off an internal message duplicate counter
                so just increment that and then let our logic process it
                """
                self._increment_duplicate_count(message)
                message.is_duplicate = True

                if (
                    self._get_duplicate_count(message)
                    >= self.options["message_duplicate_count"]
                ):
                    break

        # We check this again, because theoretically the above can take awhile to process etc
        if not self.in_guild:
            return {"status": "Bypassing message check since the user isnt in a guild"}

        self.messages = message
        log.info(f"Created Message: {message.id}")

        if (
            self._get_duplicate_count(message)
            >= self.options["message_duplicate_count"]
        ):
            log.debug(f"Message: ({message.id}) requires some form of punishment")
            # We need to punish the member with something
            return_data["should_be_punished_this_message"] = True
            only_warn = False

            if (
                self.options.get("delete_spam") is True
                and self.options.get("no_punish") is False
            ):
                try:
                    await value.delete()
                    log.debug(f"Deleted message: {value.id}")
                except discord.HTTPException:
                    # Failed to delete message
                    log.warning(
                        f"Failed to delete message {value.id} in guild {value.guild.id}"
                    )

            if self.options["warn_only"]:
                only_warn = True

            if self.options["no_punish"]:
                # no_punish, just return saying they should be punished
                return_data[
                    "status"
                ] = "User should be punished, however, was not due to no_punish being True"

            elif (
                self._get_duplicate_count(message) >= self.options["warn_threshold"]
                and self.warn_count < self.options["kick_threshold"]
                and self.kick_count < self.options["ban_threshold"]
                or only_warn
            ):
                log.debug(f"Attempting to warn: {message.author_id}")
                """
                The member has yet to reach the warn threshold,
                after the warn threshold is reached this will
                then become a kick and so on
                """
                # We are still in the warning area
                self.warn_count += 1
                channel = value.channel
                guild_message = transform_message(
                    self.options["guild_warn_message"],
                    value,
                    {"warn_count": self.warn_count, "kick_count": self.kick_count},
                )
                try:
                    if isinstance(guild_message, discord.Embed):
                        await channel.send(
                            embed=guild_message,
                            delete_after=self.options.get(
                                "guild_warn_message_delete_after"
                            ),
                        )
                    else:
                        await channel.send(
                            guild_message,
                            delete_after=self.options.get(
                                "guild_warn_message_delete_after"
                            ),
                        )
                except Exception as e:
                    self.warn_count -= 1
                    raise e

                return_data["was_warned"] = True
                return_data["status"] = "User was warned."

            elif (
                self.warn_count >= self.options["kick_threshold"]
                and self.kick_count < self.options["ban_threshold"]
            ):
                # Set this to False here to stop processing other messages, we can revert on failure
                self.in_guild = False
                self.kick_count += 1

                log.debug(f"Attempting to kick: {message.author_id}")
                # We should kick the member
                guild_message = transform_message(
                    self.options["guild_kick_message"],
                    value,
                    {"warn_count": self.warn_count, "kick_count": self.kick_count},
                )
                user_message = transform_message(
                    self.options["user_kick_message"],
                    value,
                    {"warn_count": self.warn_count, "kick_count": self.kick_count},
                )
                await self._punish_user(
                    value,
                    user_message,
                    guild_message,
                    Static.KICK,
                    self.options.get("user_kick_message_delete_after"),
                    self.options.get("guild_kick_message_delete_after"),
                )
                return_data["was_kicked"] = True
                return_data["status"] = "User was kicked."

            elif self.kick_count >= self.options["ban_threshold"]:
                # Set this to False here to stop processing other messages, we can revert on failure
                self.in_guild = False
                self.kick_count += 1

                log.debug(f"Attempting to ban: {message.author_id}")
                # We should ban the member
                guild_message = transform_message(
                    self.options["guild_ban_message"],
                    value,
                    {"warn_count": self.warn_count, "kick_count": self.kick_count},
                )
                user_message = transform_message(
                    self.options["user_ban_message"],
                    value,
                    {"warn_count": self.warn_count, "kick_count": self.kick_count},
                )
                await self._punish_user(
                    value,
                    user_message,
                    guild_message,
                    Static.BAN,
                    self.options.get("user_ban_message_delete_after"),
                    self.options.get("guild_ban_message_delete_after"),
                )

                return_data["was_banned"] = True
                return_data["status"] = "User was banned."

            else:
                raise LogicError

        return_data["warn_count"] = self.warn_count
        return_data["kick_count"] = self.kick_count
        return_data["duplicate_counter"] = self.get_correct_duplicate_count(
            message.channel_id
        )
        return return_data