Skip to content

Commit 45a23ec

Browse files
authored
Merge pull request #302 from DiscordFederation/auto-close-thread
This introduces `thread_auto_close` and `thread_auto_close_response`.…
2 parents fd63972 + 9eef9a6 commit 45a23ec

File tree

3 files changed

+85
-3
lines changed

3 files changed

+85
-3
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010

1111
- Sponsors command that will list sponsors.
1212
- An alert will now be sent to the log channel if a thread channel fails to create. This could be due to a variety of problems such as insufficient permissions or the category channel limit is met.
13+
- Threads will close automatically after some time when `thread_auto_close` is set.
14+
- Custom closing message can be set with `thread_auto_close_response`.
1315

1416
### Changed
1517
- Channel names now can contain unicode characters.

core/config.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ class ConfigManager:
3535
"blocked_emoji",
3636
"close_emoji",
3737
"disable_recipient_thread_close",
38+
"thread_auto_close",
39+
"thread_auto_close_response",
3840
"thread_creation_response",
3941
"thread_creation_footer",
4042
"thread_creation_title",
@@ -89,7 +91,7 @@ class ConfigManager:
8991

9092
colors = {"mod_color", "recipient_color", "main_color"}
9193

92-
time_deltas = {"account_age", "guild_age"}
94+
time_deltas = {"account_age", "guild_age", "thread_auto_close"}
9395

9496
valid_keys = allowed_to_change_in_command | internal_keys | protected_keys
9597

core/thread.py

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import asyncio
22
import logging
3-
import re
43
import os
4+
import re
55
import string
66
import typing
7-
from types import SimpleNamespace as param
87
from datetime import datetime, timedelta
8+
from types import SimpleNamespace as param
99

1010
import discord
11+
import isodate
1112
from discord.ext.commands import MissingRequiredArgument, CommandError
1213

14+
from core.time import human_timedelta
1315
from core.utils import is_image_url, days, match_user_id
1416
from core.utils import truncate, ignore, error
1517

@@ -39,6 +41,7 @@ def __init__(
3941
self.genesis_message = None
4042
self._ready_event = asyncio.Event()
4143
self._close_task = None
44+
self._auto_close_task = None
4245

4346
def __repr__(self):
4447
return (
@@ -231,6 +234,10 @@ async def _close(
231234

232235
await self.cancel_closure()
233236

237+
# Cancel auto closing the thread if closed by any means.
238+
if self._auto_close_task:
239+
self._auto_close_task.cancel()
240+
234241
if str(self.id) in self.bot.config.subscriptions:
235242
del self.bot.config.subscriptions[str(self.id)]
236243

@@ -349,6 +356,71 @@ async def _find_thread_message(channel, message_id):
349356
if str(message_id) == str(embed.author.url).split("/")[-1]:
350357
return msg
351358

359+
async def _fetch_timeout(
360+
self
361+
) -> typing.Union[None, isodate.duration.Duration, timedelta]:
362+
"""
363+
This grabs the timeout value for closing threads automatically
364+
from the ConfigManager and parses it for use internally.
365+
366+
:returns: None if no timeout is set.
367+
"""
368+
timeout = self.bot.config.get("thread_auto_close")
369+
if timeout is None:
370+
return timeout
371+
else:
372+
try:
373+
timeout = isodate.parse_duration(timeout)
374+
except isodate.ISO8601Error:
375+
logger.warning(
376+
"The auto_close_thread limit needs to be a "
377+
"ISO-8601 duration formatted duration string "
378+
'greater than 0 days, not "%s".',
379+
str(timeout),
380+
)
381+
del self.bot.config.cache["thread_auto_close"]
382+
await self.bot.config.update()
383+
timeout = None
384+
return timeout
385+
386+
async def _restart_close_timer(self):
387+
"""
388+
This will create or restart a timer to automatically close this
389+
thread.
390+
"""
391+
timeout = await self._grab_timeout()
392+
393+
# Exit if timeout was not set
394+
if timeout is None:
395+
return
396+
397+
# Set timeout seconds
398+
seconds = timeout.total_seconds()
399+
# seconds = 20 # Uncomment to debug with just 20 seconds
400+
reset_time = datetime.utcnow() + timedelta(seconds=seconds)
401+
human_time = human_timedelta(dt=reset_time)
402+
403+
# Grab message
404+
close_message = self.bot.config.get(
405+
"thread_auto_close_response",
406+
f"This thread has been closed automatically due to inactivity "
407+
f"after {human_time}.",
408+
)
409+
time_marker_regex = "%t"
410+
if len(re.findall(time_marker_regex, close_message)) == 1:
411+
close_message = re.sub(time_marker_regex, str(human_time), close_message)
412+
elif len(re.findall(time_marker_regex, close_message)) > 1:
413+
logger.warning(
414+
"The thread_auto_close_response should only contain one"
415+
f" '{time_marker_regex}' to specify time."
416+
)
417+
418+
if self._auto_close_task:
419+
self._auto_close_task.cancel()
420+
self._auto_close_task = self.bot.loop.call_later(
421+
seconds, self._close_after, self.bot.user, False, True, close_message
422+
)
423+
352424
async def edit_message(self, message_id: int, message: str) -> None:
353425
recipient_msg, channel_msg = await asyncio.gather(
354426
self._find_thread_message(self.recipient, message_id),
@@ -435,6 +507,8 @@ async def reply(self, message: discord.Message, anonymous: bool = False) -> None
435507
)
436508
)
437509

510+
await self._restart_close_timer()
511+
438512
if self.close_task is not None:
439513
# Cancel closing if a thread message is sent.
440514
await self.cancel_closure()
@@ -477,6 +551,10 @@ async def send(
477551
if not from_mod and not note:
478552
self.bot.loop.create_task(self.bot.api.append_log(message, self.channel.id))
479553

554+
# Cancel auto closing if we get a new message from user
555+
if self._auto_close_task:
556+
self._auto_close_task.cancel()
557+
480558
destination = destination or self.channel
481559

482560
author = message.author

0 commit comments

Comments
 (0)