aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Shirayuki Nekomata <[email protected]>2020-04-23 19:08:15 +0700
committerGravatar GitHub <[email protected]>2020-04-23 19:08:15 +0700
commit4abe5d708999031ec2a8c2689a6dcd898caee700 (patch)
treeb021c223d18e564d0c7018cfc300b9417441b8d1
parentFree tag: fix typo in header (diff)
parentMerge pull request #900 from python-discord/fix-category-cache-issue (diff)
Merge branch 'master' into free-tag
-rw-r--r--bot/cogs/help_channels.py60
-rw-r--r--bot/cogs/sync/syncers.py3
-rw-r--r--bot/cogs/tags.py2
-rw-r--r--config-default.yml1
-rw-r--r--tests/bot/cogs/sync/test_base.py3
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: