From e06f496a6e3f9a9d6cfaeb3902547aa9da1dd7c1 Mon Sep 17 00:00:00 2001 From: mbaruh Date: Wed, 14 Apr 2021 20:05:04 +0300 Subject: Add Duty cog and new Moderators role Added a cog to allow moderators to go off and on duty. The off-duty state is cached via a redis cache, and its expiry is scheduled via the Scheduler. Additionally changes which roles are pinged on mod alerts. --- bot/constants.py | 1 + bot/exts/moderation/duty.py | 135 ++++++++++++++++++++++++++++++++++++++++++ bot/exts/moderation/modlog.py | 6 +- config-default.yml | 5 +- 4 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 bot/exts/moderation/duty.py diff --git a/bot/constants.py b/bot/constants.py index 6d14bbb3a..cc3aa41a5 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -491,6 +491,7 @@ class Roles(metaclass=YAMLGetter): domain_leads: int helpers: int moderators: int + mod_team: int owners: int project_leads: int diff --git a/bot/exts/moderation/duty.py b/bot/exts/moderation/duty.py new file mode 100644 index 000000000..13be016f2 --- /dev/null +++ b/bot/exts/moderation/duty.py @@ -0,0 +1,135 @@ +import datetime +import logging + +from async_rediscache import RedisCache +from dateutil.parser import isoparse +from discord import Member +from discord.ext.commands import Cog, Context, group, has_any_role + +from bot.bot import Bot +from bot.constants import Emojis, Guild, MODERATION_ROLES, Roles +from bot.converters import Expiry +from bot.utils.scheduling import Scheduler + + +log = logging.getLogger(__name__) + + +class Duty(Cog): + """Commands for a moderator to go on and off duty.""" + + # RedisCache[str, str] + # The cache's keys are mods who are off-duty. + # The cache's values are the times when the role should be re-applied to them, stored in ISO format. + off_duty_mods = RedisCache() + + def __init__(self, bot: Bot): + self.bot = bot + self._role_scheduler = Scheduler(self.__class__.__name__) + + self.guild = None + self.moderators_role = None + + self.bot.loop.create_task(self.reschedule_roles()) + + async def reschedule_roles(self) -> None: + """Reschedule moderators role re-apply times.""" + await self.bot.wait_until_guild_available() + self.guild = self.bot.get_guild(Guild.id) + self.moderators_role = self.guild.get_role(Roles.moderators) + + mod_team = self.guild.get_role(Roles.mod_team) + on_duty = self.moderators_role.members + off_duty = await self.off_duty_mods.to_dict() + + log.trace("Applying the moderators role to the mod team where necessary.") + for mod in mod_team.members: + if mod in on_duty: # Make sure that on-duty mods aren't in the cache. + if mod in off_duty: + await self.off_duty_mods.delete(mod.id) + continue + + # Keep the role off only for those in the cache. + if mod.id not in off_duty: + await self.reapply_role(mod) + else: + expiry = isoparse(off_duty[mod.id]).replace(tzinfo=None) + self._role_scheduler.schedule_at(expiry, mod.id, self.reapply_role(mod)) + + async def reapply_role(self, mod: Member) -> None: + """Reapply the moderator's role to the given moderator.""" + log.trace(f"Re-applying role to mod with ID {mod.id}.") + await mod.add_roles(self.moderators_role, reason="Off-duty period expired.") + + @group(name='duty', invoke_without_command=True) + @has_any_role(*MODERATION_ROLES) + async def duty_group(self, ctx: Context) -> None: + """Allow the removal and re-addition of the pingable moderators role.""" + await ctx.send_help(ctx.command) + + @duty_group.command(name='off') + @has_any_role(*MODERATION_ROLES) + async def off_command(self, ctx: Context, duration: Expiry) -> None: + """ + Temporarily removes the pingable moderators role for a set amount of time. + + A unit of time should be appended to the duration. + Units (∗case-sensitive): + \u2003`y` - years + \u2003`m` - months∗ + \u2003`w` - weeks + \u2003`d` - days + \u2003`h` - hours + \u2003`M` - minutes∗ + \u2003`s` - seconds + + Alternatively, an ISO 8601 timestamp can be provided for the duration. + + The duration cannot be longer than 30 days. + """ + duration: datetime.datetime + delta = duration - datetime.datetime.utcnow() + if delta > datetime.timedelta(days=30): + await ctx.send(":x: Cannot remove the role for longer than 30 days.") + return + + mod = ctx.author + + await mod.remove_roles(self.moderators_role, reason="Entered off-duty period.") + + await self.off_duty_mods.update({mod.id: duration.isoformat()}) + + if mod.id in self._role_scheduler: + self._role_scheduler.cancel(mod.id) + self._role_scheduler.schedule_at(duration, mod.id, self.reapply_role(mod)) + + until_date = duration.replace(microsecond=0).isoformat() + await ctx.send(f"{Emojis.check_mark} Moderators role has been removed until {until_date}.") + + @duty_group.command(name='on') + @has_any_role(*MODERATION_ROLES) + async def on_command(self, ctx: Context) -> None: + """Re-apply the pingable moderators role.""" + mod = ctx.author + if mod in self.moderators_role.members: + await ctx.send(":question: You already have the role.") + return + + await mod.add_roles(self.moderators_role, reason="Off-duty period canceled.") + + await self.off_duty_mods.delete(mod.id) + + if mod.id in self._role_scheduler: + self._role_scheduler.cancel(mod.id) + + await ctx.send(f"{Emojis.check_mark} Moderators role has been re-applied.") + + def cog_unload(self) -> None: + """Cancel role tasks when the cog unloads.""" + log.trace("Cog unload: canceling role tasks.") + self._role_scheduler.cancel_all() + + +def setup(bot: Bot) -> None: + """Load the Slowmode cog.""" + bot.add_cog(Duty(bot)) diff --git a/bot/exts/moderation/modlog.py b/bot/exts/moderation/modlog.py index 2dae9d268..f68a1880e 100644 --- a/bot/exts/moderation/modlog.py +++ b/bot/exts/moderation/modlog.py @@ -14,7 +14,7 @@ from discord.abc import GuildChannel from discord.ext.commands import Cog, Context from bot.bot import Bot -from bot.constants import Categories, Channels, Colours, Emojis, Event, Guild as GuildConstant, Icons, URLs +from bot.constants import Categories, Channels, Colours, Emojis, Event, Guild as GuildConstant, Icons, Roles, URLs from bot.utils.messages import format_user from bot.utils.time import humanize_delta @@ -115,9 +115,9 @@ class ModLog(Cog, name="ModLog"): if ping_everyone: if content: - content = f"@everyone\n{content}" + content = f"<@&{Roles.moderators}> @here\n{content}" else: - content = "@everyone" + content = f"<@&{Roles.moderators}> @here" # Truncate content to 2000 characters and append an ellipsis. if content and len(content) > 2000: diff --git a/config-default.yml b/config-default.yml index 8c6e18470..6eb954cd5 100644 --- a/config-default.yml +++ b/config-default.yml @@ -260,7 +260,8 @@ guild: devops: 409416496733880320 domain_leads: 807415650778742785 helpers: &HELPERS_ROLE 267630620367257601 - moderators: &MODS_ROLE 267629731250176001 + moderators: &MODS_ROLE 831776746206265384 + mod_team: &MOD_TEAM_ROLE 267629731250176001 owners: &OWNERS_ROLE 267627879762755584 project_leads: 815701647526330398 @@ -274,12 +275,14 @@ guild: moderation_roles: - *ADMINS_ROLE - *MODS_ROLE + - *MOD_TEAM_ROLE - *OWNERS_ROLE staff_roles: - *ADMINS_ROLE - *HELPERS_ROLE - *MODS_ROLE + - *MOD_TEAM_ROLE - *OWNERS_ROLE webhooks: -- cgit v1.2.3 From 65df8e24874cda7b9525acde346199f66e59650f Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Thu, 15 Apr 2021 00:55:29 +0300 Subject: Remove extra newline Co-authored-by: ks129 <45097959+ks129@users.noreply.github.com> --- bot/exts/moderation/duty.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bot/exts/moderation/duty.py b/bot/exts/moderation/duty.py index 13be016f2..94eed9331 100644 --- a/bot/exts/moderation/duty.py +++ b/bot/exts/moderation/duty.py @@ -11,7 +11,6 @@ from bot.constants import Emojis, Guild, MODERATION_ROLES, Roles from bot.converters import Expiry from bot.utils.scheduling import Scheduler - log = logging.getLogger(__name__) -- cgit v1.2.3 From 38714aef8c5b71c5e8313a82bef18947f1f1395a Mon Sep 17 00:00:00 2001 From: mbaruh Date: Thu, 15 Apr 2021 01:00:31 +0300 Subject: Fix setup docstring to specify correct cog --- bot/exts/moderation/duty.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/duty.py b/bot/exts/moderation/duty.py index 94eed9331..3f34e366c 100644 --- a/bot/exts/moderation/duty.py +++ b/bot/exts/moderation/duty.py @@ -130,5 +130,5 @@ class Duty(Cog): def setup(bot: Bot) -> None: - """Load the Slowmode cog.""" + """Load the Duty cog.""" bot.add_cog(Duty(bot)) -- cgit v1.2.3 From 6c00f74c8dcd2f3f1aaa4eff89e72cc135b75357 Mon Sep 17 00:00:00 2001 From: mbaruh Date: Thu, 15 Apr 2021 01:04:16 +0300 Subject: Add off-duty expiration date to audit log --- bot/exts/moderation/duty.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/exts/moderation/duty.py b/bot/exts/moderation/duty.py index 3f34e366c..265261be8 100644 --- a/bot/exts/moderation/duty.py +++ b/bot/exts/moderation/duty.py @@ -94,7 +94,8 @@ class Duty(Cog): mod = ctx.author - await mod.remove_roles(self.moderators_role, reason="Entered off-duty period.") + until_date = duration.replace(microsecond=0).isoformat() + await mod.remove_roles(self.moderators_role, reason=f"Entered off-duty period until {until_date}.") await self.off_duty_mods.update({mod.id: duration.isoformat()}) @@ -102,7 +103,6 @@ class Duty(Cog): self._role_scheduler.cancel(mod.id) self._role_scheduler.schedule_at(duration, mod.id, self.reapply_role(mod)) - until_date = duration.replace(microsecond=0).isoformat() await ctx.send(f"{Emojis.check_mark} Moderators role has been removed until {until_date}.") @duty_group.command(name='on') -- cgit v1.2.3 From b5fbca6f32c437aa45e28916451de39fb1485a75 Mon Sep 17 00:00:00 2001 From: mbaruh Date: Thu, 15 Apr 2021 01:10:37 +0300 Subject: Use set instead of update in duty off --- bot/exts/moderation/duty.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/duty.py b/bot/exts/moderation/duty.py index 265261be8..0b07510db 100644 --- a/bot/exts/moderation/duty.py +++ b/bot/exts/moderation/duty.py @@ -97,7 +97,7 @@ class Duty(Cog): until_date = duration.replace(microsecond=0).isoformat() await mod.remove_roles(self.moderators_role, reason=f"Entered off-duty period until {until_date}.") - await self.off_duty_mods.update({mod.id: duration.isoformat()}) + await self.off_duty_mods.set(mod.id, duration.isoformat()) if mod.id in self._role_scheduler: self._role_scheduler.cancel(mod.id) -- cgit v1.2.3 From f11ebfde17634eed7fa242f72b309c4a75c885cd Mon Sep 17 00:00:00 2001 From: mbaruh Date: Sat, 17 Apr 2021 01:15:38 +0300 Subject: Keep config succint A moderator is expected to have the mod-team role and therefore it's enough to specify the latter in the mod and staff roles. --- config-default.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/config-default.yml b/config-default.yml index 6eb954cd5..b19164d3f 100644 --- a/config-default.yml +++ b/config-default.yml @@ -274,14 +274,12 @@ guild: moderation_roles: - *ADMINS_ROLE - - *MODS_ROLE - *MOD_TEAM_ROLE - *OWNERS_ROLE staff_roles: - *ADMINS_ROLE - *HELPERS_ROLE - - *MODS_ROLE - *MOD_TEAM_ROLE - *OWNERS_ROLE -- cgit v1.2.3 From 2053b2e36ece02680ed85b970c4fbf687fe07e0f Mon Sep 17 00:00:00 2001 From: mbaruh Date: Sat, 17 Apr 2021 01:19:54 +0300 Subject: Assume a scheduled task exists for `duty on` The lack of such a task may be indicative of a bug. --- bot/exts/moderation/duty.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bot/exts/moderation/duty.py b/bot/exts/moderation/duty.py index 0b07510db..8d0c96363 100644 --- a/bot/exts/moderation/duty.py +++ b/bot/exts/moderation/duty.py @@ -118,8 +118,7 @@ class Duty(Cog): await self.off_duty_mods.delete(mod.id) - if mod.id in self._role_scheduler: - self._role_scheduler.cancel(mod.id) + self._role_scheduler.cancel(mod.id) await ctx.send(f"{Emojis.check_mark} Moderators role has been re-applied.") -- cgit v1.2.3 From 5506fb74f90831e686f4636595f62e4bcc72a703 Mon Sep 17 00:00:00 2001 From: mbaruh Date: Sat, 17 Apr 2021 01:24:17 +0300 Subject: Improve documentation --- bot/exts/moderation/duty.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bot/exts/moderation/duty.py b/bot/exts/moderation/duty.py index 8d0c96363..eab0fd99f 100644 --- a/bot/exts/moderation/duty.py +++ b/bot/exts/moderation/duty.py @@ -94,11 +94,12 @@ class Duty(Cog): mod = ctx.author - until_date = duration.replace(microsecond=0).isoformat() + until_date = duration.replace(microsecond=0).isoformat() # Looks noisy with microseconds. await mod.remove_roles(self.moderators_role, reason=f"Entered off-duty period until {until_date}.") await self.off_duty_mods.set(mod.id, duration.isoformat()) + # Allow rescheduling the task without cancelling it separately via the `on` command. if mod.id in self._role_scheduler: self._role_scheduler.cancel(mod.id) self._role_scheduler.schedule_at(duration, mod.id, self.reapply_role(mod)) @@ -118,6 +119,7 @@ class Duty(Cog): await self.off_duty_mods.delete(mod.id) + # We assume the task exists. Lack of it may indicate a bug. self._role_scheduler.cancel(mod.id) await ctx.send(f"{Emojis.check_mark} Moderators role has been re-applied.") -- cgit v1.2.3 From 4a051cdb016748daca724e95957bd011cc3f6c3f Mon Sep 17 00:00:00 2001 From: mbaruh Date: Sat, 17 Apr 2021 01:43:17 +0300 Subject: Name the rescheduling task, and cancel it on cog unload --- bot/exts/moderation/duty.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/exts/moderation/duty.py b/bot/exts/moderation/duty.py index eab0fd99f..e05472448 100644 --- a/bot/exts/moderation/duty.py +++ b/bot/exts/moderation/duty.py @@ -29,7 +29,7 @@ class Duty(Cog): self.guild = None self.moderators_role = None - self.bot.loop.create_task(self.reschedule_roles()) + self.reschedule_task = self.bot.loop.create_task(self.reschedule_roles(), name="duty-reschedule") async def reschedule_roles(self) -> None: """Reschedule moderators role re-apply times.""" @@ -127,6 +127,7 @@ class Duty(Cog): def cog_unload(self) -> None: """Cancel role tasks when the cog unloads.""" log.trace("Cog unload: canceling role tasks.") + self.reschedule_task.cancel() self._role_scheduler.cancel_all() -- cgit v1.2.3 From d2d939c96de22ae174072dd8cc2bad2fe4f2174a Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Sat, 17 Apr 2021 13:19:08 +0300 Subject: Remove here ping Kinda defeats the purpose of being off-duty. --- bot/exts/moderation/modlog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/exts/moderation/modlog.py b/bot/exts/moderation/modlog.py index f68a1880e..5e8ea595b 100644 --- a/bot/exts/moderation/modlog.py +++ b/bot/exts/moderation/modlog.py @@ -115,9 +115,9 @@ class ModLog(Cog, name="ModLog"): if ping_everyone: if content: - content = f"<@&{Roles.moderators}> @here\n{content}" + content = f"<@&{Roles.moderators}>\n{content}" else: - content = f"<@&{Roles.moderators}> @here" + content = f"<@&{Roles.moderators}>" # Truncate content to 2000 characters and append an ellipsis. if content and len(content) > 2000: -- cgit v1.2.3 From 94af3c07678f1f2dee722f4780a816426efd0851 Mon Sep 17 00:00:00 2001 From: Vivaan Verma Date: Sun, 18 Apr 2021 21:12:08 +0100 Subject: Added default duration of 1h to superstarify --- bot/exts/moderation/infraction/superstarify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index 704dddf9c..245f14905 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -109,7 +109,7 @@ class Superstarify(InfractionScheduler, Cog): self, ctx: Context, member: Member, - duration: Expiry, + duration: Expiry = "1h", *, reason: str = '', ) -> None: -- cgit v1.2.3 From 3126e00a28e498afc8ecef1ed87b356f0e4a38c4 Mon Sep 17 00:00:00 2001 From: Vivaan Verma Date: Sun, 18 Apr 2021 22:11:46 +0100 Subject: Make duration an optional arg and default it to 1 hour --- bot/exts/moderation/infraction/superstarify.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index 245f14905..8a6d14d41 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -1,3 +1,4 @@ +import datetime import json import logging import random @@ -109,7 +110,7 @@ class Superstarify(InfractionScheduler, Cog): self, ctx: Context, member: Member, - duration: Expiry = "1h", + duration: t.Optional[Expiry], *, reason: str = '', ) -> None: @@ -134,6 +135,9 @@ class Superstarify(InfractionScheduler, Cog): if await _utils.get_active_infraction(ctx, member, "superstar"): return + # Set the duration to 1 hour if none was provided + duration = datetime.datetime.now() + datetime.timedelta(hours=1) + # Post the infraction to the API old_nick = member.display_name infraction_reason = f'Old nickname: {old_nick}. {reason}' -- cgit v1.2.3 From 7fc5e37ecd2e1589b77b7fa16af26ee42e72dcdc Mon Sep 17 00:00:00 2001 From: Vivaan Verma Date: Sun, 18 Apr 2021 22:17:27 +0100 Subject: Check if a duration was provided --- bot/exts/moderation/infraction/superstarify.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index 8a6d14d41..f5d6259cd 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -136,8 +136,9 @@ class Superstarify(InfractionScheduler, Cog): return # Set the duration to 1 hour if none was provided - duration = datetime.datetime.now() + datetime.timedelta(hours=1) - + if not duration: + duration = datetime.datetime.now() + datetime.timedelta(hours=1) + # Post the infraction to the API old_nick = member.display_name infraction_reason = f'Old nickname: {old_nick}. {reason}' -- cgit v1.2.3 From 6169ed2b73a5f2d763a2758e69ba4983127a1373 Mon Sep 17 00:00:00 2001 From: Vivaan Verma <54081925+doublevcodes@users.noreply.github.com> Date: Sun, 18 Apr 2021 22:31:40 +0100 Subject: Fix linting errors --- bot/exts/moderation/infraction/superstarify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index f5d6259cd..6fa0d550f 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -138,7 +138,7 @@ class Superstarify(InfractionScheduler, Cog): # Set the duration to 1 hour if none was provided if not duration: duration = datetime.datetime.now() + datetime.timedelta(hours=1) - + # Post the infraction to the API old_nick = member.display_name infraction_reason = f'Old nickname: {old_nick}. {reason}' -- cgit v1.2.3 From bd54449e8994c38b2fd073056f82e6c52785d4c6 Mon Sep 17 00:00:00 2001 From: mbaruh Date: Mon, 19 Apr 2021 15:43:33 +0300 Subject: Renamed Duty cog to Modpings The renaming includes the commands inside it. --- bot/exts/moderation/duty.py | 46 ++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/bot/exts/moderation/duty.py b/bot/exts/moderation/duty.py index e05472448..c351db615 100644 --- a/bot/exts/moderation/duty.py +++ b/bot/exts/moderation/duty.py @@ -14,13 +14,13 @@ from bot.utils.scheduling import Scheduler log = logging.getLogger(__name__) -class Duty(Cog): - """Commands for a moderator to go on and off duty.""" +class Modpings(Cog): + """Commands for a moderator to turn moderator pings on and off.""" # RedisCache[str, str] - # The cache's keys are mods who are off-duty. + # The cache's keys are mods who have pings off. # The cache's values are the times when the role should be re-applied to them, stored in ISO format. - off_duty_mods = RedisCache() + pings_off_mods = RedisCache() def __init__(self, bot: Bot): self.bot = bot @@ -29,7 +29,7 @@ class Duty(Cog): self.guild = None self.moderators_role = None - self.reschedule_task = self.bot.loop.create_task(self.reschedule_roles(), name="duty-reschedule") + self.reschedule_task = self.bot.loop.create_task(self.reschedule_roles(), name="mod-pings-reschedule") async def reschedule_roles(self) -> None: """Reschedule moderators role re-apply times.""" @@ -38,35 +38,35 @@ class Duty(Cog): self.moderators_role = self.guild.get_role(Roles.moderators) mod_team = self.guild.get_role(Roles.mod_team) - on_duty = self.moderators_role.members - off_duty = await self.off_duty_mods.to_dict() + pings_on = self.moderators_role.members + pings_off = await self.pings_off_mods.to_dict() log.trace("Applying the moderators role to the mod team where necessary.") for mod in mod_team.members: - if mod in on_duty: # Make sure that on-duty mods aren't in the cache. - if mod in off_duty: - await self.off_duty_mods.delete(mod.id) + if mod in pings_on: # Make sure that on-duty mods aren't in the cache. + if mod in pings_off: + await self.pings_off_mods.delete(mod.id) continue # Keep the role off only for those in the cache. - if mod.id not in off_duty: + if mod.id not in pings_off: await self.reapply_role(mod) else: - expiry = isoparse(off_duty[mod.id]).replace(tzinfo=None) + expiry = isoparse(pings_off[mod.id]).replace(tzinfo=None) self._role_scheduler.schedule_at(expiry, mod.id, self.reapply_role(mod)) async def reapply_role(self, mod: Member) -> None: """Reapply the moderator's role to the given moderator.""" log.trace(f"Re-applying role to mod with ID {mod.id}.") - await mod.add_roles(self.moderators_role, reason="Off-duty period expired.") + await mod.add_roles(self.moderators_role, reason="Pings off period expired.") - @group(name='duty', invoke_without_command=True) + @group(name='modpings', aliases=('modping',), invoke_without_command=True) @has_any_role(*MODERATION_ROLES) - async def duty_group(self, ctx: Context) -> None: + async def modpings_group(self, ctx: Context) -> None: """Allow the removal and re-addition of the pingable moderators role.""" await ctx.send_help(ctx.command) - @duty_group.command(name='off') + @modpings_group.command(name='off') @has_any_role(*MODERATION_ROLES) async def off_command(self, ctx: Context, duration: Expiry) -> None: """ @@ -95,9 +95,9 @@ class Duty(Cog): mod = ctx.author until_date = duration.replace(microsecond=0).isoformat() # Looks noisy with microseconds. - await mod.remove_roles(self.moderators_role, reason=f"Entered off-duty period until {until_date}.") + await mod.remove_roles(self.moderators_role, reason=f"Turned pings off until {until_date}.") - await self.off_duty_mods.set(mod.id, duration.isoformat()) + await self.pings_off_mods.set(mod.id, duration.isoformat()) # Allow rescheduling the task without cancelling it separately via the `on` command. if mod.id in self._role_scheduler: @@ -106,7 +106,7 @@ class Duty(Cog): await ctx.send(f"{Emojis.check_mark} Moderators role has been removed until {until_date}.") - @duty_group.command(name='on') + @modpings_group.command(name='on') @has_any_role(*MODERATION_ROLES) async def on_command(self, ctx: Context) -> None: """Re-apply the pingable moderators role.""" @@ -115,9 +115,9 @@ class Duty(Cog): await ctx.send(":question: You already have the role.") return - await mod.add_roles(self.moderators_role, reason="Off-duty period canceled.") + await mod.add_roles(self.moderators_role, reason="Pings off period canceled.") - await self.off_duty_mods.delete(mod.id) + await self.pings_off_mods.delete(mod.id) # We assume the task exists. Lack of it may indicate a bug. self._role_scheduler.cancel(mod.id) @@ -132,5 +132,5 @@ class Duty(Cog): def setup(bot: Bot) -> None: - """Load the Duty cog.""" - bot.add_cog(Duty(bot)) + """Load the Modpings cog.""" + bot.add_cog(Modpings(bot)) -- cgit v1.2.3 From e30667fb4e23648c3f308bfc06cf643852d0c29c Mon Sep 17 00:00:00 2001 From: mbaruh Date: Mon, 19 Apr 2021 15:44:58 +0300 Subject: Renamed duty.py to modpings.py --- bot/exts/moderation/duty.py | 136 ---------------------------------------- bot/exts/moderation/modpings.py | 136 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 136 deletions(-) delete mode 100644 bot/exts/moderation/duty.py create mode 100644 bot/exts/moderation/modpings.py diff --git a/bot/exts/moderation/duty.py b/bot/exts/moderation/duty.py deleted file mode 100644 index c351db615..000000000 --- a/bot/exts/moderation/duty.py +++ /dev/null @@ -1,136 +0,0 @@ -import datetime -import logging - -from async_rediscache import RedisCache -from dateutil.parser import isoparse -from discord import Member -from discord.ext.commands import Cog, Context, group, has_any_role - -from bot.bot import Bot -from bot.constants import Emojis, Guild, MODERATION_ROLES, Roles -from bot.converters import Expiry -from bot.utils.scheduling import Scheduler - -log = logging.getLogger(__name__) - - -class Modpings(Cog): - """Commands for a moderator to turn moderator pings on and off.""" - - # RedisCache[str, str] - # The cache's keys are mods who have pings off. - # The cache's values are the times when the role should be re-applied to them, stored in ISO format. - pings_off_mods = RedisCache() - - def __init__(self, bot: Bot): - self.bot = bot - self._role_scheduler = Scheduler(self.__class__.__name__) - - self.guild = None - self.moderators_role = None - - self.reschedule_task = self.bot.loop.create_task(self.reschedule_roles(), name="mod-pings-reschedule") - - async def reschedule_roles(self) -> None: - """Reschedule moderators role re-apply times.""" - await self.bot.wait_until_guild_available() - self.guild = self.bot.get_guild(Guild.id) - self.moderators_role = self.guild.get_role(Roles.moderators) - - mod_team = self.guild.get_role(Roles.mod_team) - pings_on = self.moderators_role.members - pings_off = await self.pings_off_mods.to_dict() - - log.trace("Applying the moderators role to the mod team where necessary.") - for mod in mod_team.members: - if mod in pings_on: # Make sure that on-duty mods aren't in the cache. - if mod in pings_off: - await self.pings_off_mods.delete(mod.id) - continue - - # Keep the role off only for those in the cache. - if mod.id not in pings_off: - await self.reapply_role(mod) - else: - expiry = isoparse(pings_off[mod.id]).replace(tzinfo=None) - self._role_scheduler.schedule_at(expiry, mod.id, self.reapply_role(mod)) - - async def reapply_role(self, mod: Member) -> None: - """Reapply the moderator's role to the given moderator.""" - log.trace(f"Re-applying role to mod with ID {mod.id}.") - await mod.add_roles(self.moderators_role, reason="Pings off period expired.") - - @group(name='modpings', aliases=('modping',), invoke_without_command=True) - @has_any_role(*MODERATION_ROLES) - async def modpings_group(self, ctx: Context) -> None: - """Allow the removal and re-addition of the pingable moderators role.""" - await ctx.send_help(ctx.command) - - @modpings_group.command(name='off') - @has_any_role(*MODERATION_ROLES) - async def off_command(self, ctx: Context, duration: Expiry) -> None: - """ - Temporarily removes the pingable moderators role for a set amount of time. - - A unit of time should be appended to the duration. - Units (∗case-sensitive): - \u2003`y` - years - \u2003`m` - months∗ - \u2003`w` - weeks - \u2003`d` - days - \u2003`h` - hours - \u2003`M` - minutes∗ - \u2003`s` - seconds - - Alternatively, an ISO 8601 timestamp can be provided for the duration. - - The duration cannot be longer than 30 days. - """ - duration: datetime.datetime - delta = duration - datetime.datetime.utcnow() - if delta > datetime.timedelta(days=30): - await ctx.send(":x: Cannot remove the role for longer than 30 days.") - return - - mod = ctx.author - - until_date = duration.replace(microsecond=0).isoformat() # Looks noisy with microseconds. - await mod.remove_roles(self.moderators_role, reason=f"Turned pings off until {until_date}.") - - await self.pings_off_mods.set(mod.id, duration.isoformat()) - - # Allow rescheduling the task without cancelling it separately via the `on` command. - if mod.id in self._role_scheduler: - self._role_scheduler.cancel(mod.id) - self._role_scheduler.schedule_at(duration, mod.id, self.reapply_role(mod)) - - await ctx.send(f"{Emojis.check_mark} Moderators role has been removed until {until_date}.") - - @modpings_group.command(name='on') - @has_any_role(*MODERATION_ROLES) - async def on_command(self, ctx: Context) -> None: - """Re-apply the pingable moderators role.""" - mod = ctx.author - if mod in self.moderators_role.members: - await ctx.send(":question: You already have the role.") - return - - await mod.add_roles(self.moderators_role, reason="Pings off period canceled.") - - await self.pings_off_mods.delete(mod.id) - - # We assume the task exists. Lack of it may indicate a bug. - self._role_scheduler.cancel(mod.id) - - await ctx.send(f"{Emojis.check_mark} Moderators role has been re-applied.") - - def cog_unload(self) -> None: - """Cancel role tasks when the cog unloads.""" - log.trace("Cog unload: canceling role tasks.") - self.reschedule_task.cancel() - self._role_scheduler.cancel_all() - - -def setup(bot: Bot) -> None: - """Load the Modpings cog.""" - bot.add_cog(Modpings(bot)) diff --git a/bot/exts/moderation/modpings.py b/bot/exts/moderation/modpings.py new file mode 100644 index 000000000..c351db615 --- /dev/null +++ b/bot/exts/moderation/modpings.py @@ -0,0 +1,136 @@ +import datetime +import logging + +from async_rediscache import RedisCache +from dateutil.parser import isoparse +from discord import Member +from discord.ext.commands import Cog, Context, group, has_any_role + +from bot.bot import Bot +from bot.constants import Emojis, Guild, MODERATION_ROLES, Roles +from bot.converters import Expiry +from bot.utils.scheduling import Scheduler + +log = logging.getLogger(__name__) + + +class Modpings(Cog): + """Commands for a moderator to turn moderator pings on and off.""" + + # RedisCache[str, str] + # The cache's keys are mods who have pings off. + # The cache's values are the times when the role should be re-applied to them, stored in ISO format. + pings_off_mods = RedisCache() + + def __init__(self, bot: Bot): + self.bot = bot + self._role_scheduler = Scheduler(self.__class__.__name__) + + self.guild = None + self.moderators_role = None + + self.reschedule_task = self.bot.loop.create_task(self.reschedule_roles(), name="mod-pings-reschedule") + + async def reschedule_roles(self) -> None: + """Reschedule moderators role re-apply times.""" + await self.bot.wait_until_guild_available() + self.guild = self.bot.get_guild(Guild.id) + self.moderators_role = self.guild.get_role(Roles.moderators) + + mod_team = self.guild.get_role(Roles.mod_team) + pings_on = self.moderators_role.members + pings_off = await self.pings_off_mods.to_dict() + + log.trace("Applying the moderators role to the mod team where necessary.") + for mod in mod_team.members: + if mod in pings_on: # Make sure that on-duty mods aren't in the cache. + if mod in pings_off: + await self.pings_off_mods.delete(mod.id) + continue + + # Keep the role off only for those in the cache. + if mod.id not in pings_off: + await self.reapply_role(mod) + else: + expiry = isoparse(pings_off[mod.id]).replace(tzinfo=None) + self._role_scheduler.schedule_at(expiry, mod.id, self.reapply_role(mod)) + + async def reapply_role(self, mod: Member) -> None: + """Reapply the moderator's role to the given moderator.""" + log.trace(f"Re-applying role to mod with ID {mod.id}.") + await mod.add_roles(self.moderators_role, reason="Pings off period expired.") + + @group(name='modpings', aliases=('modping',), invoke_without_command=True) + @has_any_role(*MODERATION_ROLES) + async def modpings_group(self, ctx: Context) -> None: + """Allow the removal and re-addition of the pingable moderators role.""" + await ctx.send_help(ctx.command) + + @modpings_group.command(name='off') + @has_any_role(*MODERATION_ROLES) + async def off_command(self, ctx: Context, duration: Expiry) -> None: + """ + Temporarily removes the pingable moderators role for a set amount of time. + + A unit of time should be appended to the duration. + Units (∗case-sensitive): + \u2003`y` - years + \u2003`m` - months∗ + \u2003`w` - weeks + \u2003`d` - days + \u2003`h` - hours + \u2003`M` - minutes∗ + \u2003`s` - seconds + + Alternatively, an ISO 8601 timestamp can be provided for the duration. + + The duration cannot be longer than 30 days. + """ + duration: datetime.datetime + delta = duration - datetime.datetime.utcnow() + if delta > datetime.timedelta(days=30): + await ctx.send(":x: Cannot remove the role for longer than 30 days.") + return + + mod = ctx.author + + until_date = duration.replace(microsecond=0).isoformat() # Looks noisy with microseconds. + await mod.remove_roles(self.moderators_role, reason=f"Turned pings off until {until_date}.") + + await self.pings_off_mods.set(mod.id, duration.isoformat()) + + # Allow rescheduling the task without cancelling it separately via the `on` command. + if mod.id in self._role_scheduler: + self._role_scheduler.cancel(mod.id) + self._role_scheduler.schedule_at(duration, mod.id, self.reapply_role(mod)) + + await ctx.send(f"{Emojis.check_mark} Moderators role has been removed until {until_date}.") + + @modpings_group.command(name='on') + @has_any_role(*MODERATION_ROLES) + async def on_command(self, ctx: Context) -> None: + """Re-apply the pingable moderators role.""" + mod = ctx.author + if mod in self.moderators_role.members: + await ctx.send(":question: You already have the role.") + return + + await mod.add_roles(self.moderators_role, reason="Pings off period canceled.") + + await self.pings_off_mods.delete(mod.id) + + # We assume the task exists. Lack of it may indicate a bug. + self._role_scheduler.cancel(mod.id) + + await ctx.send(f"{Emojis.check_mark} Moderators role has been re-applied.") + + def cog_unload(self) -> None: + """Cancel role tasks when the cog unloads.""" + log.trace("Cog unload: canceling role tasks.") + self.reschedule_task.cancel() + self._role_scheduler.cancel_all() + + +def setup(bot: Bot) -> None: + """Load the Modpings cog.""" + bot.add_cog(Modpings(bot)) -- cgit v1.2.3 From cb253750a5597d8ca63e8742307bafc096c7e189 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 19 Apr 2021 18:05:11 +0100 Subject: Require a mod role for stream commands Previously any staff member (including helpers) could use the stream commands. --- bot/exts/moderation/stream.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bot/exts/moderation/stream.py b/bot/exts/moderation/stream.py index 12e195172..7ea7f635b 100644 --- a/bot/exts/moderation/stream.py +++ b/bot/exts/moderation/stream.py @@ -8,7 +8,7 @@ from async_rediscache import RedisCache from discord.ext import commands from bot.bot import Bot -from bot.constants import Colours, Emojis, Guild, Roles, STAFF_ROLES, VideoPermission +from bot.constants import Colours, Emojis, Guild, MODERATION_ROLES, Roles, VideoPermission from bot.converters import Expiry from bot.utils.scheduling import Scheduler from bot.utils.time import format_infraction_with_duration @@ -69,7 +69,7 @@ class Stream(commands.Cog): ) @commands.command(aliases=("streaming",)) - @commands.has_any_role(*STAFF_ROLES) + @commands.has_any_role(*MODERATION_ROLES) async def stream(self, ctx: commands.Context, member: discord.Member, duration: Expiry = None) -> None: """ Temporarily grant streaming permissions to a member for a given duration. @@ -126,7 +126,7 @@ class Stream(commands.Cog): log.debug(f"Successfully gave {member} ({member.id}) permission to stream until {revoke_time}.") @commands.command(aliases=("pstream",)) - @commands.has_any_role(*STAFF_ROLES) + @commands.has_any_role(*MODERATION_ROLES) async def permanentstream(self, ctx: commands.Context, member: discord.Member) -> None: """Permanently grants the given member the permission to stream.""" log.trace(f"Attempting to give permanent streaming permission to {member} ({member.id}).") @@ -153,7 +153,7 @@ class Stream(commands.Cog): log.debug(f"Successfully gave {member} ({member.id}) permanent streaming permission.") @commands.command(aliases=("unstream", "rstream")) - @commands.has_any_role(*STAFF_ROLES) + @commands.has_any_role(*MODERATION_ROLES) async def revokestream(self, ctx: commands.Context, member: discord.Member) -> None: """Revoke the permission to stream from the given member.""" log.trace(f"Attempting to remove streaming permission from {member} ({member.id}).") -- cgit v1.2.3 From 90ed28f4cb31b5b41f7a395abfe61f4f9e49e091 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 19 Apr 2021 18:08:39 +0100 Subject: Add command to list users with streaming perms This is useful to audit users who still have the permission to stream. I have chosen to also sort and paginate the embed to make it easier to read. The sorting is based on how long until the user's streaming permissions are revoked, with permanent streamers at the end. --- bot/exts/moderation/stream.py | 44 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/bot/exts/moderation/stream.py b/bot/exts/moderation/stream.py index 7ea7f635b..5f3820748 100644 --- a/bot/exts/moderation/stream.py +++ b/bot/exts/moderation/stream.py @@ -1,5 +1,6 @@ import logging from datetime import timedelta, timezone +from operator import itemgetter import arrow import discord @@ -8,8 +9,9 @@ from async_rediscache import RedisCache from discord.ext import commands from bot.bot import Bot -from bot.constants import Colours, Emojis, Guild, MODERATION_ROLES, Roles, VideoPermission +from bot.constants import Colours, Emojis, Guild, MODERATION_ROLES, Roles, STAFF_ROLES, VideoPermission from bot.converters import Expiry +from bot.pagination import LinePaginator from bot.utils.scheduling import Scheduler from bot.utils.time import format_infraction_with_duration @@ -173,6 +175,46 @@ class Stream(commands.Cog): await ctx.send(f"{Emojis.cross_mark} This member doesn't have video permissions to remove!") log.debug(f"{member} ({member.id}) didn't have the streaming permission to remove!") + @commands.command(aliases=('lstream',)) + @commands.has_any_role(*MODERATION_ROLES) + async def liststream(self, ctx: commands.Context) -> None: + """Lists all non-staff users who have permission to stream.""" + non_staff_members_with_stream = [ + _member + for _member in ctx.guild.get_role(Roles.video).members + if not any(role.id in STAFF_ROLES for role in _member.roles) + ] + + # List of tuples (UtcPosixTimestamp, str) + # This is so that we can sort before outputting to the paginator + streamer_info = [] + for member in non_staff_members_with_stream: + if revoke_time := await self.task_cache.get(member.id): + # Member only has temporary streaming perms + revoke_delta = Arrow.utcfromtimestamp(revoke_time).humanize() + message = f"{member.mention} will have stream permissions revoked {revoke_delta}." + else: + message = f"{member.mention} has permanent streaming permissions." + + # If revoke_time is None use max timestamp to force sort to put them at the end + streamer_info.append( + (revoke_time or Arrow.max.timestamp(), message) + ) + + if streamer_info: + # Sort based on duration left of streaming perms + streamer_info.sort(key=itemgetter(0)) + + # Only output the message in the pagination + lines = [line[1] for line in streamer_info] + embed = discord.Embed( + title=f"Members who can stream (`{len(lines)}` total)", + colour=Colours.soft_green + ) + await LinePaginator.paginate(lines, ctx, embed, max_size=400, empty=False) + else: + await ctx.send("No members with stream permissions found.") + def setup(bot: Bot) -> None: """Loads the Stream cog.""" -- cgit v1.2.3 From a6b76092e6e6005fc98c9863db051804d7bb963a Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 19 Apr 2021 18:16:42 +0100 Subject: Update wording of comment to be clearer. --- bot/exts/moderation/stream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/stream.py b/bot/exts/moderation/stream.py index 5f3820748..d9837b5ed 100644 --- a/bot/exts/moderation/stream.py +++ b/bot/exts/moderation/stream.py @@ -186,7 +186,7 @@ class Stream(commands.Cog): ] # List of tuples (UtcPosixTimestamp, str) - # This is so that we can sort before outputting to the paginator + # This is so that output can be sorted on [0] before passed it's to the paginator streamer_info = [] for member in non_staff_members_with_stream: if revoke_time := await self.task_cache.get(member.id): -- cgit v1.2.3 From 94db90b038574077beb2fafb4f17741061ee8152 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 19 Apr 2021 18:23:34 +0100 Subject: Remove unnecessary _ in variable name --- bot/exts/moderation/stream.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bot/exts/moderation/stream.py b/bot/exts/moderation/stream.py index d9837b5ed..e541baeb2 100644 --- a/bot/exts/moderation/stream.py +++ b/bot/exts/moderation/stream.py @@ -180,9 +180,9 @@ class Stream(commands.Cog): async def liststream(self, ctx: commands.Context) -> None: """Lists all non-staff users who have permission to stream.""" non_staff_members_with_stream = [ - _member - for _member in ctx.guild.get_role(Roles.video).members - if not any(role.id in STAFF_ROLES for role in _member.roles) + member + for member in ctx.guild.get_role(Roles.video).members + if not any(role.id in STAFF_ROLES for role in member.roles) ] # List of tuples (UtcPosixTimestamp, str) -- cgit v1.2.3 From 131dab3754da9fc1c3cf770d76bb9deea46f2f8d Mon Sep 17 00:00:00 2001 From: ChrisJL Date: Mon, 19 Apr 2021 18:40:23 +0100 Subject: Improve the wording of the list streamers embed Co-authored-by: Matteo Bertucci --- bot/exts/moderation/stream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/stream.py b/bot/exts/moderation/stream.py index e541baeb2..bd93ea492 100644 --- a/bot/exts/moderation/stream.py +++ b/bot/exts/moderation/stream.py @@ -208,7 +208,7 @@ class Stream(commands.Cog): # Only output the message in the pagination lines = [line[1] for line in streamer_info] embed = discord.Embed( - title=f"Members who can stream (`{len(lines)}` total)", + title=f"Members with streaming permission (`{len(lines)}` total)", colour=Colours.soft_green ) await LinePaginator.paginate(lines, ctx, embed, max_size=400, empty=False) -- cgit v1.2.3 From a7581a4f9f2724672eebfdf541a922973c018c23 Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Mon, 19 Apr 2021 20:48:26 +0300 Subject: CamelCase the cog name --- bot/exts/moderation/modpings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bot/exts/moderation/modpings.py b/bot/exts/moderation/modpings.py index c351db615..690aa7c68 100644 --- a/bot/exts/moderation/modpings.py +++ b/bot/exts/moderation/modpings.py @@ -14,7 +14,7 @@ from bot.utils.scheduling import Scheduler log = logging.getLogger(__name__) -class Modpings(Cog): +class ModPings(Cog): """Commands for a moderator to turn moderator pings on and off.""" # RedisCache[str, str] @@ -132,5 +132,5 @@ class Modpings(Cog): def setup(bot: Bot) -> None: - """Load the Modpings cog.""" - bot.add_cog(Modpings(bot)) + """Load the ModPings cog.""" + bot.add_cog(ModPings(bot)) -- cgit v1.2.3 From c001456cf29f944deb632b28130fb16a170092e9 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 19 Apr 2021 18:49:09 +0100 Subject: Update comment in list stream for readibility --- bot/exts/moderation/stream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/stream.py b/bot/exts/moderation/stream.py index bd93ea492..1dbb2a46b 100644 --- a/bot/exts/moderation/stream.py +++ b/bot/exts/moderation/stream.py @@ -186,7 +186,7 @@ class Stream(commands.Cog): ] # List of tuples (UtcPosixTimestamp, str) - # This is so that output can be sorted on [0] before passed it's to the paginator + # So that the list can be sorted on the UtcPosixTimestamp before the message is passed to the paginator. streamer_info = [] for member in non_staff_members_with_stream: if revoke_time := await self.task_cache.get(member.id): -- cgit v1.2.3 From b8b920bfa5c4d918d41bfe06d85b1e85f4bec0da Mon Sep 17 00:00:00 2001 From: Vivaan Verma <54081925+doublevcodes@users.noreply.github.com> Date: Mon, 19 Apr 2021 20:01:41 +0100 Subject: Inline duration assignment Co-authored-by: Rohan Reddy Alleti --- bot/exts/moderation/infraction/superstarify.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index 6fa0d550f..3d880dec3 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -136,8 +136,7 @@ class Superstarify(InfractionScheduler, Cog): return # Set the duration to 1 hour if none was provided - if not duration: - duration = datetime.datetime.now() + datetime.timedelta(hours=1) + duration = duration or datetime.datetime.utcnow() + datetime.timedelta(hours=1) # Post the infraction to the API old_nick = member.display_name -- cgit v1.2.3 From ae5d1cb65ddec0e70df00a4051a5bf813d4e6e20 Mon Sep 17 00:00:00 2001 From: Vivaan Verma Date: Mon, 19 Apr 2021 21:06:15 +0100 Subject: Add default duration as constant and use Duration converter --- bot/exts/moderation/infraction/superstarify.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index 3d880dec3..0bc2198c3 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -1,4 +1,3 @@ -import datetime import json import logging import random @@ -12,7 +11,7 @@ from discord.utils import escape_markdown from bot import constants from bot.bot import Bot -from bot.converters import Expiry +from bot.converters import Duration from bot.exts.moderation.infraction import _utils from bot.exts.moderation.infraction._scheduler import InfractionScheduler from bot.utils.messages import format_user @@ -20,6 +19,7 @@ from bot.utils.time import format_infraction log = logging.getLogger(__name__) NICKNAME_POLICY_URL = "https://pythondiscord.com/pages/rules/#nickname-policy" +SUPERSTARIFY_DEFAULT_DURATION = "1h" with Path("bot/resources/stars.json").open(encoding="utf-8") as stars_file: STAR_NAMES = json.load(stars_file) @@ -110,7 +110,7 @@ class Superstarify(InfractionScheduler, Cog): self, ctx: Context, member: Member, - duration: t.Optional[Expiry], + duration: t.Optional[Duration], *, reason: str = '', ) -> None: @@ -136,7 +136,7 @@ class Superstarify(InfractionScheduler, Cog): return # Set the duration to 1 hour if none was provided - duration = duration or datetime.datetime.utcnow() + datetime.timedelta(hours=1) + duration = duration or await Duration().convert(ctx, SUPERSTARIFY_DEFAULT_DURATION) # Post the infraction to the API old_nick = member.display_name -- cgit v1.2.3 From 03f909df6758a10c95f0b63df487f1acd97ec36d Mon Sep 17 00:00:00 2001 From: Vivaan Verma Date: Mon, 19 Apr 2021 21:15:11 +0100 Subject: Change type hint from duration to expiry --- bot/exts/moderation/infraction/superstarify.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index 0bc2198c3..ef88fb43f 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -11,7 +11,7 @@ from discord.utils import escape_markdown from bot import constants from bot.bot import Bot -from bot.converters import Duration +from bot.converters import Duration, Expiry from bot.exts.moderation.infraction import _utils from bot.exts.moderation.infraction._scheduler import InfractionScheduler from bot.utils.messages import format_user @@ -110,7 +110,7 @@ class Superstarify(InfractionScheduler, Cog): self, ctx: Context, member: Member, - duration: t.Optional[Duration], + duration: t.Optional[Expiry], *, reason: str = '', ) -> None: -- cgit v1.2.3 From 91bdf9415ec88715fadf2e0a56b900b376b638db Mon Sep 17 00:00:00 2001 From: Vivaan Verma <54081925+doublevcodes@users.noreply.github.com> Date: Mon, 19 Apr 2021 22:02:45 +0100 Subject: Update bot/exts/moderation/infraction/superstarify.py Co-authored-by: Boris Muratov <8bee278@gmail.com> --- bot/exts/moderation/infraction/superstarify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index ef88fb43f..07e79b9fe 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -135,7 +135,7 @@ class Superstarify(InfractionScheduler, Cog): if await _utils.get_active_infraction(ctx, member, "superstar"): return - # Set the duration to 1 hour if none was provided + # Set to default duration if none was provided. duration = duration or await Duration().convert(ctx, SUPERSTARIFY_DEFAULT_DURATION) # Post the infraction to the API -- cgit v1.2.3 From 9aa2b42aa04724a4ebc74d3ff6c339c33547dce3 Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Tue, 20 Apr 2021 17:20:44 +0200 Subject: Tests: AsyncMock is now in the standard library! The `tests/README.md` file still referenced our old custom `AsyncMock` that has been removed in favour of the standard library one that has been introduced in 3.8. This commit fixes this by updating the section. --- tests/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/README.md b/tests/README.md index 4f62edd68..092324123 100644 --- a/tests/README.md +++ b/tests/README.md @@ -114,7 +114,7 @@ class BotCogTests(unittest.TestCase): ### Mocking coroutines -By default, the `unittest.mock.Mock` and `unittest.mock.MagicMock` classes cannot mock coroutines, since the `__call__` method they provide is synchronous. In anticipation of the `AsyncMock` that will be [introduced in Python 3.8](https://docs.python.org/3.9/whatsnew/3.8.html#unittest), we have added an `AsyncMock` helper to [`helpers.py`](/tests/helpers.py). Do note that this drop-in replacement only implements an asynchronous `__call__` method, not the additional assertions that will come with the new `AsyncMock` type in Python 3.8. +By default, the `unittest.mock.Mock` and `unittest.mock.MagicMock` classes cannot mock coroutines, since the `__call__` method they provide is synchronous. The [`AsyncMock`](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.AsyncMock) that has been [introduced in Python 3.8](https://docs.python.org/3.9/whatsnew/3.8.html#unittest) is an asynchronous version of `MagicMock` that can be used anywhere a coroutine is expected. ### Special mocks for some `discord.py` types -- cgit v1.2.3 From b12666dc4b75146b150c0812c5cb56f4317773ae Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Tue, 20 Apr 2021 18:48:12 +0300 Subject: Improve rediscache doc Co-authored-by: ChrisJL --- bot/exts/moderation/modpings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/modpings.py b/bot/exts/moderation/modpings.py index 690aa7c68..2f180e594 100644 --- a/bot/exts/moderation/modpings.py +++ b/bot/exts/moderation/modpings.py @@ -17,7 +17,7 @@ log = logging.getLogger(__name__) class ModPings(Cog): """Commands for a moderator to turn moderator pings on and off.""" - # RedisCache[str, str] + # RedisCache[discord.Member.id, 'Naïve ISO 8601 string'] # The cache's keys are mods who have pings off. # The cache's values are the times when the role should be re-applied to them, stored in ISO format. pings_off_mods = RedisCache() -- cgit v1.2.3 From 8a73d2b5e71444595b72155d7106c0fc48eeb027 Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Tue, 20 Apr 2021 19:14:10 +0300 Subject: Remove allowed mentions in modlog alert The modlog alert embed no longer pings everyone. --- bot/exts/moderation/modlog.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bot/exts/moderation/modlog.py b/bot/exts/moderation/modlog.py index 5e8ea595b..e92f76c9a 100644 --- a/bot/exts/moderation/modlog.py +++ b/bot/exts/moderation/modlog.py @@ -127,8 +127,7 @@ class ModLog(Cog, name="ModLog"): log_message = await channel.send( content=content, embed=embed, - files=files, - allowed_mentions=discord.AllowedMentions(everyone=True) + files=files ) if additional_embeds: -- cgit v1.2.3 From c20f84ff95671527e6fbacb04f07bcee3baaafcd Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Tue, 20 Apr 2021 17:54:44 +0100 Subject: Add the Moderators role to moderation_roles in config This allows mod alert pings to go through in #mod-alerts, the allowed mentions only included the mods team role which is not pinged on mod alerts. --- config-default.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config-default.yml b/config-default.yml index b19164d3f..b7c446889 100644 --- a/config-default.yml +++ b/config-default.yml @@ -275,6 +275,7 @@ guild: moderation_roles: - *ADMINS_ROLE - *MOD_TEAM_ROLE + - *MODS_ROLE - *OWNERS_ROLE staff_roles: -- cgit v1.2.3