From 8ea13768378deadef6e666ed40ed88ff8a08e16d Mon Sep 17 00:00:00 2001 From: Steele Date: Fri, 20 Nov 2020 22:22:10 -0500 Subject: `!close` removes the cooldown role from the claimant even when invoked by someone else; flattened `close_command` --- bot/exts/help_channels.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/bot/exts/help_channels.py b/bot/exts/help_channels.py index f5a8b251b..e50fab7fc 100644 --- a/bot/exts/help_channels.py +++ b/bot/exts/help_channels.py @@ -213,18 +213,23 @@ class HelpChannels(commands.Cog): and reset the send permissions cooldown for the user who started the session. """ log.trace("close command invoked; checking if the channel is in-use.") - if ctx.channel.category == self.in_use_category: - if await self.dormant_check(ctx): - await self.remove_cooldown_role(ctx.author) + if ctx.channel.category != self.in_use_category: + log.debug(f"{ctx.author} invoked command 'dormant' outside an in-use help channel") + return - # Ignore missing task when cooldown has passed but the channel still isn't dormant. - if ctx.author.id in self.scheduler: - self.scheduler.cancel(ctx.author.id) + if not await self.dormant_check(ctx): + return - await self.move_to_dormant(ctx.channel, "command") - self.scheduler.cancel(ctx.channel.id) - else: - log.debug(f"{ctx.author} invoked command 'dormant' outside an in-use help channel") + guild = self.bot.get_guild(constants.Guild.id) + claimant = guild.get_member(await self.help_channel_claimants.get(ctx.channel.id)) + await self.move_to_dormant(ctx.channel, "command") + await self.remove_cooldown_role(claimant) + + # Ignore missing task when cooldown has passed but the channel still isn't dormant. + if ctx.author.id in self.scheduler: + self.scheduler.cancel(ctx.author.id) + + self.scheduler.cancel(ctx.channel.id) async def get_available_candidate(self) -> discord.TextChannel: """ -- cgit v1.2.3 From 089efa35345af32c8f5475bb49bd09b9cd3f06c3 Mon Sep 17 00:00:00 2001 From: Steele Date: Wed, 25 Nov 2020 16:09:40 -0500 Subject: `!close` removes role when they have no help channels left; needs to be fixed so role is removed when the channel times out --- bot/exts/help_channels.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bot/exts/help_channels.py b/bot/exts/help_channels.py index e50fab7fc..e1d28ece3 100644 --- a/bot/exts/help_channels.py +++ b/bot/exts/help_channels.py @@ -223,7 +223,10 @@ class HelpChannels(commands.Cog): guild = self.bot.get_guild(constants.Guild.id) claimant = guild.get_member(await self.help_channel_claimants.get(ctx.channel.id)) await self.move_to_dormant(ctx.channel, "command") - await self.remove_cooldown_role(claimant) + + # Remove the cooldown role if they have no other channels left + if claimant.id not in {user_id for _, user_id in await self.help_channel_claimants.items()}: + await self.remove_cooldown_role(claimant) # Ignore missing task when cooldown has passed but the channel still isn't dormant. if ctx.author.id in self.scheduler: @@ -413,6 +416,8 @@ class HelpChannels(commands.Cog): for channel in self.get_category_channels(self.in_use_category): await self.move_idle_channel(channel, has_task=False) + log.trace(f'Initial state of help_channel_claimants: {await self.help_channel_claimants.items()}') + # Prevent the command from being used until ready. # The ready event wasn't used because channels could change categories between the time # the command is invoked and the cog is ready (e.g. if move_idle_channel wasn't called yet). -- cgit v1.2.3 From 731fea162705583e1ee6edeb5da270b628a018d5 Mon Sep 17 00:00:00 2001 From: Steele Date: Wed, 25 Nov 2020 16:44:37 -0500 Subject: Moved the removal of the cooldown role from `close_command` to `move_to_dormant` --- bot/exts/help_channels.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/bot/exts/help_channels.py b/bot/exts/help_channels.py index e1d28ece3..4fd4896df 100644 --- a/bot/exts/help_channels.py +++ b/bot/exts/help_channels.py @@ -220,14 +220,8 @@ class HelpChannels(commands.Cog): if not await self.dormant_check(ctx): return - guild = self.bot.get_guild(constants.Guild.id) - claimant = guild.get_member(await self.help_channel_claimants.get(ctx.channel.id)) await self.move_to_dormant(ctx.channel, "command") - # Remove the cooldown role if they have no other channels left - if claimant.id not in {user_id for _, user_id in await self.help_channel_claimants.items()}: - await self.remove_cooldown_role(claimant) - # Ignore missing task when cooldown has passed but the channel still isn't dormant. if ctx.author.id in self.scheduler: self.scheduler.cancel(ctx.author.id) @@ -552,18 +546,25 @@ class HelpChannels(commands.Cog): async def move_to_dormant(self, channel: discord.TextChannel, caller: str) -> None: """ - Make the `channel` dormant. + Make the `channel` dormant and remove the help cooldown role if it was the claimant's only channel. A caller argument is provided for metrics. """ log.info(f"Moving #{channel} ({channel.id}) to the Dormant category.") + guild = self.bot.get_guild(constants.Guild.id) + claimant = guild.get_member(await self.help_channel_claimants.get(channel.id)) + await self.help_channel_claimants.delete(channel.id) await self.move_to_bottom_position( channel=channel, category_id=constants.Categories.help_dormant, ) + # Remove the cooldown role if the claimant has no other channels left + if claimant.id not in {user_id for _, user_id in await self.help_channel_claimants.items()}: + await self.remove_cooldown_role(claimant) + self.bot.stats.incr(f"help.dormant_calls.{caller}") in_use_time = await self.get_in_use_time(channel.id) -- cgit v1.2.3 From 3f490ab413b64474bc7e40a2d66d3c3178d615ba Mon Sep 17 00:00:00 2001 From: Steele Date: Thu, 26 Nov 2020 13:34:03 -0500 Subject: Changes requested by @MarkKoz, new `unclaim_channel` method Deleted expensive logging operation; moved cooldown role removal functionality to new `unclaim_channel` method; handle possibility that claimant has left the guild; optimized redis cache iteration with `any` --- bot/exts/help_channels.py | 46 ++++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/bot/exts/help_channels.py b/bot/exts/help_channels.py index 5676728e9..25ca67d47 100644 --- a/bot/exts/help_channels.py +++ b/bot/exts/help_channels.py @@ -221,16 +221,9 @@ class HelpChannels(commands.Cog): log.debug(f"{ctx.author} invoked command 'dormant' outside an in-use help channel") return - if not await self.dormant_check(ctx): - return - - await self.move_to_dormant(ctx.channel, "command") - - # Ignore missing task when cooldown has passed but the channel still isn't dormant. - if ctx.author.id in self.scheduler: - self.scheduler.cancel(ctx.author.id) - - self.scheduler.cancel(ctx.channel.id) + if await self.dormant_check(ctx): + await self.move_to_dormant(ctx.channel, "command") + self.scheduler.cancel(ctx.channel.id) async def get_available_candidate(self) -> discord.TextChannel: """ @@ -414,8 +407,6 @@ class HelpChannels(commands.Cog): for channel in self.get_category_channels(self.in_use_category): await self.move_idle_channel(channel, has_task=False) - log.trace(f'Initial state of help_channel_claimants: {await self.help_channel_claimants.items()}') - # Prevent the command from being used until ready. # The ready event wasn't used because channels could change categories between the time # the command is invoked and the cog is ready (e.g. if move_idle_channel wasn't called yet). @@ -550,24 +541,18 @@ class HelpChannels(commands.Cog): async def move_to_dormant(self, channel: discord.TextChannel, caller: str) -> None: """ - Make the `channel` dormant and remove the help cooldown role if it was the claimant's only channel. + Make the `channel` dormant. A caller argument is provided for metrics. """ log.info(f"Moving #{channel} ({channel.id}) to the Dormant category.") - guild = self.bot.get_guild(constants.Guild.id) - claimant = guild.get_member(await self.help_channel_claimants.get(channel.id)) - - await self.help_channel_claimants.delete(channel.id) await self.move_to_bottom_position( channel=channel, category_id=constants.Categories.help_dormant, ) - # Remove the cooldown role if the claimant has no other channels left - if claimant.id not in {user_id for _, user_id in await self.help_channel_claimants.items()}: - await self.remove_cooldown_role(claimant) + await self.unclaim_channel(channel) self.bot.stats.incr(f"help.dormant_calls.{caller}") @@ -592,6 +577,27 @@ class HelpChannels(commands.Cog): self.channel_queue.put_nowait(channel) self.report_stats() + async def unclaim_channel(self, channel: discord.TextChannel) -> None: + """ + Deletes `channel` from the mapping of channels to claimants and removes the help cooldown + role from the claimant if it was their only channel + """ + claimant_id = await self.help_channel_claimants.pop(channel.id) + + # Ignore missing task when cooldown has passed but the channel still isn't dormant. + if claimant_id in self.scheduler: + self.scheduler.cancel(claimant_id) + + claimant = self.bot.get_guild(constants.Guild.id).get_member(claimant_id) + + if claimant is None: + # `claimant` has left the guild, so the cooldown role need not be removed + return + + # Remove the cooldown role if the claimant has no other channels left + if not any(claimant.id == user_id for _, user_id in await self.help_channel_claimants.items()): + await self.remove_cooldown_role(claimant) + async def move_to_in_use(self, channel: discord.TextChannel) -> None: """Make a channel in-use and schedule it to be made dormant.""" log.info(f"Moving #{channel} ({channel.id}) to the In Use category.") -- cgit v1.2.3 From 6ac6786c480ec9919009acca4906a52234f42285 Mon Sep 17 00:00:00 2001 From: Steele Date: Sun, 29 Nov 2020 13:04:38 -0500 Subject: `!close` removes role from claimant only, new method `unclaim_channel`. Previously `!close` would remove the cooldown role from the person who issued the command, whereas now `unclaim_channel` handles removing the role from the claimant if it was their only channel open. --- bot/exts/help_channels/_cog.py | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py index e22d4663e..86eb91b02 100644 --- a/bot/exts/help_channels/_cog.py +++ b/bot/exts/help_channels/_cog.py @@ -145,22 +145,17 @@ class HelpChannels(commands.Cog): Make the current in-use help channel dormant. Make the channel dormant if the user passes the `dormant_check`, - delete the message that invoked this, - and reset the send permissions cooldown for the user who started the session. + delete the message that invoked this. """ log.trace("close command invoked; checking if the channel is in-use.") - if ctx.channel.category == self.in_use_category: - if await self.dormant_check(ctx): - await _cooldown.remove_cooldown_role(ctx.author) - # Ignore missing task when cooldown has passed but the channel still isn't dormant. - if ctx.author.id in self.scheduler: - self.scheduler.cancel(ctx.author.id) - - await self.move_to_dormant(ctx.channel, "command") - self.scheduler.cancel(ctx.channel.id) - else: + if ctx.channel.category != self.in_use_category: log.debug(f"{ctx.author} invoked command 'dormant' outside an in-use help channel") + return + + if await self.dormant_check(ctx): + await self.move_to_dormant(ctx.channel, "command") + self.scheduler.cancel(ctx.channel.id) async def get_available_candidate(self) -> discord.TextChannel: """ @@ -368,12 +363,13 @@ class HelpChannels(commands.Cog): """ log.info(f"Moving #{channel} ({channel.id}) to the Dormant category.") - await _caches.claimants.delete(channel.id) await self.move_to_bottom_position( channel=channel, category_id=constants.Categories.help_dormant, ) + await self.unclaim_channel(channel) + self.bot.stats.incr(f"help.dormant_calls.{caller}") in_use_time = await _channel.get_in_use_time(channel.id) @@ -397,6 +393,26 @@ class HelpChannels(commands.Cog): self.channel_queue.put_nowait(channel) self.report_stats() + async def unclaim_channel(self, channel: discord.TextChannel) -> None: + """ + Remove the claimant from the claimant cache and remove the cooldown role + if it was their last open help channel. + """ + claimant_id = await _caches.claimants.pop(channel.id) + + # Ignore missing task when cooldown has passed but the channel still isn't dormant. + if claimant_id in self.scheduler: + self.scheduler.cancel(claimant_id) + + claimant = self.bot.get_guild(constants.Guild.id).get_member(claimant_id) + if claimant is None: + log.info(f"{claimant_id} left the guild during their help session; the cooldown role won't be removed") + return + + # Remove the cooldown role if the claimant has no other channels left + if not any(claimant.id == user_id for _, user_id in await _caches.claimants.items()): + await _cooldown.remove_cooldown_role(claimant) + async def move_to_in_use(self, channel: discord.TextChannel) -> None: """Make a channel in-use and schedule it to be made dormant.""" log.info(f"Moving #{channel} ({channel.id}) to the In Use category.") -- cgit v1.2.3 From 15593de0a1a503a39aa29031061bc17ac26e4230 Mon Sep 17 00:00:00 2001 From: Steele Date: Sun, 29 Nov 2020 14:15:19 -0500 Subject: Corrected `unclaim_channel` docstring to comply with style guide --- bot/exts/help_channels/_cog.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py index 86eb91b02..983c5d183 100644 --- a/bot/exts/help_channels/_cog.py +++ b/bot/exts/help_channels/_cog.py @@ -395,8 +395,10 @@ class HelpChannels(commands.Cog): async def unclaim_channel(self, channel: discord.TextChannel) -> None: """ - Remove the claimant from the claimant cache and remove the cooldown role - if it was their last open help channel. + Mark the channel as unclaimed and remove the cooldown role from the claimant if needed. + + The role is only removed if they have no claimed channels left once the current one is unclaimed. + This method also handles canceling the automatic removal of the cooldown role. """ claimant_id = await _caches.claimants.pop(channel.id) -- cgit v1.2.3 From f537768034a2c9791ca08a91c66b9f97aef8edca Mon Sep 17 00:00:00 2001 From: Steele Date: Sat, 5 Dec 2020 12:08:44 -0500 Subject: Bot relays the infraction reason in the DM. Previously, the infraction DM from the bot gave a formulaic message about the nickname policy. It now gives a slightly different message along with the reason given by the mod. This means that the message the user gets and the infraction reason that gets recorded are now the same. --- bot/exts/moderation/infraction/superstarify.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index 96dfb562f..a4327fb95 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -111,7 +111,7 @@ class Superstarify(InfractionScheduler, Cog): member: Member, duration: Expiry, *, - reason: str = None, + reason: str = '', ) -> None: """ Temporarily force a random superstar name (like Taylor Swift) to be the user's nickname. @@ -128,15 +128,16 @@ class Superstarify(InfractionScheduler, Cog): Alternatively, an ISO 8601 timestamp can be provided for the duration. - An optional reason can be provided. If no reason is given, the original name will be shown - in a generated reason. + An optional reason can be provided, which would be added to a message stating their old nickname + and linking to the nickname policy. """ if await _utils.get_active_infraction(ctx, member, "superstar"): return # Post the infraction to the API old_nick = member.display_name - reason = reason or f"old nick: {old_nick}" + reason = (f"Nickname '{old_nick}' does not comply with our [nickname policy]({NICKNAME_POLICY_URL}). " + f"{reason}") infraction = await _utils.post_infraction(ctx, member, "superstar", reason, duration, active=True) id_ = infraction["id"] @@ -152,7 +153,6 @@ class Superstarify(InfractionScheduler, Cog): old_nick = escape_markdown(old_nick) forced_nick = escape_markdown(forced_nick) - superstar_reason = f"Your nickname didn't comply with our [nickname policy]({NICKNAME_POLICY_URL})." nickname_info = textwrap.dedent(f""" Old nickname: `{old_nick}` New nickname: `{forced_nick}` @@ -160,7 +160,7 @@ class Superstarify(InfractionScheduler, Cog): successful = await self.apply_infraction( ctx, infraction, member, action(), - user_reason=superstar_reason, + user_reason=reason, additional_info=nickname_info ) -- cgit v1.2.3 From d7e94f2570c69ae04c32bc4bad338b1be0c1da26 Mon Sep 17 00:00:00 2001 From: Steele Date: Sat, 5 Dec 2020 12:09:54 -0500 Subject: Add `starify` and `unstarify` as command aliases. --- 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 a4327fb95..e7d1c4da8 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -104,7 +104,7 @@ class Superstarify(InfractionScheduler, Cog): await self.reapply_infraction(infraction, action) - @command(name="superstarify", aliases=("force_nick", "star")) + @command(name="superstarify", aliases=("force_nick", "star", "starify")) async def superstarify( self, ctx: Context, @@ -182,7 +182,7 @@ class Superstarify(InfractionScheduler, Cog): ) await ctx.send(embed=embed) - @command(name="unsuperstarify", aliases=("release_nick", "unstar")) + @command(name="unsuperstarify", aliases=("release_nick", "unstar", "unstarify")) async def unsuperstarify(self, ctx: Context, member: Member) -> None: """Remove the superstarify infraction and allow the user to change their nickname.""" await self.pardon_infraction(ctx, "superstar", member) -- cgit v1.2.3 From 0f66fe3040d70de51ece1aa0de38a88b20000221 Mon Sep 17 00:00:00 2001 From: Steele Date: Sun, 6 Dec 2020 11:16:56 -0500 Subject: User gets a more detailed message from the bot Whereas one of my previous commits makes the message the user gets and the infraction that gets recorded the same, the recorded infraction is now shorter, but the message the user gets is more similar to the embed posted in the public channel. We also softened the language of the user-facing message a bit. --- bot/exts/moderation/infraction/superstarify.py | 28 +++++++++++++++----------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index e7d1c4da8..1d512a4c7 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -136,9 +136,8 @@ class Superstarify(InfractionScheduler, Cog): # Post the infraction to the API old_nick = member.display_name - reason = (f"Nickname '{old_nick}' does not comply with our [nickname policy]({NICKNAME_POLICY_URL}). " - f"{reason}") - infraction = await _utils.post_infraction(ctx, member, "superstar", reason, duration, active=True) + infraction_reason = f'Old nickname: {old_nick}. {reason}' + infraction = await _utils.post_infraction(ctx, member, "superstar", infraction_reason, duration, active=True) id_ = infraction["id"] forced_nick = self.get_nick(id_, member.id) @@ -158,9 +157,21 @@ class Superstarify(InfractionScheduler, Cog): New nickname: `{forced_nick}` """).strip() + formatted_reason = f'**Additional details:** {reason}\n\n' if reason else '' + + embed_reason = ( + f"Your previous nickname, **{old_nick}**, " + f"didn't comply with our nickname policy. " + f"Your new nickname will be **{forced_nick}**.\n\n" + f"{formatted_reason}" + f"You will be unable to change your nickname until **{expiry_str}**. " + "If you're confused by this, please read our " + f"[official nickname policy]({NICKNAME_POLICY_URL})." + ) + successful = await self.apply_infraction( ctx, infraction, member, action(), - user_reason=reason, + user_reason=embed_reason, additional_info=nickname_info ) @@ -171,14 +182,7 @@ class Superstarify(InfractionScheduler, Cog): embed = Embed( title="Congratulations!", colour=constants.Colours.soft_orange, - description=( - f"Your previous nickname, **{old_nick}**, " - f"was so bad that we have decided to change it. " - f"Your new nickname will be **{forced_nick}**.\n\n" - f"You will be unable to change your nickname until **{expiry_str}**.\n\n" - "If you're confused by this, please read our " - f"[official nickname policy]({NICKNAME_POLICY_URL})." - ) + description=embed_reason ) await ctx.send(embed=embed) -- cgit v1.2.3 From 032b64f625d9d16f532ba0e895a412bc24ee9659 Mon Sep 17 00:00:00 2001 From: Steele Date: Mon, 7 Dec 2020 11:04:38 -0500 Subject: Use the original wording of the public embed, but change the title to "Superstarified!" Per internal staff discussion, we'll keep the wording of the message. --- bot/exts/moderation/infraction/superstarify.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index 1d512a4c7..ffc470c54 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -157,32 +157,29 @@ class Superstarify(InfractionScheduler, Cog): New nickname: `{forced_nick}` """).strip() - formatted_reason = f'**Additional details:** {reason}\n\n' if reason else '' - - embed_reason = ( + user_message = ( f"Your previous nickname, **{old_nick}**, " - f"didn't comply with our nickname policy. " + f"was so bad that we have decided to change it. " f"Your new nickname will be **{forced_nick}**.\n\n" - f"{formatted_reason}" + "{reason}" f"You will be unable to change your nickname until **{expiry_str}**. " "If you're confused by this, please read our " f"[official nickname policy]({NICKNAME_POLICY_URL})." - ) + ).format successful = await self.apply_infraction( ctx, infraction, member, action(), - user_reason=embed_reason, + user_reason=user_message(reason=f'**Additional details:** {reason}\n\n' if reason else ''), additional_info=nickname_info ) - # Send an embed with the infraction information to the invoking context if - # superstar was successful. + # Send an embed with to the invoking context if superstar was successful. if successful: log.trace(f"Sending superstar #{id_} embed.") embed = Embed( - title="Congratulations!", + title="Superstarified!", colour=constants.Colours.soft_orange, - description=embed_reason + description=user_message(reason='') ) await ctx.send(embed=embed) -- cgit v1.2.3