Exemple #1
0
    def create_guild_from_dict(guild_data: dict) -> Guild:
        guild: Guild = Guild(id=guild_data["id"],
                             options=Options(**guild_data["options"]))

        for member in guild_data["members"]:
            guild.members[
                member["id"]] = FactoryBuilder.create_member_from_dict(member)

        log.info("Created Guild(id=%s) from dict", guild.id)

        return guild
Exemple #2
0
    async def get_guild(self, guild_id: int) -> Guild:
        log.debug("Attempting to return cached Guild(id=%s)", guild_id)
        resp = await self.redis.get(f"GUILD:{guild_id}")
        if not resp:
            raise GuildNotFound

        as_json = json.loads(resp.decode("utf-8"))
        guild: Guild = Guild(**as_json)
        # This is actually a dict here
        guild.options = Options(**guild.options)  # type: ignore

        guild_members: Dict[int, Member] = {}
        for member_id in guild.members:  # type: ignore
            member: Member = await self.get_member(member_id, guild_id)
            guild_members[member.id] = member

        guild.members = guild_members
        return guild
Exemple #3
0
    async def clean_cache(self, strict=False) -> None:
        """
        Cleans the internal cache, pruning
        any old/un-needed entries.

        Non Strict mode:
         - Member deletion criteria:
            - warn_count == default
            - kick_count == default
            - duplicate_counter == default
            - duplicate_channel_counter_dict == default
            - addons dict == default
            - Also must have no active messages after cleaning.

         - Guild deletion criteria:
            - options are not custom
            - log_channel_id is not set
            - addons dict == default
            - Also must have no members stored

        Strict mode:
         - Member deletion criteria
            - Has no active messages

         - Guild deletion criteria
            - Does not have custom options
            - log_channel_id is not set
            - Has no active members

        Parameters
        ----------
        strict : bool
            Toggles the above


        Notes
        -----
        This is expensive, and likely
        only required to be run every so often
        depending on how high traffic your bot is.
        """
        # In a nutshell,
        # Get entire cache and loop over
        # Build a list of 'still valid' cache entries
        # Drop the previous cache
        # Insert the new list of 'still valid' entries, this is now the cache

        # Ideally I don't want to load this cache into memory
        cache = []
        async for guild in self.cache.get_all_guilds():
            new_guild = Guild(guild.id)
            for member in guild.members.values():
                FactoryBuilder.clean_old_messages(member, get_aware_time(),
                                                  self.options)

                if strict and len(member.messages) != 0:
                    new_guild.members[member.id] = member
                elif (len(member.messages) != 0 or member.kick_count != 0
                      or member.warn_count != 0
                      or member.duplicate_counter != 1
                      or bool(member.duplicate_channel_counter_dict)
                      or bool(member.addons)):
                    new_guild.members[member.id] = member

            # Clean guild
            predicate = (guild.options != Options()
                         or guild.log_channel_id is not None
                         or bool(new_guild.members))
            if strict and predicate:
                new_guild.options = guild.options
                new_guild.log_channel_id = guild.log_channel_id
                cache.append(new_guild)
            elif predicate or bool(guild.addons):
                new_guild.addons = guild.addons
                new_guild.options = guild.options
                new_guild.log_channel_id = guild.log_channel_id
                cache.append(new_guild)

        await self.cache.drop()

        for guild in cache:
            await self.cache.set_guild(guild)

        log.info("Cleaned the internal cache")
Exemple #4
0
    def __init__(
        self,
        bot,
        *,
        library: Library = Library.DPY,
        options: Options = None,
        cache: Cache = None,
    ):
        """
        AntiSpamHandler entry point.

        Parameters
        ----------
        bot
            A reference to your discord bot object.
        library : Library, Optional
            An enum denoting the library this AntiSpamHandler.
            See :py:class:`antispam.enums.library.Library` for more

            Defaults to DPY support
        options : Options, Optional
            An instance of your custom Options
            the handler should use
        cache : Cache, Optional
            Your choice of backend caching
        """
        # TODO Implement an async cache initialization somehow

        options = options or Options()
        if not isinstance(options, Options):
            raise ValueError("Expected `option`s of type `Option`")

        self.options = options

        if self.options.no_punish and (self.options.delete_spam
                                       or self.options.warn_only
                                       or self.options.per_channel_spam):
            log.warning(
                "You are attempting to create an AntiSpamHandler with options that are mutually exclusive. "
                "Please note `no_punish` will over rule any other options you attempt to set which are "
                "mutually exclusive.")

        cache = cache or MemoryCache(self)
        if not issubclass(type(cache), Cache):
            raise ValueError(
                "Expected `cache` that inherits from the `Cache` Protocol")

        self.bot = bot
        self.cache = cache
        self.core = Core(self)

        self.needs_init = True

        self.pre_invoke_plugins: Dict[str, BasePlugin] = {}
        self.after_invoke_extensions: Dict[str, BasePlugin] = {}

        # Import these here to avoid errors when not having the
        # other lib installed, I think
        if library == Library.HIKARI:
            from antispam.libs.hikari import Hikari

            self.lib_handler = Hikari(self)

        elif library == Library.PINCER:
            from antispam.libs.pincer import Pincer

            self.lib_handler = Pincer(self)

        else:
            from antispam.libs.dpy import DPY

            self.lib_handler = DPY(self)

        log.info("Package instance created")
Exemple #5
0
    async def load_from_dict(
        bot,
        data: dict,
        *,
        raise_on_exception: bool = True,
        plugins: Set[Type[BasePlugin]] = None,
    ):
        """
        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
            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)``
        plugins : Set[Type[BasePlugin]]
            A set for plugin lookups if you want to initialise
            plugins from an initial saved state. This should follow the format.

            .. code-block:: python

                {ClassReference}

            So for example:

            .. code-block:: python
                :linenos:

                class Plugin(BasePlugin):
                    pass

                # Where you load ASH
                await AntiSpamHandler.load_from_dict(..., ..., plugins={Plugin}

        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 will simply ignore the saved state of plugins that don't have
        a ``plugins`` mapping.

        """
        # TODO Add redis cache here
        plugins = plugins or {}
        plugins: Dict[str, Type[BasePlugin]] = {
            class_ref.__name__: class_ref
            for class_ref in plugins
        }

        caches = {"MemoryCache": MemoryCache}

        try:
            ash = AntiSpamHandler(bot=bot, options=Options(**data["options"]))
            cache_type = data["cache"]
            ash.cache = caches[cache_type](ash)
            for guild in data["guilds"]:
                await ash.cache.set_guild(
                    FactoryBuilder.create_guild_from_dict(guild))

            if pre_invoke_plugins := data.get("pre_invoke_plugins"):
                for plugin, plugin_data in pre_invoke_plugins.items():
                    if plugin_class_ref := plugins.get(plugin):
                        ash.register_plugin(await
                                            plugin_class_ref.load_from_dict(
                                                ash, plugin_data))
                    else:
                        log.debug("Skipping state loading for %s", plugin)
    def __init__(
        self,
        bot,
        library: Library,
        *,
        options: Options = None,
        cache: Cache = None,
    ):
        """
        AntiSpamHandler entry point.

        Parameters
        ----------
        bot
            A reference to your discord bot object.
        library : Library, Optional
            An enum denoting the library this AntiSpamHandler.
            See :py:class:`antispam.enums.library.Library` for more
        options : Options, Optional
            An instance of your custom Options
            the handler should use
        cache : Cache, Optional
            Your choice of backend caching
        """

        options = options or Options()
        if not isinstance(options, Options):
            raise ValueError("Expected `option`s of type `Option`")

        self.options = options

        if self.options.no_punish and (
            self.options.delete_spam
            or self.options.warn_only
            or self.options.per_channel_spam
        ):
            log.warning(
                "You are attempting to create an AntiSpamHandler with options that are mutually exclusive. "
                "Please note `no_punish` will over rule any other options you attempt to set which are "
                "mutually exclusive."
            )

        if self.options.use_timeouts and self.options.warn_only:
            log.warning(
                "You are attempting to create an AntiSpamHandler with options that are mutually exclusive. "
                "Please note `use_timeouts` will over rule any other options you attempt to set which are "
                "mutually exclusive."
            )

        cache = cache or MemoryCache(self)
        if not issubclass(type(cache), Cache):
            raise ValueError("Expected `cache` that inherits from the `Cache` Protocol")

        self.bot = bot
        self.cache = cache
        self.core = Core(self)

        self.needs_init = True

        self.pre_invoke_plugins: Dict[str, BasePlugin] = {}
        self.after_invoke_plugins: Dict[str, BasePlugin] = {}

        # Import these here to avoid errors when not
        # having the other lib installed
        self.lib_handler = None
        if library == Library.HIKARI:
            from antispam.libs.lib_hikari import Hikari

            self.lib_handler = Hikari(self)

        elif library == Library.PINCER:
            from antispam.libs.lib_pincer import Pincer

            if self.options.use_timeouts:
                raise UnsupportedAction(
                    "Pincer does not currently support timeouts. Please disable this option."
                )

            self.lib_handler = Pincer(self)

        elif library == Library.DISNAKE:
            from antispam.libs.dpy_forks.lib_disnake import Disnake

            self.lib_handler = Disnake(self)

        elif library == Library.ENHANCED_DPY:
            from antispam.libs.dpy_forks.lib_enhanced_dpy import EnhancedDPY

            self.lib_handler = EnhancedDPY(self)

        elif library == Library.NEXTCORD:
            from antispam.libs.dpy_forks.lib_nextcord import Nextcord

            self.lib_handler = Nextcord(self)

        elif library == Library.PYCORD:
            raise UnsupportedAction(
                "Py-cord is no longer officially supported, please see the following url for support:\n"
                "https://gist.github.com/Skelmis/b15a64f11c2ef89a7c6083ff455774a2"
            )

        elif library == Library.CUSTOM:
            pass

        elif library == Library.DPY:
            from antispam.libs.dpy import DPY

            self.lib_handler = DPY(self)
        else:
            raise UnsupportedAction(
                "You must set a library for usage. See here for choices: "
                "https://dpy-anti-spam.readthedocs.io/en/latest/modules/interactions/enums.html#antispam.enums.Library"
            )

        log.info("Package instance created")