diff options
| -rw-r--r-- | bot/decorators.py | 33 | ||||
| -rw-r--r-- | bot/exts/moderation/infraction/infractions.py | 9 | ||||
| -rw-r--r-- | bot/exts/moderation/infraction/management.py | 2 | ||||
| -rw-r--r-- | bot/exts/moderation/infraction/superstarify.py | 2 | 
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, | 
