示例#1
0
 async def asyncSetUp(self):
     """
     Simply setup our Ash obj before usage
     """
     self.ash = AntiSpamHandler(get_mocked_bot(name="bot", id=98987))
     self.ash.guilds = Guild(None, 12, Static.DEFAULTS)
     self.ash.guilds = Guild(None, 15, Static.DEFAULTS)
示例#2
0
    def remove_custom_guild_options(self, guild_id: int) -> None:
        """
        Reset a guilds options to the ASH options

        Parameters
        ----------
        guild_id : int
            The guild to reset

        Notes
        -----
        This method will silently ignore guilds that
        do not exist, as it is considered to have
        'removed' custom options due to how Guild's
        are created

        """
        guild = Guild(self.bot, guild_id, self.options, logger=logging)
        try:
            guild = next(iter(g for g in self.guilds if g == guild))
        except StopIteration:
            pass
        else:
            guild.options = self.options
            guild.has_custom_options = False
示例#3
0
    def remove_custom_guild_options(self, guild_id: int) -> None:
        """
        Reset a guilds options to the ASH options

        Parameters
        ----------
        guild_id : int
            The guild to reset

        Warnings
        --------
        If using ``AntiSpamTracker``, please call this
        method on that class instance. Not this one.

        Notes
        -----
        This method will silently ignore guilds that
        do not exist, as it is considered to have
        'removed' custom options due to how Guild's
        are created

        This is also a somewhat expensive operation at ``O(n)``
        where ``n`` is the total number of users cached for the guild
        """
        guild = Guild(self.bot, guild_id, self.options)
        try:
            guild = next(iter(g for g in self.guilds if g == guild))
        except StopIteration:
            pass
        else:
            guild.options = self.options
            guild.has_custom_options = False

            log.debug(f"Reset guild options for {guild_id}")
    def remove_custom_guild_options(self, guild_id: int) -> None:
        """
        Reset a guilds options to the ASH options

        Parameters
        ----------
        guild_id : int
            The guild to reset

        Warnings
        --------
        If using ``AntiSpamTracker``, please call this
        method on that class instance. Not this one.

        Notes
        -----
        This method will silently ignore guilds that
        do not exist, as it is considered to have
        'removed' custom options due to how Guild's
        are created

        """
        guild = Guild(self.bot, guild_id, self.options)
        try:
            guild = next(iter(g for g in self.guilds if g == guild))
        except StopIteration:
            pass
        else:
            guild.options = self.options
            guild.has_custom_options = False
示例#5
0
 async def asyncSetUp(self):
     """
     Simply setup our Ash obj before usage
     """
     self.ash = AntiSpamHandler(
         MockedMember(name="bot", member_id=98987,
                      mock_type="bot").to_mock())
     self.ash.guilds = Guild(None, 12, Static.DEFAULTS)
     self.ash.guilds = Guild(None, 15, Static.DEFAULTS)
示例#6
0
 def setUp(self):
     """
     Simply setup our Guild obj before usage
     """
     self.guild = Guild(None, 15, Static.DEFAULTS)
     self.guild.users = User(None, 20, 15, Static.DEFAULTS)
     self.guild.users = User(None, 21, 15, Static.DEFAULTS)
示例#7
0
    async def test_properties(self):
        with self.assertRaises(ValueError):
            self.ash.guilds = "1"

        with self.assertRaises(DuplicateObject):
            self.ash.guilds = Guild(
                None, 15, Static.DEFAULTS, logger=logging.getLogger(__name__)
            )
示例#8
0
    def reset_user_count(self, user_id: int, guild_id: int,
                         counter: str) -> None:
        """
        Reset an internal counter attached
        to a User object

        Parameters
        ----------
        user_id : int
            The user to reset
        guild_id : int
            The guild they are attached to
        counter : str
            A str denoting which count
            to reset, Options are:\n
            ``warn_counter`` -> Reset the warn count\n
            ``kick_counter`` -> Reset the kick count

        Raises
        ======
        LogicError
            Invalid count to reset

        Notes
        =====
        Silently ignores if the User or
        Guild does not exist. This is because
        in the packages mind, the counts are
        'reset' since the default value is
        the reset value.

        """
        guild = Guild(self.bot, guild_id, self.options)
        try:
            guild = next(iter(g for g in self.guilds if g == guild))
        except StopIteration:
            return

        user = User(self.bot,
                    user_id,
                    guild_id=guild_id,
                    options=guild.options)
        try:
            user = next(iter(u for u in guild.users if u == user))
        except StopIteration:
            return

        if counter.lower() == Static.WARNCOUNTER:
            user.warn_count = 0
            log.debug(f"Reset the warn count for user: {user_id}")
        elif counter.lower() == Static.KICKCOUNTER:
            user.kick_count = 0
            log.debug(f"Reset the kick count for user: {user_id}")
        else:
            raise LogicError(
                "Invalid counter argument, please select a valid counter.")
示例#9
0
    def get_guild_options(self, guild_id: int) -> tuple:
        """
        Get the options dictionary for a given guild,
        if the guild doesnt exist raise an exception

        Parameters
        ----------
        guild_id : int
            The guild to get custom options for

        Returns
        -------
        tuple
            The options for this guild as tuple[0] and tuple[1] is a bool
            which is used to say if the guild has custom options or not

            Be wary of the return value. It is in the format,
            (dict, boolean),
            where dict is the options and boolean is whether
            or not these options are custom

        Raises
        ------
        BaseASHException
            This guild does not exist

        Notes
        -----
        The value for tuple[1] is not checked/ensured at runtime.
        Be wary of this if you access guild and manually change
        options rather then using this libraries methods.

        Another thing to note is this returns a deepcopy of the
        options dictionary. This is to encourage usage of this
        libraries methods for changing options, rather then
        playing around with them yourself and potentially
        doing damage.

        """
        guild = Guild(self.bot, guild_id, self.options)
        try:
            guild = next(iter(g for g in self.guilds if g == guild))
        except StopIteration:
            raise BaseASHException("This guild does not exist")
        else:
            log.debug(f"Returned guild options for {guild_id}")
            return deepcopy(guild.options), guild.has_custom_options
示例#10
0
 def setUp(self):
     """
     Simply setup our Guild obj before usage
     """
     self.guild = Guild(None,
                        15,
                        Static.DEFAULTS,
                        logger=logging.getLogger(__name__))
     self.guild.users = User(None,
                             20,
                             15,
                             Static.DEFAULTS,
                             logger=logging.getLogger(__name__))
     self.guild.users = User(None,
                             21,
                             15,
                             Static.DEFAULTS,
                             logger=logging.getLogger(__name__))
示例#11
0
    async def test_properties(self):
        with self.assertRaises(ValueError):
            self.ash.guilds = "1"

        with self.assertRaises(DuplicateObject):
            self.ash.guilds = Guild(None, 15, Static.DEFAULTS)
示例#12
0
    def load_from_dict(
            bot,
            data: dict,
            *,
            raise_on_exception: bool = True):  # TODO typehint this correct
        """
        Can be used as an entry point when starting your bot
        to reload a previous state so you don't lose all of
        the previous punishment records, etc, etc

        Parameters
        ----------
        bot : commands.Bot
            The bot instance
        data : dict
            The data to load AntiSpamHandler from
        raise_on_exception : bool
            Whether or not to raise if an issue is encountered
            while trying to rebuild ``AntiSpamHandler`` from a saved state

            If you set this to False, and an exception occurs during the
            build process. This will return an ``AntiSpamhandler`` instance
            **without** any of the saved state and is equivalent to simply
            doing ``AntiSpamHandler(bot)``

        Returns
        -------
        AntiSpamHandler
            A new AntiSpamHandler instance where
            the state is equal to the provided dict

        Warnings
        --------
        Don't provide data that was not given to you
        outside of the ``save_to_dict`` method unless
        you are maintaining the correct format.

        Notes
        -----
        This method does not check for data conformity.
        Any invalid input will error unless you set
        ``raise_on_exception`` to ``False`` in which case
        the following occurs

        If you set ``raise_on_exception`` to ``False``, and an exception occurs during the
        build process. This method will return an ``AntiSpamhandler`` instance
        **without** any of the saved state and is equivalent to simply
        doing ``AntiSpamHandler(bot)``

        -----

        This is fairly computationally expensive. It deepcopies
        nearly everything lol.

        """
        try:
            ash = AntiSpamHandler(bot=bot, **data["options"])
            for guild in data["guilds"]:
                ash.guilds = Guild.load_from_dict(bot, guild)

            log.info("Loaded AntiSpamHandler from state")
        except Exception as e:
            if raise_on_exception:
                log.debug(
                    "Raising exception when attempting to load from state")
                raise e

            ash = AntiSpamHandler(bot=bot)
            log.info(
                "Failed to load AntiSpamHandler from state, returning a generic instance"
            )

        return ash
示例#13
0
    def add_custom_guild_options(self, guild_id: int, **kwargs):
        """
        Set a guild's options to a custom set, rather then the base level
        set used and defined in ASH initialization

        Parameters
        ----------
        guild_id : int
            The id of the guild to create
        warn_threshold : int, optional
            This is the amount of messages in a row that result in a warning within the message_interval
        kick_threshold : int, optional
            The amount of 'warns' before a kick occurs
        ban_threshold : int, optional
            The amount of 'kicks' that occur before a ban occurs
        message_interval : int, optional
            Amount of time a message is kept before being discarded.
            Essentially the amount of time (In milliseconds) a message can count towards spam
        guild_warn_message : Union[str, dict], optional
            The message to be sent in the guild upon warn_threshold being reached
        guild_kick_message : Union[str, dict], optional
            The message to be sent in the guild upon kick_threshold being reached
        guild_ban_message : Union[str, dict], optional
            The message to be sent in the guild upon ban_threshold being reached
        user_kick_message : Union[str, dict], optional
            The message to be sent to the user who is being warned
        user_ban_message : Union[str, dict], optional
            The message to be sent to the user who is being banned
        user_failed_kick_message : Union[str, dict], optional
            The message to be sent to the user if the bot fails to kick them
        user_failed_ban_message : Union[str, dict], optional
            The message to be sent to the user if the bot fails to ban them
        message_duplicate_count : int, optional
            Amount of duplicate messages needed to trip a punishment
        message_duplicate_accuracy : float, optional
            How 'close' messages need to be to be registered as duplicates (Out of 100)
        delete_spam : bool, optional
            Whether or not to delete any messages marked as spam
        ignore_perms : list, optional
            The perms (ID Form), that bypass anti-spam
        ignore_channels : list, optional
            The channels (ID Form) that are ignored
        ignore_roles : list, optional
            The roles (ID, Name) that are ignored
        ignore_guilds : list, optional
            The guilds (ID) that are ignored
        ignore_users : list, optional
            The users (ID Form), that bypass anti-spam
        ignore_bots : bool, optional
            Should bots bypass anti-spam?
        warn_only : bool, optional
            Only warn users?
        no_punish : bool, optional
            Dont punish users?
            Return if they should be punished or
            not without actually punishing them
        per_channel_spam : bool, optional
            Track spam as per channel,
            rather then per guild
        guild_warn_message_delete_after : int, optional
            The time to delete the ``guild_warn_message`` message
        user_kick_message_delete_after : int, optional
            The time to delete the ``user_kick_message`` message
        guild_kick_message_delete_after : int, optional
            The time to delete the ``guild_kick_message`` message
        user_ban_message_delete_after : int, optional
            The time to delete the ``user_ban_message`` message
        guild_ban_message_delete_after : int, optional
            The time to delete the ``guild_ban_message`` message

        Warnings
        --------
        If using ``AntiSpamTracker``, please call this
        method on that class instance. Not this one.

        Notes
        =====
        This will override any current settings, if you wish
        to continue using existing settings and merely change some
        I suggest using the get_options method first and then giving
        those values back to this method with the changed arguments

        This is also a somewhat expensive operation at ``O(n)``
        where ``n`` is the total number of users cached for the guild
        """
        options = self._ensure_options(**kwargs)

        guild = Guild(self.bot, guild_id, options, custom_options=True)
        try:
            guild = next(iter(g for g in self.guilds if g == guild))
        except StopIteration:
            log.warning(
                f"I cannot ensure I have permissions to kick/ban ban people in guild: {guild_id}"
            )

            self.guilds = guild
            log.info(f"Created Guild: {guild.id}")
        else:
            guild.options = options
            guild.has_custom_options = True

        log.info(f"Set custom options for guild: {guild_id}")
示例#14
0
    async def propagate(self, message: discord.Message) -> Optional[dict]:
        """
        This method is the base level intake for messages, then
        propagating it out to the relevant guild or creating one
        if that is required

        For what this returns please see the top of this page.

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

        Returns
        =======
        dict
            A dictionary of useful information about the user in question
        """
        if not isinstance(message, discord.Message) and not isinstance(
                message, AsyncMock):
            log.debug("Invalid value given to propagate")
            raise ValueError(
                "Expected message of ignore_type: discord.Message")

        # Ensure we only moderate actual guild messages
        if not message.guild:
            log.debug("Message was not in a guild")
            return {"status": "Ignoring messages from dm's"}

        # The bot is immune to spam
        if message.author.id == self.bot.user.id:
            log.debug("Message was from myself")
            return {"status": "Ignoring messages from myself (the bot)"}

        if isinstance(message.author, discord.User):
            log.warning(f"Given message with an author of type User")

        # Return if ignored bot
        if self.options["ignore_bots"] and message.author.bot:
            log.debug(
                f"I ignore bots, and this is a bot message: {message.author.id}"
            )
            return {"status": "Ignoring messages from bots"}

        # Return if ignored member
        if message.author.id in self.options["ignore_users"]:
            log.debug(
                f"The user who sent this message is ignored: {message.author.id}"
            )
            return {"status": f"Ignoring this user: {message.author.id}"}

        # Return if ignored channel
        if (message.channel.id in self.options["ignore_channels"]
                or message.channel.name in self.options["ignore_channels"]):
            log.debug(f"{message.channel} is ignored")
            return {"status": f"Ignoring this channel: {message.channel.id}"}

        # Return if member has an ignored role
        try:
            user_roles = [role.id for role in message.author.roles]
            user_roles.extend([role.name for role in message.author.roles])
            for item in user_roles:
                if item in self.options.get("ignore_roles"):
                    log.debug(f"{item} is a part of ignored roles")
                    return {"status": f"Ignoring this role: {item}"}
        except AttributeError:
            log.warning(
                f"Could not compute ignore_roles for {message.author.name}({message.author.id})"
            )

        # Return if ignored guild
        if message.guild.id in self.options.get("ignore_guilds"):
            log.debug(f"{message.guild.id} is an ignored guild")
            return {"status": f"Ignoring this guild: {message.guild.id}"}

        log.debug(
            f"Propagating message for: {message.author.name}({message.author.id})"
        )

        guild = Guild(self.bot, message.guild.id, self.options)
        try:
            guild = next(iter(g for g in self.guilds if g == guild))
        except StopIteration:
            # Check we have perms to actually create this guild object
            # and punish based upon our guild wide permissions
            perms = message.guild.me.guild_permissions
            if not perms.kick_members or not perms.ban_members:
                raise MissingGuildPermissions

            self.guilds = guild
            log.info(f"Created Guild: {guild.id}")

        data = {"pre_invoke_extensions": [], "after_invoke_extensions": []}

        for pre_invoke_ext in self.pre_invoke_extensions.values():
            pre_invoke_return = await pre_invoke_ext.propagate(message)
            data["pre_invoke_extensions"].append(pre_invoke_return)

        main_return = await guild.propagate(message)

        for after_invoke_ext in self.after_invoke_extensions.values():
            after_invoke_return = await after_invoke_ext.propagate(
                message, main_return)
            data["after_invoke_extensions"].append(after_invoke_return)

        return {**main_return, **data}