From 5f48d63d035fc7010755fd5595374459e060f1c6 Mon Sep 17 00:00:00 2001 From: Shakya Majumdar Date: Wed, 5 Jan 2022 14:29:15 +0530 Subject: disallow editing infraction durations into the past --- bot/exts/moderation/infraction/management.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bot/exts/moderation/infraction/management.py b/bot/exts/moderation/infraction/management.py index 9649ff852..66ce698ee 100644 --- a/bot/exts/moderation/infraction/management.py +++ b/bot/exts/moderation/infraction/management.py @@ -150,6 +150,10 @@ class ModManagement(commands.Cog): request_data['expires_at'] = None confirm_messages.append("marked as permanent") elif duration is not None: + now_datetime = datetime.now(duration.tzinfo) if duration.tzinfo else datetime.utcnow() + if duration < now_datetime: + await ctx.send(":x: Expiration is in the past.") + return request_data['expires_at'] = duration.isoformat() expiry = time.format_infraction_with_duration(request_data['expires_at']) confirm_messages.append(f"set to expire on {expiry}") -- cgit v1.2.3 From fbd072115ae79f4c7ec4960444a359a120bb0b05 Mon Sep 17 00:00:00 2001 From: Shakya Majumdar Date: Sat, 8 Jan 2022 08:57:45 +0530 Subject: remove redundant tzinfo check --- bot/exts/moderation/infraction/management.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/infraction/management.py b/bot/exts/moderation/infraction/management.py index 66ce698ee..1db579b89 100644 --- a/bot/exts/moderation/infraction/management.py +++ b/bot/exts/moderation/infraction/management.py @@ -150,7 +150,7 @@ class ModManagement(commands.Cog): request_data['expires_at'] = None confirm_messages.append("marked as permanent") elif duration is not None: - now_datetime = datetime.now(duration.tzinfo) if duration.tzinfo else datetime.utcnow() + now_datetime = datetime.now(timezone.utc) if duration < now_datetime: await ctx.send(":x: Expiration is in the past.") return -- cgit v1.2.3 From 22ad252db64f351cba32403fd890f281e8d975a0 Mon Sep 17 00:00:00 2001 From: Shakya Majumdar Date: Sat, 8 Jan 2022 11:25:07 +0530 Subject: migrate management.py to arrow --- bot/exts/moderation/infraction/management.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bot/exts/moderation/infraction/management.py b/bot/exts/moderation/infraction/management.py index 1db579b89..45a629293 100644 --- a/bot/exts/moderation/infraction/management.py +++ b/bot/exts/moderation/infraction/management.py @@ -1,10 +1,10 @@ import textwrap import typing as t -from datetime import datetime, timezone -import dateutil.parser +import arrow import discord from dateutil.relativedelta import relativedelta +from dateutil.tz import tzutc from discord.ext import commands from discord.ext.commands import Context from discord.utils import escape_markdown @@ -150,7 +150,7 @@ class ModManagement(commands.Cog): request_data['expires_at'] = None confirm_messages.append("marked as permanent") elif duration is not None: - now_datetime = datetime.now(timezone.utc) + now_datetime = arrow.utcnow() if duration < now_datetime: await ctx.send(":x: Expiration is in the past.") return @@ -376,12 +376,12 @@ class ModManagement(commands.Cog): if expires_at is None: duration = "*Permanent*" else: - date_from = datetime.fromtimestamp( + date_from = arrow.Arrow.fromtimestamp( float(time.DISCORD_TIMESTAMP_REGEX.match(created).group(1)), - timezone.utc + tzutc() ) - date_to = dateutil.parser.isoparse(expires_at) - duration = humanize_delta(relativedelta(date_to, date_from)) + date_to = arrow.get(expires_at) + duration = humanize_delta(relativedelta(date_to.datetime, date_from.datetime)) # Format `dm_sent` if dm_sent is None: -- cgit v1.2.3 From 69bcab19cbb9f4e43b9194d5ce1f3560d0094d29 Mon Sep 17 00:00:00 2001 From: Shakya Majumdar Date: Sat, 8 Jan 2022 11:37:14 +0530 Subject: disallow setting infraction durations into the past --- bot/exts/moderation/infraction/_scheduler.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bot/exts/moderation/infraction/_scheduler.py b/bot/exts/moderation/infraction/_scheduler.py index 57aa2d9b6..97d29eb60 100644 --- a/bot/exts/moderation/infraction/_scheduler.py +++ b/bot/exts/moderation/infraction/_scheduler.py @@ -137,8 +137,14 @@ class InfractionScheduler: icon = _utils.INFRACTION_ICONS[infr_type][0] reason = infraction["reason"] expiry = time.format_infraction_with_duration(infraction["expires_at"]) + expiry_datetime = arrow.get(infraction["expires_at"]) id_ = infraction['id'] + now_datetime = arrow.utcnow() + if expiry_datetime < now_datetime: + await ctx.send(":x: Expiration is in the past.") + return False + if user_reason is None: user_reason = reason -- cgit v1.2.3 From 82f08d1a3ab820545172d8415a9caed6559ad735 Mon Sep 17 00:00:00 2001 From: Shakya Majumdar Date: Mon, 21 Feb 2022 10:20:26 +0530 Subject: remove unused imports --- bot/exts/moderation/infraction/management.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bot/exts/moderation/infraction/management.py b/bot/exts/moderation/infraction/management.py index 3a2ee7ad0..a9dc231c1 100644 --- a/bot/exts/moderation/infraction/management.py +++ b/bot/exts/moderation/infraction/management.py @@ -3,8 +3,6 @@ import typing as t import arrow import discord -from dateutil.relativedelta import relativedelta -from dateutil.tz import tzutc from discord.ext import commands from discord.ext.commands import Context from discord.utils import escape_markdown -- cgit v1.2.3 From cc270dfa60ffed5e6251c9cfec82c69249c2931c Mon Sep 17 00:00:00 2001 From: Shakya Majumdar Date: Tue, 1 Mar 2022 18:47:08 +0530 Subject: move duration check to a decorator --- bot/decorators.py | 30 +++++++++++++++++++++++++++ bot/exts/moderation/infraction/_scheduler.py | 6 ------ bot/exts/moderation/infraction/infractions.py | 9 +++++++- bot/exts/moderation/infraction/management.py | 7 ++----- 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/bot/decorators.py b/bot/decorators.py index f4331264f..dd001d3ca 100644 --- a/bot/decorators.py +++ b/bot/decorators.py @@ -3,7 +3,9 @@ import functools import types import typing as t from contextlib import suppress +from datetime import datetime +import arrow from discord import Member, NotFound from discord.ext import commands from discord.ext.commands import Cog, Context @@ -236,3 +238,31 @@ def mock_in_debug(return_value: t.Any) -> t.Callable: return await func(*args, **kwargs) return wrapped return decorator + + +def ensure_duration_in_future(duration_arg: function.Argument) -> t.Callable: + """ + Ensure the duration argument is in the future. + + If the condition fails, a warning is sent to the invoking context. + + `duration_arg` is the keyword name or position index of the parameter of the decorated command + whose value is the target duration. + + 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(duration_arg, bound_args) + + ctx = function.get_arg_value(1, bound_args) + + if isinstance(target, datetime) and target < arrow.utcnow(): + await ctx.send(":x: Expiration is in the past.") + return + + return await func(*args, **kwargs) + return wrapper + return decorator diff --git a/bot/exts/moderation/infraction/_scheduler.py b/bot/exts/moderation/infraction/_scheduler.py index e607bf752..47b639421 100644 --- a/bot/exts/moderation/infraction/_scheduler.py +++ b/bot/exts/moderation/infraction/_scheduler.py @@ -137,14 +137,8 @@ class InfractionScheduler: icon = _utils.INFRACTION_ICONS[infr_type][0] reason = infraction["reason"] expiry = time.format_with_duration(infraction["expires_at"]) - expiry_datetime = arrow.get(infraction["expires_at"]) id_ = infraction['id'] - now_datetime = arrow.utcnow() - if expiry_datetime < now_datetime: - await ctx.send(":x: Expiration is in the past.") - return False - if user_reason is None: user_reason = reason diff --git a/bot/exts/moderation/infraction/infractions.py b/bot/exts/moderation/infraction/infractions.py index af42ab1b8..09610cb1a 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_duration_in_future, 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_duration_in_future(duration_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_duration_in_future(duration_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_duration_in_future(duration_arg=3) async def voicemute( self, ctx: Context, @@ -180,6 +183,7 @@ class Infractions(InfractionScheduler, commands.Cog): # region: Temporary infractions @command(aliases=["mute"]) + @ensure_duration_in_future(duration_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_duration_in_future(duration_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_duration_in_future(duration_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_duration_in_future(duration_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 a9dc231c1..4ec1039af 100644 --- a/bot/exts/moderation/infraction/management.py +++ b/bot/exts/moderation/infraction/management.py @@ -1,7 +1,6 @@ import textwrap import typing as t -import arrow import discord from discord.ext import commands from discord.ext.commands import Context @@ -10,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_duration_in_future from bot.errors import InvalidInfraction from bot.exts.moderation.infraction.infractions import Infractions from bot.exts.moderation.modlog import ModLog @@ -100,6 +100,7 @@ class ModManagement(commands.Cog): await self.infraction_edit(ctx, infraction, duration, reason=reason) @infraction_group.command(name='edit', aliases=('e',)) + @ensure_duration_in_future(duration_arg=3) async def infraction_edit( self, ctx: Context, @@ -147,10 +148,6 @@ class ModManagement(commands.Cog): request_data['expires_at'] = None confirm_messages.append("marked as permanent") elif duration is not None: - now_datetime = arrow.utcnow() - if duration < now_datetime: - await ctx.send(":x: Expiration is in the past.") - return request_data['expires_at'] = duration.isoformat() expiry = time.format_with_duration(duration) confirm_messages.append(f"set to expire on {expiry}") -- cgit v1.2.3 From 574adc3606f4d20c3a6f2415ae2aca028710350b Mon Sep 17 00:00:00 2001 From: Shakya Majumdar Date: Fri, 18 Mar 2022 10:25:35 +0530 Subject: rename function to be more generic --- bot/decorators.py | 14 +++++++------- bot/exts/moderation/infraction/infractions.py | 16 ++++++++-------- bot/exts/moderation/infraction/management.py | 4 ++-- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/bot/decorators.py b/bot/decorators.py index dd001d3ca..a0390fc32 100644 --- a/bot/decorators.py +++ b/bot/decorators.py @@ -240,14 +240,14 @@ def mock_in_debug(return_value: t.Any) -> t.Callable: return decorator -def ensure_duration_in_future(duration_arg: function.Argument) -> t.Callable: +def ensure_future_timestamp(timestamp_arg: function.Argument) -> t.Callable: """ - Ensure the duration argument is in the future. + Ensure the timestamp argument is in the future. - If the condition fails, a warning is sent to the invoking context. + If the condition fails, send a warning to the invoking context. - `duration_arg` is the keyword name or position index of the parameter of the decorated command - whose value is the target duration. + `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. """ @@ -255,12 +255,12 @@ def ensure_duration_in_future(duration_arg: function.Argument) -> t.Callable: @command_wraps(func) async def wrapper(*args, **kwargs) -> t.Any: bound_args = function.get_bound_args(func, args, kwargs) - target = function.get_arg_value(duration_arg, bound_args) + target = function.get_arg_value(timestamp_arg, bound_args) ctx = function.get_arg_value(1, bound_args) if isinstance(target, datetime) and target < arrow.utcnow(): - await ctx.send(":x: Expiration is in the past.") + await ctx.send(":x: Provided timestamp is in the past.") return return await func(*args, **kwargs) diff --git a/bot/exts/moderation/infraction/infractions.py b/bot/exts/moderation/infraction/infractions.py index 09610cb1a..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 ensure_duration_in_future, 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,7 +81,7 @@ class Infractions(InfractionScheduler, commands.Cog): await self.apply_kick(ctx, user, reason) @command() - @ensure_duration_in_future(duration_arg=3) + @ensure_future_timestamp(timestamp_arg=3) async def ban( self, ctx: Context, @@ -98,7 +98,7 @@ class Infractions(InfractionScheduler, commands.Cog): await self.apply_ban(ctx, user, reason, expires_at=duration) @command(aliases=("cban", "purgeban", "pban")) - @ensure_duration_in_future(duration_arg=3) + @ensure_future_timestamp(timestamp_arg=3) async def cleanban( self, ctx: Context, @@ -163,7 +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_duration_in_future(duration_arg=3) + @ensure_future_timestamp(timestamp_arg=3) async def voicemute( self, ctx: Context, @@ -183,7 +183,7 @@ class Infractions(InfractionScheduler, commands.Cog): # region: Temporary infractions @command(aliases=["mute"]) - @ensure_duration_in_future(duration_arg=3) + @ensure_future_timestamp(timestamp_arg=3) async def tempmute( self, ctx: Context, user: UnambiguousMemberOrUser, @@ -217,7 +217,7 @@ class Infractions(InfractionScheduler, commands.Cog): await self.apply_mute(ctx, user, reason, expires_at=duration) @command(aliases=("tban",)) - @ensure_duration_in_future(duration_arg=3) + @ensure_future_timestamp(timestamp_arg=3) async def tempban( self, ctx: Context, @@ -253,7 +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_duration_in_future(duration_arg=3) + @ensure_future_timestamp(timestamp_arg=3) async def tempvoicemute( self, ctx: Context, @@ -300,7 +300,7 @@ class Infractions(InfractionScheduler, commands.Cog): # region: Temporary shadow infractions @command(hidden=True, aliases=["shadowtempban", "stempban", "stban"]) - @ensure_duration_in_future(duration_arg=3) + @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 4ec1039af..842cdabcd 100644 --- a/bot/exts/moderation/infraction/management.py +++ b/bot/exts/moderation/infraction/management.py @@ -9,7 +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_duration_in_future +from bot.decorators import ensure_future_timestamp from bot.errors import InvalidInfraction from bot.exts.moderation.infraction.infractions import Infractions from bot.exts.moderation.modlog import ModLog @@ -100,7 +100,7 @@ class ModManagement(commands.Cog): await self.infraction_edit(ctx, infraction, duration, reason=reason) @infraction_group.command(name='edit', aliases=('e',)) - @ensure_duration_in_future(duration_arg=3) + @ensure_future_timestamp(timestamp_arg=3) async def infraction_edit( self, ctx: Context, -- cgit v1.2.3 From 61feabb68feaf862e79d9a186c8346e961471183 Mon Sep 17 00:00:00 2001 From: Shakya Majumdar Date: Fri, 18 Mar 2022 10:35:51 +0530 Subject: try-except to prevent a TypeError instead of `if isinstance` --- bot/decorators.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bot/decorators.py b/bot/decorators.py index a0390fc32..8971898b3 100644 --- a/bot/decorators.py +++ b/bot/decorators.py @@ -3,7 +3,6 @@ import functools import types import typing as t from contextlib import suppress -from datetime import datetime import arrow from discord import Member, NotFound @@ -259,7 +258,11 @@ def ensure_future_timestamp(timestamp_arg: function.Argument) -> t.Callable: ctx = function.get_arg_value(1, bound_args) - if isinstance(target, datetime) and target < arrow.utcnow(): + 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 -- cgit v1.2.3 From ecbed8f022c79a4afacdd6ea0ebb5fddcff5e0fb Mon Sep 17 00:00:00 2001 From: Shakya Majumdar Date: Fri, 18 Mar 2022 11:55:27 +0530 Subject: apply decorator on superstarify too --- bot/exts/moderation/infraction/superstarify.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index 3f1bffd76..af676a0de 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 @@ -111,6 +112,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, -- cgit v1.2.3