Add automodule reference for the antispam module (#5641)

Co-authored-by: Jakub Kuczys <me@jacken.men>
Co-authored-by: Flame442 <34169552+Flame442@users.noreply.github.com>
This commit is contained in:
Kreusada 2022-12-22 22:49:44 +00:00 committed by GitHub
parent 3cb17116be
commit 1c7178a10b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 89 additions and 16 deletions

View File

@ -80,3 +80,9 @@ Utility UI
.. automodule:: redbot.core.utils.views
:members:
AntiSpam
========
.. automodule:: redbot.core.utils.antispam
:members:

View File

@ -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 = [