|
1 | 1 | import asyncio |
2 | 2 | import logging |
3 | | -import re |
4 | 3 | import os |
| 4 | +import re |
5 | 5 | import string |
6 | 6 | import typing |
7 | | -from types import SimpleNamespace as param |
8 | 7 | from datetime import datetime, timedelta |
| 8 | +from types import SimpleNamespace as param |
9 | 9 |
|
10 | 10 | import discord |
| 11 | +import isodate |
11 | 12 | from discord.ext.commands import MissingRequiredArgument, CommandError |
12 | 13 |
|
| 14 | +from core.time import human_timedelta |
13 | 15 | from core.utils import is_image_url, days, match_user_id |
14 | 16 | from core.utils import truncate, ignore, error |
15 | 17 |
|
@@ -39,6 +41,7 @@ def __init__( |
39 | 41 | self.genesis_message = None |
40 | 42 | self._ready_event = asyncio.Event() |
41 | 43 | self._close_task = None |
| 44 | + self._auto_close_task = None |
42 | 45 |
|
43 | 46 | def __repr__(self): |
44 | 47 | return ( |
@@ -218,6 +221,10 @@ async def _close( |
218 | 221 |
|
219 | 222 | await self.cancel_closure() |
220 | 223 |
|
| 224 | + # Cancel auto closing the thread if closed by any means. |
| 225 | + if self._auto_close_task: |
| 226 | + self._auto_close_task.cancel() |
| 227 | + |
221 | 228 | if str(self.id) in self.bot.config.subscriptions: |
222 | 229 | del self.bot.config.subscriptions[str(self.id)] |
223 | 230 |
|
@@ -336,6 +343,72 @@ async def _find_thread_message(channel, message_id): |
336 | 343 | if str(message_id) == str(embed.author.url).split("/")[-1]: |
337 | 344 | return msg |
338 | 345 |
|
| 346 | + async def _grab_timeout( |
| 347 | + self |
| 348 | + ) -> typing.Union[None, isodate.duration.Duration, timedelta]: |
| 349 | + """ |
| 350 | + This grabs the timeout value for closing threads automatically |
| 351 | + from the ConfigManager and parses it for use internally. |
| 352 | +
|
| 353 | + :returns: None if no timeout is set. |
| 354 | + """ |
| 355 | + timeout = self.bot.config.get("thread_auto_close") |
| 356 | + if timeout is None: |
| 357 | + return timeout |
| 358 | + else: |
| 359 | + try: |
| 360 | + timeout = isodate.parse_duration(timeout) |
| 361 | + except isodate.ISO8601Error: |
| 362 | + logger.warning( |
| 363 | + "The auto_close_thread limit needs to be a " |
| 364 | + "ISO-8601 duration formatted duration string " |
| 365 | + 'greater than 0 days, not "%s".', |
| 366 | + str(timeout), |
| 367 | + ) |
| 368 | + del self.bot.config.cache["thread_auto_close"] |
| 369 | + await self.bot.config.update() |
| 370 | + timeout = None |
| 371 | + return timeout |
| 372 | + |
| 373 | + async def _restart_close_timer(self): |
| 374 | + """ |
| 375 | + This will create or restart a timer to automatically close this |
| 376 | + thread. |
| 377 | + """ |
| 378 | + timeout = await self._grab_timeout() |
| 379 | + |
| 380 | + # Exit if timeout was not set |
| 381 | + if timeout is None: |
| 382 | + return |
| 383 | + |
| 384 | + # Set timeout seconds |
| 385 | + seconds = timeout.total_seconds() |
| 386 | + # seconds = 20 # Uncomment to debug with just 20 seconds |
| 387 | + reset_time = datetime.utcnow() + timedelta(seconds=seconds) |
| 388 | + human_time = human_timedelta(dt=reset_time) |
| 389 | + |
| 390 | + # Grab message |
| 391 | + close_message = self.bot.config.get( |
| 392 | + "thread_auto_close_response", |
| 393 | + f"This thread has been closed automatically after no response from" |
| 394 | + f" you for {human_time}." |
| 395 | + ) |
| 396 | + time_marker_regex = "%t" |
| 397 | + if len(re.findall(time_marker_regex, close_message)) == 1: |
| 398 | + close_message = re.sub(time_marker_regex, str(human_time), close_message) |
| 399 | + elif len(re.findall(time_marker_regex, close_message)) > 1: |
| 400 | + logger.warning( |
| 401 | + "The thread_auto_close_response should only contain one" |
| 402 | + f" '{time_marker_regex}' to specify time." |
| 403 | + ) |
| 404 | + |
| 405 | + if self._auto_close_task: |
| 406 | + self._auto_close_task.cancel() |
| 407 | + self._auto_close_task = self.bot.loop.call_later( |
| 408 | + seconds, self._close_after, self.bot.user, False, True, |
| 409 | + close_message |
| 410 | + ) |
| 411 | + |
339 | 412 | async def edit_message(self, message_id: int, message: str) -> None: |
340 | 413 | recipient_msg, channel_msg = await asyncio.gather( |
341 | 414 | self._find_thread_message(self.recipient, message_id), |
@@ -422,6 +495,8 @@ async def reply(self, message: discord.Message, anonymous: bool = False) -> None |
422 | 495 | ) |
423 | 496 | ) |
424 | 497 |
|
| 498 | + await self._restart_close_timer() |
| 499 | + |
425 | 500 | if self.close_task is not None: |
426 | 501 | # Cancel closing if a thread message is sent. |
427 | 502 | await self.cancel_closure() |
@@ -464,6 +539,10 @@ async def send( |
464 | 539 | if not from_mod and not note: |
465 | 540 | self.bot.loop.create_task(self.bot.api.append_log(message, self.channel.id)) |
466 | 541 |
|
| 542 | + # Cancel auto closing if we get a new message from user |
| 543 | + if self._auto_close_task: |
| 544 | + self._auto_close_task.cancel() |
| 545 | + |
467 | 546 | destination = destination or self.channel |
468 | 547 |
|
469 | 548 | author = message.author |
|
0 commit comments