diff options
Diffstat (limited to '')
| -rw-r--r-- | bot/cogs/help_channels.py | 60 | ||||
| -rw-r--r-- | bot/cogs/sync/syncers.py | 3 | ||||
| -rw-r--r-- | bot/cogs/tags.py | 2 | ||||
| -rw-r--r-- | config-default.yml | 1 | ||||
| -rw-r--r-- | tests/bot/cogs/sync/test_base.py | 3 | 
5 files changed, 57 insertions, 12 deletions
| diff --git a/bot/cogs/help_channels.py b/bot/cogs/help_channels.py index 692bb234c..a61f30deb 100644 --- a/bot/cogs/help_channels.py +++ b/bot/cogs/help_channels.py @@ -62,7 +62,8 @@ through our guide for [asking a good question]({ASKING_GUIDE_URL}).  """  AVAILABLE_EMOJI = "✅" -IN_USE_EMOJI = "⌛" +IN_USE_ANSWERED_EMOJI = "⌛" +IN_USE_UNANSWERED_EMOJI = "⏳"  NAME_SEPARATOR = "|" @@ -132,7 +133,14 @@ class HelpChannels(Scheduler, commands.Cog):          self.init_task = self.bot.loop.create_task(self.init_cog())          # Stats -        self.claim_times = {} + +        # This dictionary maps a help channel to the time it was claimed +        self.claim_times: t.Dict[int, datetime] = {} + +        # This dictionary maps a help channel to whether it has had any +        # activity other than the original claimant. True being no other +        # activity and False being other activity. +        self.unanswered: t.Dict[int, bool] = {}      def cog_unload(self) -> None:          """Cancel the init task and scheduled tasks when the cog unloads.""" @@ -268,13 +276,12 @@ class HelpChannels(Scheduler, commands.Cog):          return name -    @staticmethod -    def get_category_channels(category: discord.CategoryChannel) -> t.Iterable[discord.TextChannel]: +    def get_category_channels(self, category: discord.CategoryChannel) -> t.Iterable[discord.TextChannel]:          """Yield the text channels of the `category` in an unsorted manner."""          log.trace(f"Getting text channels in the category '{category}' ({category.id}).")          # This is faster than using category.channels because the latter sorts them. -        for channel in category.guild.channels: +        for channel in self.bot.get_guild(constants.Guild.id).channels:              if channel.category_id == category.id and isinstance(channel, discord.TextChannel):                  yield channel @@ -420,6 +427,12 @@ class HelpChannels(Scheduler, commands.Cog):          embed = message.embeds[0]          return message.author == self.bot.user and embed.description.strip() == DORMANT_MSG.strip() +    @staticmethod +    def is_in_category(channel: discord.TextChannel, category_id: int) -> bool: +        """Return True if `channel` is within a category with `category_id`.""" +        actual_category = getattr(channel, "category", None) +        return actual_category is not None and actual_category.id == category_id +      async def move_idle_channel(self, channel: discord.TextChannel, has_task: bool = True) -> None:          """          Make the `channel` dormant if idle or schedule the move if still active. @@ -500,6 +513,12 @@ class HelpChannels(Scheduler, commands.Cog):              in_use_time = datetime.now() - claimed              self.bot.stats.timing("help.in_use_time", in_use_time) +        if channel.id in self.unanswered: +            if self.unanswered[channel.id]: +                self.bot.stats.incr("help.sessions.unanswered") +            else: +                self.bot.stats.incr("help.sessions.answered") +          log.trace(f"Position of #{channel} ({channel.id}) is actually {channel.position}.")          log.trace(f"Sending dormant message for #{channel} ({channel.id}).") @@ -515,7 +534,7 @@ class HelpChannels(Scheduler, commands.Cog):          log.info(f"Moving #{channel} ({channel.id}) to the In Use category.")          await channel.edit( -            name=f"{IN_USE_EMOJI}{NAME_SEPARATOR}{self.get_clean_channel_name(channel)}", +            name=f"{IN_USE_UNANSWERED_EMOJI}{NAME_SEPARATOR}{self.get_clean_channel_name(channel)}",              category=self.in_use_category,              sync_permissions=True,              topic=IN_USE_TOPIC, @@ -574,6 +593,27 @@ class HelpChannels(Scheduler, commands.Cog):              # Handle it here cause this feature isn't critical for the functionality of the system.              log.exception("Failed to send notification about lack of dormant channels!") +    async def check_for_answer(self, message: discord.Message) -> None: +        """Checks for whether new content in a help channel comes from non-claimants.""" +        channel = message.channel +        log.trace(f"Checking if #{channel} ({channel.id}) has been answered.") + +        # Confirm the channel is an in use help channel +        if self.is_in_category(channel, constants.Categories.help_in_use): +            # Check if there is an entry in unanswered (does not persist across restarts) +            if channel.id in self.unanswered: +                claimant_id = self.help_channel_claimants[channel].id + +                # Check the message did not come from the claimant +                if claimant_id != message.author.id: +                    # Mark the channel as answered +                    self.unanswered[channel.id] = False + +                    # Change the emoji in the channel name to signify activity +                    log.trace(f"#{channel} ({channel.id}) has been answered; changing its emoji") +                    name = self.get_clean_channel_name(channel) +                    await channel.edit(name=f"{IN_USE_ANSWERED_EMOJI}{NAME_SEPARATOR}{name}") +      @commands.Cog.listener()      async def on_message(self, message: discord.Message) -> None:          """Move an available channel to the In Use category and replace it with a dormant one.""" @@ -581,7 +621,10 @@ class HelpChannels(Scheduler, commands.Cog):              return  # Ignore messages sent by bots.          channel = message.channel -        if channel.category and channel.category.id != constants.Categories.help_available: + +        await self.check_for_answer(message) + +        if not self.is_in_category(channel, constants.Categories.help_available):              return  # Ignore messages outside the Available category.          log.trace("Waiting for the cog to be ready before processing messages.") @@ -591,7 +634,7 @@ class HelpChannels(Scheduler, commands.Cog):          async with self.on_message_lock:              log.trace(f"on_message lock acquired for {message.id}.") -            if channel.category and channel.category.id != constants.Categories.help_available: +            if not self.is_in_category(channel, constants.Categories.help_available):                  log.debug(                      f"Message {message.id} will not make #{channel} ({channel.id}) in-use "                      f"because another message in the channel already triggered that." @@ -606,6 +649,7 @@ class HelpChannels(Scheduler, commands.Cog):              self.bot.stats.incr("help.claimed")              self.claim_times[channel.id] = datetime.now() +            self.unanswered[channel.id] = True              log.trace(f"Releasing on_message lock for {message.id}.") diff --git a/bot/cogs/sync/syncers.py b/bot/cogs/sync/syncers.py index 003bf3727..e55bf27fd 100644 --- a/bot/cogs/sync/syncers.py +++ b/bot/cogs/sync/syncers.py @@ -1,4 +1,5 @@  import abc +import asyncio  import logging  import typing as t  from collections import namedtuple @@ -122,7 +123,7 @@ class Syncer(abc.ABC):                  check=partial(self._reaction_check, author, message),                  timeout=constants.Sync.confirm_timeout              ) -        except TimeoutError: +        except asyncio.TimeoutError:              # reaction will remain none thus sync will be aborted in the finally block below.              log.debug(f"The {self.name} syncer confirmation prompt timed out.") diff --git a/bot/cogs/tags.py b/bot/cogs/tags.py index 9ba33d7e0..a813ffff5 100644 --- a/bot/cogs/tags.py +++ b/bot/cogs/tags.py @@ -43,7 +43,7 @@ class Tags(Cog):              tag = {                  "title": tag_title,                  "embed": { -                    "description": file.read_text() +                    "description": file.read_text(encoding="utf-8")                  }              }              cache[tag_title] = tag diff --git a/config-default.yml b/config-default.yml index 4cd61ce10..f2b0bfa9f 100644 --- a/config-default.yml +++ b/config-default.yml @@ -475,7 +475,6 @@ anti_malware:          - '.mp3'          - '.wav'          - '.ogg' -        - '.md'  reddit: diff --git a/tests/bot/cogs/sync/test_base.py b/tests/bot/cogs/sync/test_base.py index 6ee9dfda6..70aea2bab 100644 --- a/tests/bot/cogs/sync/test_base.py +++ b/tests/bot/cogs/sync/test_base.py @@ -1,3 +1,4 @@ +import asyncio  import unittest  from unittest import mock @@ -211,7 +212,7 @@ class SyncerConfirmationTests(unittest.IsolatedAsyncioTestCase):          subtests = (              (constants.Emojis.check_mark, True, None),              ("InVaLiD", False, None), -            (None, False, TimeoutError), +            (None, False, asyncio.TimeoutError),          )          for emoji, ret_val, side_effect in subtests: | 
