From 1c7178a10b2ae2ae017956c8a3dcbd95ba8fce00 Mon Sep 17 00:00:00 2001 From: Kreusada <67752638+Kreusada@users.noreply.github.com> Date: Thu, 22 Dec 2022 22:49:44 +0000 Subject: [PATCH] Add automodule reference for the antispam module (#5641) Co-authored-by: Jakub Kuczys Co-authored-by: Flame442 <34169552+Flame442@users.noreply.github.com> --- docs/framework_utils.rst | 6 +++ redbot/core/utils/antispam.py | 99 +++++++++++++++++++++++++++++------ 2 files changed, 89 insertions(+), 16 deletions(-) diff --git a/docs/framework_utils.rst b/docs/framework_utils.rst index 29a3b8447..9d719a795 100644 --- a/docs/framework_utils.rst +++ b/docs/framework_utils.rst @@ -80,3 +80,9 @@ Utility UI .. automodule:: redbot.core.utils.views :members: + +AntiSpam +======== + +.. automodule:: redbot.core.utils.antispam + :members: diff --git a/redbot/core/utils/antispam.py b/redbot/core/utils/antispam.py index b2b6c9b33..4a49bc4c1 100644 --- a/redbot/core/utils/antispam.py +++ b/redbot/core/utils/antispam.py @@ -2,21 +2,84 @@ from datetime import datetime, timedelta from typing import Tuple, List from collections import namedtuple -Interval = Tuple[timedelta, int] -AntiSpamInterval = namedtuple("AntiSpamInterval", ["period", "frequency"]) +_AntiSpamInterval = namedtuple("_AntiSpamInterval", ["period", "frequency"]) class AntiSpam: """ - Custom class which is more flexible than using discord.py's - `commands.cooldown()` + A class that can be used to count the number of events that happened + within specified intervals. Later, it can be checked whether the specified + maximum count for any of the specified intervals has been exceeded. - Can be intialized with a custom set of intervals - These should be provided as a list of tuples in the form - (timedelta, quantity) + Examples + -------- + Tracking whether the number of reports sent by a user within a single guild is spammy: - Where quantity represents the maximum amount of times - something should be allowed in an interval. + .. code-block:: python + + class MyCog(commands.Cog): + INTERVALS = [ + # More than one report within last 5 seconds is considered spam. + (datetime.timedelta(seconds=5), 1), + # More than 3 reports within the last 5 minutes are considered spam. + (datetime.timedelta(minutes=5), 3), + # More than 10 reports within the last hour are considered spam. + (datetime.timedelta(hours=1), 10), + # More than 24 reports within a single day (last 24 hours) are considered spam. + (datetime.timedelta(days=1), 24), + ] + + def __init__(self, bot): + self.bot = bot + self.antispam = {} + + @commands.guild_only() + @commands.command() + async def report(self, ctx, content): + # We want to track whether a single user within a single guild + # sends a spammy number of reports. + key = (ctx.guild.id, ctx.author.id) + + if key not in self.antispam: + # Create an instance of the AntiSpam class with given intervals. + self.antispam[key] = AntiSpam(self.INTERVALS) + # If you want to use the default intervals, you can use: AntiSpam([]) + + # Check if the user sent too many reports recently. + # The `AntiSpam.spammy` property is `True` if, for any interval, + # the number of events that happened within that interval + # exceeds the number specified for that interval. + if self.antispam[key].spammy: + await ctx.send( + "You've sent too many reports recently, please try again later." + ) + return + + # Make any other early-return checks. + + # Record the event. + self.antispam[key].stamp() + + # Save the report. + # self.config... + + # Send a message to the user. + await ctx.send("Your report has been submitted.") + + Parameters + ---------- + intervals : List[Tuple[datetime.timedelta, int]] + A list of tuples in the format (timedelta, int), + where the timedelta represents the length of the interval, + and the int represents the maximum number of times something + can happen within that interval. + + If an empty list is provided, the following defaults will be used: + + * 3 per 5 seconds + * 5 per 1 minute + * 10 per 1 hour + * 24 per 1 day """ # TODO : Decorator interface for command check using `spammy` @@ -30,13 +93,13 @@ class AntiSpam: (timedelta(days=1), 24), ] - def __init__(self, intervals: List[Interval]): + def __init__(self, intervals: List[Tuple[timedelta, int]]): self.__event_timestamps = [] - _itvs = intervals if intervals else self.default_intervals - self.__intervals = [AntiSpamInterval(*x) for x in _itvs] + _itvs = intervals or self.default_intervals + self.__intervals = [_AntiSpamInterval(*x) for x in _itvs] self.__discard_after = max([x.period for x in self.__intervals]) - def __interval_check(self, interval: AntiSpamInterval): + def __interval_check(self, interval: _AntiSpamInterval): return ( len([t for t in self.__event_timestamps if (t + interval.period) > datetime.utcnow()]) >= interval.frequency @@ -45,14 +108,18 @@ class AntiSpam: @property def spammy(self): """ - use this to check if any interval criteria are met + Whether, for any interval, the number of events that happened + within that interval exceeds the number specified for that interval. """ return any(self.__interval_check(x) for x in self.__intervals) def stamp(self): """ - Use this to mark an event that counts against the intervals - as happening now + Mark an event timestamp against the list of antispam intervals happening right now. + Use this after all checks have passed, and your action is taking place. + + The stamp will last until the corresponding interval duration + has expired (set when this AntiSpam object was initiated). """ self.__event_timestamps.append(datetime.utcnow()) self.__event_timestamps = [