diff options
| author | 2021-04-11 20:17:49 +0100 | |
|---|---|---|
| committer | 2021-04-11 20:17:49 +0100 | |
| commit | a5b586eae2408b3d1897c4543f1386b2cb6e1325 (patch) | |
| tree | cbe556a219dc8ed53647393a6404f3fd56e6abf2 | |
| parent | Filtering: use a more thorough regex for zalgo & invisible chars (diff) | |
| parent | Merge pull request #1414 from python-discord/dynamic-available-message-patch (diff) | |
Merge branch 'main' into bug/filters/1469/invis-chars
| -rw-r--r-- | bot/constants.py | 1 | ||||
| -rw-r--r-- | bot/exts/help_channels/_caches.py | 4 | ||||
| -rw-r--r-- | bot/exts/help_channels/_cog.py | 48 | ||||
| -rw-r--r-- | bot/exts/recruitment/talentpool/_cog.py | 19 | ||||
| -rw-r--r-- | bot/exts/recruitment/talentpool/_review.py | 51 | ||||
| -rw-r--r-- | config-default.yml | 1 | 
6 files changed, 101 insertions, 23 deletions
| diff --git a/bot/constants.py b/bot/constants.py index 547a94a0b..6d14bbb3a 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -412,6 +412,7 @@ class Channels(metaclass=YAMLGetter):      python_general: int      cooldown: int +    how_to_get_help: int      attachment_log: int      message_log: int diff --git a/bot/exts/help_channels/_caches.py b/bot/exts/help_channels/_caches.py index e741fd20f..c5e4ee917 100644 --- a/bot/exts/help_channels/_caches.py +++ b/bot/exts/help_channels/_caches.py @@ -20,3 +20,7 @@ non_claimant_last_message_times = RedisCache(namespace="HelpChannels.non_claiman  # This cache maps a help channel to original question message in same channel.  # RedisCache[discord.TextChannel.id, discord.Message.id]  question_messages = RedisCache(namespace="HelpChannels.question_messages") + +# This cache keeps track of the dynamic message ID for +# the continuously updated message in the #How-to-get-help channel. +dynamic_message = RedisCache(namespace="HelpChannels.dynamic_message") diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py index 18457f6a5..c18fcf875 100644 --- a/bot/exts/help_channels/_cog.py +++ b/bot/exts/help_channels/_cog.py @@ -21,6 +21,7 @@ NAMESPACE = "help"  HELP_CHANNEL_TOPIC = """  This is a Python help channel. You can claim your own help channel in the Python Help: Available category.  """ +AVAILABLE_HELP_CHANNELS = "**Currently available help channel(s):** {available}"  class HelpChannels(commands.Cog): @@ -75,6 +76,9 @@ class HelpChannels(commands.Cog):          self.last_notification: t.Optional[arrow.Arrow] = None +        self.dynamic_message: t.Optional[int] = None +        self.available_help_channels: t.Set[discord.TextChannel] = set() +          # Asyncio stuff          self.queue_tasks: t.List[asyncio.Task] = []          self.init_task = self.bot.loop.create_task(self.init_cog()) @@ -123,6 +127,9 @@ class HelpChannels(commands.Cog):          # Delete to indicate that the help session has yet to receive an answer.          await _caches.non_claimant_last_message_times.delete(message.channel.id) +        # Removing the help channel from the dynamic message, and editing/sending that message. +        self.available_help_channels.remove(message.channel) +          # Not awaited because it may indefinitely hold the lock while waiting for a channel.          scheduling.create_task(self.move_to_available(), name=f"help_claim_{message.id}") @@ -240,6 +247,10 @@ class HelpChannels(commands.Cog):              for channel in channels[:abs(missing)]:                  await self.unclaim_channel(channel, closed_on=_channel.ClosingReason.CLEANUP) +        # Getting channels that need to be included in the dynamic message. +        await self.update_available_help_channels() +        log.trace("Dynamic available help message updated.") +      async def init_categories(self) -> None:          """Get the help category objects. Remove the cog if retrieval fails."""          log.trace("Getting the CategoryChannel objects for the help categories.") @@ -284,6 +295,10 @@ class HelpChannels(commands.Cog):          # This may confuse users. So would potentially long delays for the cog to become ready.          self.close_command.enabled = True +        # Acquiring the dynamic message ID, if it exists within the cache. +        log.trace("Attempting to fetch How-to-get-help dynamic message ID.") +        self.dynamic_message = await _caches.dynamic_message.get("message_id") +          await self.init_available()          _stats.report_counts() @@ -338,6 +353,10 @@ class HelpChannels(commands.Cog):              category_id=constants.Categories.help_available,          ) +        # Adding the help channel to the dynamic message, and editing/sending that message. +        self.available_help_channels.add(channel) +        await self.update_available_help_channels() +          _stats.report_counts()      async def move_to_dormant(self, channel: discord.TextChannel) -> None: @@ -472,3 +491,32 @@ class HelpChannels(commands.Cog):          self.queue_tasks.remove(task)          return channel + +    async def update_available_help_channels(self) -> None: +        """Updates the dynamic message within #how-to-get-help for available help channels.""" +        if not self.available_help_channels: +            self.available_help_channels = set( +                c for c in self.available_category.channels if not _channel.is_excluded_channel(c) +            ) + +        available_channels = AVAILABLE_HELP_CHANNELS.format( +            available=', '.join(c.mention for c in self.available_help_channels) or None +        ) + +        if self.dynamic_message is not None: +            try: +                log.trace("Help channels have changed, dynamic message has been edited.") +                await self.bot.http.edit_message( +                    constants.Channels.how_to_get_help, self.dynamic_message, content=available_channels +                ) +            except discord.NotFound: +                pass +            else: +                return + +        log.trace("Dynamic message could not be edited or found. Creating a new one.") +        new_dynamic_message = await self.bot.http.send_message( +            constants.Channels.how_to_get_help, available_channels +        ) +        self.dynamic_message = new_dynamic_message["id"] +        await _caches.dynamic_message.set("message_id", self.dynamic_message) diff --git a/bot/exts/recruitment/talentpool/_cog.py b/bot/exts/recruitment/talentpool/_cog.py index fbe79382d..72604be51 100644 --- a/bot/exts/recruitment/talentpool/_cog.py +++ b/bot/exts/recruitment/talentpool/_cog.py @@ -1,14 +1,16 @@  import logging  import textwrap  from collections import ChainMap +from io import StringIO  from typing import Union +import discord  from discord import Color, Embed, Member, User  from discord.ext.commands import Cog, Context, group, has_any_role  from bot.api import ResponseCodeError  from bot.bot import Bot -from bot.constants import Channels, Guild, MODERATION_ROLES, STAFF_ROLES, Webhooks +from bot.constants import Channels, Emojis, Guild, MODERATION_ROLES, STAFF_ROLES, Webhooks  from bot.converters import FetchedMember  from bot.exts.moderation.watchchannels._watchchannel import WatchChannel  from bot.exts.recruitment.talentpool._review import Reviewer @@ -330,7 +332,18 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):          """Mark a user's nomination as reviewed and cancel the review task."""          if not await self.reviewer.mark_reviewed(ctx, user_id):              return -        await ctx.send(f"✅ The user with ID `{user_id}` was marked as reviewed.") +        await ctx.send(f"{Emojis.check_mark} The user with ID `{user_id}` was marked as reviewed.") + +    @nomination_group.command(aliases=('gr',)) +    @has_any_role(*MODERATION_ROLES) +    async def get_review(self, ctx: Context, user_id: int) -> None: +        """Get the user's review as a markdown file.""" +        review = (await self.reviewer.make_review(user_id))[0] +        if review: +            file = discord.File(StringIO(review), f"{user_id}_review.md") +            await ctx.send(file=file) +        else: +            await ctx.send(f"There doesn't appear to be an active nomination for {user_id}")      @nomination_group.command(aliases=('review',))      @has_any_role(*MODERATION_ROLES) @@ -340,7 +353,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):              return          await self.reviewer.post_review(user_id, update_database=False) -        await ctx.message.add_reaction("✅") +        await ctx.message.add_reaction(Emojis.check_mark)      @Cog.listener()      async def on_member_ban(self, guild: Guild, user: Union[User, Member]) -> None: diff --git a/bot/exts/recruitment/talentpool/_review.py b/bot/exts/recruitment/talentpool/_review.py index fb3461238..11aa3b62b 100644 --- a/bot/exts/recruitment/talentpool/_review.py +++ b/bot/exts/recruitment/talentpool/_review.py @@ -66,26 +66,40 @@ class Reviewer:              self._review_scheduler.schedule_at(review_at, user_id, self.post_review(user_id, update_database=True))      async def post_review(self, user_id: int, update_database: bool) -> None: -        """Format a generic review of a user and post it to the nomination voting channel.""" +        """Format the review of a user and post it to the nomination voting channel.""" +        review, seen_emoji = await self.make_review(user_id) +        if not review: +            return + +        guild = self.bot.get_guild(Guild.id) +        channel = guild.get_channel(Channels.nomination_voting) +          log.trace(f"Posting the review of {user_id}") +        message = (await self._bulk_send(channel, review))[-1] +        if seen_emoji: +            for reaction in (seen_emoji, "\N{THUMBS UP SIGN}", "\N{THUMBS DOWN SIGN}"): +                await message.add_reaction(reaction) + +        if update_database: +            nomination = self._pool.watched_users[user_id] +            await self.bot.api_client.patch(f"{self._pool.api_endpoint}/{nomination['id']}", json={"reviewed": True}) + +    async def make_review(self, user_id: int) -> typing.Tuple[str, Optional[Emoji]]: +        """Format a generic review of a user and return it with the seen emoji.""" +        log.trace(f"Formatting the review of {user_id}")          nomination = self._pool.watched_users[user_id]          if not nomination:              log.trace(f"There doesn't appear to be an active nomination for {user_id}") -            return +            return "", None          guild = self.bot.get_guild(Guild.id) -        channel = guild.get_channel(Channels.nomination_voting)          member = guild.get_member(user_id) -        if update_database: -            await self.bot.api_client.patch(f"{self._pool.api_endpoint}/{nomination['id']}", json={"reviewed": True}) -          if not member: -            await channel.send( -                f"I tried to review the user with ID `{user_id}`, but they don't appear to be on the server 😔" -            ) -            return +            return ( +                f"I tried to review the user with ID `{user_id}`, but they don't appear to be on the server :pensive:" +            ), None          opening = f"<@&{Roles.moderators}> <@&{Roles.admins}>\n{member.mention} ({member}) for Helper!" @@ -100,14 +114,11 @@ class Reviewer:          vote_request = (              "*Refer to their nomination and infraction histories for further details*.\n"              f"*Please react {seen_emoji} if you've seen this post." -            " Then react 👍 for approval, or 👎 for disapproval*." +            " Then react :+1: for approval, or :-1: for disapproval*."          ) -        review = "\n\n".join(part for part in (opening, current_nominations, review_body, vote_request)) - -        message = (await self._bulk_send(channel, review))[-1] -        for reaction in (seen_emoji, "👍", "👎"): -            await message.add_reaction(reaction) +        review = "\n\n".join((opening, current_nominations, review_body, vote_request)) +        return review, seen_emoji      async def _construct_review_body(self, member: Member) -> str:          """Formats the body of the nomination, with details of activity, infractions, and previous nominations.""" @@ -256,10 +267,10 @@ class Reviewer:      @staticmethod      def _random_ducky(guild: Guild) -> Union[Emoji, str]: -        """Picks a random ducky emoji to be used to mark the vote as seen. If no duckies found returns 👀.""" +        """Picks a random ducky emoji to be used to mark the vote as seen. If no duckies found returns :eyes:."""          duckies = [emoji for emoji in guild.emojis if emoji.name.startswith("ducky")]          if not duckies: -            return "👀" +            return ":eyes:"          return random.choice(duckies)      @staticmethod @@ -289,12 +300,12 @@ class Reviewer:          await self._pool.fetch_user_cache()          if user_id not in self._pool.watched_users:              log.trace(f"Can't find a nominated user with id {user_id}") -            await ctx.send(f"❌ Can't find a currently nominated user with id `{user_id}`") +            await ctx.send(f":x: Can't find a currently nominated user with id `{user_id}`")              return False          nomination = self._pool.watched_users[user_id]          if nomination["reviewed"]: -            await ctx.send("❌ This nomination was already reviewed, but here's a cookie 🍪") +            await ctx.send(":x: This nomination was already reviewed, but here's a cookie :cookie:")              return False          await self.bot.api_client.patch(f"{self._pool.api_endpoint}/{nomination['id']}", json={"reviewed": True}) diff --git a/config-default.yml b/config-default.yml index 9b07d026d..8c6e18470 100644 --- a/config-default.yml +++ b/config-default.yml @@ -163,6 +163,7 @@ guild:          # Python Help: Available          cooldown:           720603994149486673 +        how_to_get_help:    704250143020417084          # Topical          discord_py:         343944376055103488 | 
