diff options
author | 2020-04-23 19:08:15 +0700 | |
---|---|---|
committer | 2020-04-23 19:08:15 +0700 | |
commit | 4abe5d708999031ec2a8c2689a6dcd898caee700 (patch) | |
tree | b021c223d18e564d0c7018cfc300b9417441b8d1 | |
parent | Free tag: fix typo in header (diff) | |
parent | Merge pull request #900 from python-discord/fix-category-cache-issue (diff) |
Merge branch 'master' into free-tag
-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: |