aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Chris Lovering <[email protected]>2022-11-26 13:31:54 +0000
committerGravatar Chris Lovering <[email protected]>2022-11-26 17:38:52 +0000
commit707ace7304bba6351100f44893e65e53e071b658 (patch)
tree16af9208a6117fb407a2eee50ef169fa6314f56f
parentDon't load the help channel cog if disabled (diff)
Auto archive help forum posts after inactivity
I decided to keep the archive logic simple, and just go for 30 minutes since last message, rather than the hybrid of 30 mins + 10 depending on who sent the last message. The reason for using the hybrid approach previously was due to us running out of channels frequently Since this is no longer a problem, I decided to keep the logic simple.
-rw-r--r--bot/constants.py2
-rw-r--r--bot/exts/help_channels/_channel.py61
-rw-r--r--bot/exts/help_channels/_cog.py9
-rw-r--r--config-default.yml7
4 files changed, 78 insertions, 1 deletions
diff --git a/bot/constants.py b/bot/constants.py
index 24862059e..9851aea97 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -615,6 +615,8 @@ class HelpChannels(metaclass=YAMLGetter):
section = 'help_channels'
enable: bool
+ idle_minutes: int
+ deleted_idle_minutes: int
cmd_whitelist: List[int]
diff --git a/bot/exts/help_channels/_channel.py b/bot/exts/help_channels/_channel.py
index 3a290bdfd..48dcc13c6 100644
--- a/bot/exts/help_channels/_channel.py
+++ b/bot/exts/help_channels/_channel.py
@@ -1,9 +1,11 @@
"""Contains all logic to handle changes to posts in the help forum."""
import asyncio
import textwrap
+from datetime import timedelta
+import arrow
import discord
-from pydis_core.utils import members
+from pydis_core.utils import members, scheduling
import bot
from bot import constants
@@ -168,3 +170,60 @@ async def help_post_deleted(deleted_post_event: discord.RawThreadDeleteEvent) ->
if cached_post and not cached_post.archived:
# If the post is in the bot's cache, and it was not archived before deleting, report a complete session.
await _stats.report_complete_session(cached_post, _stats.ClosingReason.DELETED)
+
+
+async def get_closing_time(post: discord.Thread) -> tuple[arrow.Arrow, _stats.ClosingReason]:
+ """
+ Return the time at which the given help `post` should be closed along with the reason.
+
+ The time is calculated by first checking if the opening message is deleted.
+ If it is, then get the last 100 messages (the most that can be fetched in one API call).
+ If less than 100 message are returned, and none are from the post owner, then assume the poster
+ has sent no further messages and close deleted_idle_minutes after the post creation time.
+
+ Otherwise, use the most recent message's create_at date and add `idle_minutes_claimant`.
+ """
+ try:
+ starter_message = post.starter_message or await post.fetch_message(post.id)
+ except discord.NotFound:
+ starter_message = None
+
+ last_100_messages = [message async for message in post.history(limit=100, oldest_first=False)]
+
+ if starter_message is None and len(last_100_messages) < 100:
+ if not discord.utils.get(last_100_messages, author__id=post.owner_id):
+ time = arrow.Arrow.fromdatetime(post.created_at)
+ time += timedelta(minutes=constants.HelpChannels.deleted_idle_minutes)
+ return time, _stats.ClosingReason.DELETED
+
+ time = arrow.Arrow.fromdatetime(last_100_messages[0].created_at)
+ time += timedelta(minutes=constants.HelpChannels.idle_minutes)
+ return time, _stats.ClosingReason.INACTIVE
+
+
+async def maybe_archive_idle_post(post: discord.Thread, scheduler: scheduling.Scheduler, has_task: bool = True) -> None:
+ """
+ Archive the `post` if idle, or schedule the archive for later if still active.
+
+ If `has_task` is True and rescheduling is required, the extant task to make the post
+ dormant will first be cancelled.
+ """
+ log.trace(f"Handling open post #{post} ({post.id}).")
+
+ closing_time, closing_reason = await get_closing_time(post)
+
+ if closing_time < (arrow.utcnow() + timedelta(seconds=1)):
+ # Closing time is in the past.
+ # Add 1 second due to POSIX timestamps being lower resolution than datetime objects.
+ log.info(
+ f"#{post} ({post.id}) is idle past {closing_time} and will be archived. Reason: {closing_reason.value}"
+ )
+ await _close_help_post(post, closing_reason)
+ return
+
+ if has_task:
+ scheduler.cancel(post.id)
+ delay = (closing_time - arrow.utcnow()).seconds
+ log.info(f"#{post} ({post.id}) is still active; scheduling it to be archived after {delay} seconds.")
+
+ scheduler.schedule_later(delay, post.id, maybe_archive_idle_post(post, scheduler, has_task=True))
diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py
index 742cf38dd..0c3510c29 100644
--- a/bot/exts/help_channels/_cog.py
+++ b/bot/exts/help_channels/_cog.py
@@ -4,6 +4,7 @@ import typing as t
import discord
from discord.ext import commands
+from pydis_core.utils import scheduling
from bot import constants
from bot.bot import Bot
@@ -28,13 +29,21 @@ class HelpForum(commands.Cog):
def __init__(self, bot: Bot):
self.bot = bot
+ self.scheduler = scheduling.Scheduler(self.__class__.__name__)
self.help_forum_channel: discord.ForumChannel = None
+ async def cog_unload(self) -> None:
+ """Cancel all scheduled tasks on unload."""
+ self.scheduler.cancel_all()
+
async def cog_load(self) -> None:
"""Archive all idle open posts, schedule check for later for active open posts."""
log.trace("Initialising help forum cog.")
self.help_forum_channel = self.bot.get_channel(constants.Channels.help_system_forum)
+ for post in self.help_forum_channel.channels:
+ await _channel.maybe_archive_idle_post(post, self.scheduler, has_task=False)
+
async def close_check(self, ctx: commands.Context) -> bool:
"""Return True if the channel is a help post, and the user is the claimant or has a whitelisted role."""
if not _channel.is_help_forum_post(ctx.channel):
diff --git a/config-default.yml b/config-default.yml
index c9d043ff7..1d7a2ff78 100644
--- a/config-default.yml
+++ b/config-default.yml
@@ -493,6 +493,13 @@ free:
help_channels:
enable: true
+ # Allowed duration of inactivity before archiving a help post
+ idle_minutes: 30
+
+ # Allowed duration of inactivity when post is empty (due to deleted messages)
+ # before archiving a help post
+ deleted_idle_minutes: 5
+
# Roles which are allowed to use the command which makes channels dormant
cmd_whitelist:
- *HELPERS_ROLE