aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/decorators.py33
-rw-r--r--bot/exts/moderation/infraction/infractions.py9
-rw-r--r--bot/exts/moderation/infraction/management.py2
-rw-r--r--bot/exts/moderation/infraction/superstarify.py2
4 files changed, 45 insertions, 1 deletions
diff --git a/bot/decorators.py b/bot/decorators.py
index f4331264f..8971898b3 100644
--- a/bot/decorators.py
+++ b/bot/decorators.py
@@ -4,6 +4,7 @@ import types
import typing as t
from contextlib import suppress
+import arrow
from discord import Member, NotFound
from discord.ext import commands
from discord.ext.commands import Cog, Context
@@ -236,3 +237,35 @@ def mock_in_debug(return_value: t.Any) -> t.Callable:
return await func(*args, **kwargs)
return wrapped
return decorator
+
+
+def ensure_future_timestamp(timestamp_arg: function.Argument) -> t.Callable:
+ """
+ Ensure the timestamp argument is in the future.
+
+ If the condition fails, send a warning to the invoking context.
+
+ `timestamp_arg` is the keyword name or position index of the parameter of the decorated command
+ whose value is the target timestamp.
+
+ This decorator must go before (below) the `command` decorator.
+ """
+ def decorator(func: types.FunctionType) -> types.FunctionType:
+ @command_wraps(func)
+ async def wrapper(*args, **kwargs) -> t.Any:
+ bound_args = function.get_bound_args(func, args, kwargs)
+ target = function.get_arg_value(timestamp_arg, bound_args)
+
+ ctx = function.get_arg_value(1, bound_args)
+
+ try:
+ is_future = target > arrow.utcnow()
+ except TypeError:
+ is_future = True
+ if not is_future:
+ await ctx.send(":x: Provided timestamp is in the past.")
+ return
+
+ return await func(*args, **kwargs)
+ return wrapper
+ return decorator
diff --git a/bot/exts/moderation/infraction/infractions.py b/bot/exts/moderation/infraction/infractions.py
index af42ab1b8..18bed5080 100644
--- a/bot/exts/moderation/infraction/infractions.py
+++ b/bot/exts/moderation/infraction/infractions.py
@@ -10,7 +10,7 @@ from bot import constants
from bot.bot import Bot
from bot.constants import Event
from bot.converters import Age, Duration, Expiry, MemberOrUser, UnambiguousMemberOrUser
-from bot.decorators import respect_role_hierarchy
+from bot.decorators import ensure_future_timestamp, respect_role_hierarchy
from bot.exts.moderation.infraction import _utils
from bot.exts.moderation.infraction._scheduler import InfractionScheduler
from bot.log import get_logger
@@ -81,6 +81,7 @@ class Infractions(InfractionScheduler, commands.Cog):
await self.apply_kick(ctx, user, reason)
@command()
+ @ensure_future_timestamp(timestamp_arg=3)
async def ban(
self,
ctx: Context,
@@ -97,6 +98,7 @@ class Infractions(InfractionScheduler, commands.Cog):
await self.apply_ban(ctx, user, reason, expires_at=duration)
@command(aliases=("cban", "purgeban", "pban"))
+ @ensure_future_timestamp(timestamp_arg=3)
async def cleanban(
self,
ctx: Context,
@@ -161,6 +163,7 @@ class Infractions(InfractionScheduler, commands.Cog):
await ctx.send(":x: This command is not yet implemented. Maybe you meant to use `voicemute`?")
@command(aliases=("vmute",))
+ @ensure_future_timestamp(timestamp_arg=3)
async def voicemute(
self,
ctx: Context,
@@ -180,6 +183,7 @@ class Infractions(InfractionScheduler, commands.Cog):
# region: Temporary infractions
@command(aliases=["mute"])
+ @ensure_future_timestamp(timestamp_arg=3)
async def tempmute(
self, ctx: Context,
user: UnambiguousMemberOrUser,
@@ -213,6 +217,7 @@ class Infractions(InfractionScheduler, commands.Cog):
await self.apply_mute(ctx, user, reason, expires_at=duration)
@command(aliases=("tban",))
+ @ensure_future_timestamp(timestamp_arg=3)
async def tempban(
self,
ctx: Context,
@@ -248,6 +253,7 @@ class Infractions(InfractionScheduler, commands.Cog):
await ctx.send(":x: This command is not yet implemented. Maybe you meant to use `tempvoicemute`?")
@command(aliases=("tempvmute", "tvmute"))
+ @ensure_future_timestamp(timestamp_arg=3)
async def tempvoicemute(
self,
ctx: Context,
@@ -294,6 +300,7 @@ class Infractions(InfractionScheduler, commands.Cog):
# region: Temporary shadow infractions
@command(hidden=True, aliases=["shadowtempban", "stempban", "stban"])
+ @ensure_future_timestamp(timestamp_arg=3)
async def shadow_tempban(
self,
ctx: Context,
diff --git a/bot/exts/moderation/infraction/management.py b/bot/exts/moderation/infraction/management.py
index c12dff928..62d349519 100644
--- a/bot/exts/moderation/infraction/management.py
+++ b/bot/exts/moderation/infraction/management.py
@@ -9,6 +9,7 @@ from discord.utils import escape_markdown
from bot import constants
from bot.bot import Bot
from bot.converters import Expiry, Infraction, MemberOrUser, Snowflake, UnambiguousUser, allowed_strings
+from bot.decorators import ensure_future_timestamp
from bot.errors import InvalidInfraction
from bot.exts.moderation.infraction import _utils
from bot.exts.moderation.infraction.infractions import Infractions
@@ -122,6 +123,7 @@ class ModManagement(commands.Cog):
await self.infraction_edit(ctx, infraction, duration, reason=reason)
@infraction_group.command(name='edit', aliases=('e',))
+ @ensure_future_timestamp(timestamp_arg=3)
async def infraction_edit(
self,
ctx: Context,
diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py
index b91a5edba..c4a7e5081 100644
--- a/bot/exts/moderation/infraction/superstarify.py
+++ b/bot/exts/moderation/infraction/superstarify.py
@@ -11,6 +11,7 @@ from discord.utils import escape_markdown
from bot import constants
from bot.bot import Bot
from bot.converters import Duration, Expiry
+from bot.decorators import ensure_future_timestamp
from bot.exts.moderation.infraction import _utils
from bot.exts.moderation.infraction._scheduler import InfractionScheduler
from bot.log import get_logger
@@ -103,6 +104,7 @@ class Superstarify(InfractionScheduler, Cog):
await self.reapply_infraction(infraction, action)
@command(name="superstarify", aliases=("force_nick", "star", "starify", "superstar"))
+ @ensure_future_timestamp(timestamp_arg=3)
async def superstarify(
self,
ctx: Context,