Skip to content

Commit 25b1a72

Browse files
author
kwzrd
committed
Merge: changes from 'upstream/main'
Lockfile conflict resolved by re-locking on the merged Pipfile.
2 parents 4b1e90f + 5f4dc42 commit 25b1a72

File tree

14 files changed

+282
-272
lines changed

14 files changed

+282
-272
lines changed

.github/CODEOWNERS

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
**/bot/exts/moderation/*silence.py @MarkKoz
55
bot/exts/info/codeblock/** @MarkKoz
66
bot/exts/utils/extensions.py @MarkKoz
7-
bot/exts/utils/snekbox.py @MarkKoz @Akarys42
7+
bot/exts/utils/snekbox.py @MarkKoz @Akarys42 @jb3
88
bot/exts/help_channels/** @MarkKoz @Akarys42
9-
bot/exts/moderation/** @Akarys42 @mbaruh @Den4200 @ks129
10-
bot/exts/info/** @Akarys42 @Den4200
11-
bot/exts/info/information.py @mbaruh
12-
bot/exts/filters/** @mbaruh
9+
bot/exts/moderation/** @Akarys42 @mbaruh @Den4200 @ks129 @jb3
10+
bot/exts/info/** @Akarys42 @Den4200 @jb3
11+
bot/exts/info/information.py @mbaruh @jb3
12+
bot/exts/filters/** @mbaruh @jb3
1313
bot/exts/fun/** @ks129
14-
bot/exts/utils/** @ks129
14+
bot/exts/utils/** @ks129 @jb3
1515
bot/exts/recruitment/** @wookie184
1616

1717
# Rules
@@ -30,9 +30,9 @@ tests/bot/exts/test_cogs.py @MarkKoz
3030
tests/** @Akarys42
3131

3232
# CI & Docker
33-
.github/workflows/** @MarkKoz @Akarys42 @SebastiaanZ @Den4200
34-
Dockerfile @MarkKoz @Akarys42 @Den4200
35-
docker-compose.yml @MarkKoz @Akarys42 @Den4200
33+
.github/workflows/** @MarkKoz @Akarys42 @SebastiaanZ @Den4200 @jb3
34+
Dockerfile @MarkKoz @Akarys42 @Den4200 @jb3
35+
docker-compose.yml @MarkKoz @Akarys42 @Den4200 @jb3
3636

3737
# Tools
3838
Pipfile* @Akarys42

Pipfile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ aiodns = "~=2.0"
99
aiohttp = "~=3.7"
1010
aioping = "~=0.3.1"
1111
aioredis = "~=1.3.1"
12+
arrow = "~=1.0.3"
1213
"async-rediscache[fakeredis]" = "~=0.1.2"
1314
beautifulsoup4 = "~=4.9"
1415
colorama = {version = "~=0.4.3",sys_platform = "== 'win32'"}
1516
coloredlogs = "~=14.0"
1617
deepdiff = "~=4.0"
1718
"discord.py" = "~=1.6.0"
19+
emoji = "~=0.6"
1820
feedparser = "~=5.2"
1921
fuzzywuzzy = "~=0.17"
2022
lxml = "~=4.4"
@@ -27,11 +29,10 @@ requests = "~=2.22"
2729
sentry-sdk = "~=0.19"
2830
sphinx = "~=2.2"
2931
statsd = "~=3.3"
30-
arrow = "~=0.17"
31-
emoji = "~=0.6"
3232

3333
[dev-packages]
3434
coverage = "~=5.0"
35+
coveralls = "~=2.1"
3536
flake8 = "~=3.8"
3637
flake8-annotations = "~=2.0"
3738
flake8-bugbear = "~=20.1"
@@ -42,7 +43,6 @@ flake8-tidy-imports = "~=4.0"
4243
flake8-todo = "~=0.7"
4344
pep8-naming = "~=0.9"
4445
pre-commit = "~=2.1"
45-
coveralls = "~=2.1"
4646

4747
[requires]
4848
python_version = "3.8"

Pipfile.lock

Lines changed: 50 additions & 55 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bot/constants.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,6 @@ class Channels(metaclass=YAMLGetter):
402402
python_events: int
403403
python_news: int
404404
reddit: int
405-
user_event_announcements: int
406405

407406
dev_contrib: int
408407
dev_core: int
@@ -414,7 +413,6 @@ class Channels(metaclass=YAMLGetter):
414413
cooldown: int
415414

416415
attachment_log: int
417-
dm_log: int
418416
message_log: int
419417
mod_log: int
420418
user_log: int
@@ -466,7 +464,6 @@ class Webhooks(metaclass=YAMLGetter):
466464

467465
big_brother: int
468466
dev_log: int
469-
dm_log: int
470467
duck_pond: int
471468
incidents_archive: int
472469
reddit: int
@@ -593,7 +590,8 @@ class HelpChannels(metaclass=YAMLGetter):
593590
enable: bool
594591
claim_minutes: int
595592
cmd_whitelist: List[int]
596-
idle_minutes: int
593+
idle_minutes_claimant: int
594+
idle_minutes_others: int
597595
deleted_idle_minutes: int
598596
max_available: int
599597
max_total_channels: int

bot/exts/help_channels/_caches.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88
# RedisCache[discord.TextChannel.id, t.Union[discord.User.id, discord.Member.id]]
99
claimants = RedisCache(namespace="HelpChannels.help_channel_claimants")
1010

11+
# Stores the timestamp of the last message from the claimant of a help channel
12+
# RedisCache[discord.TextChannel.id, UtcPosixTimestamp]
13+
claimant_last_message_times = RedisCache(namespace="HelpChannels.claimant_last_message_times")
14+
15+
# This cache maps a help channel to the timestamp of the last non-claimant message.
16+
# This cache being empty for a given help channel indicates the question is unanswered.
17+
# RedisCache[discord.TextChannel.id, UtcPosixTimestamp]
18+
non_claimant_last_message_times = RedisCache(namespace="HelpChannels.non_claimant_last_message_times")
19+
1120
# This cache maps a help channel to original question message in same channel.
1221
# RedisCache[discord.TextChannel.id, discord.Message.id]
1322
question_messages = RedisCache(namespace="HelpChannels.question_messages")
14-
15-
# This cache maps a help channel to whether it has had any
16-
# activity other than the original claimant. True being no other
17-
# activity and False being other activity.
18-
# RedisCache[discord.TextChannel.id, bool]
19-
unanswered = RedisCache(namespace="HelpChannels.unanswered")

bot/exts/help_channels/_channel.py

Lines changed: 73 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import logging
22
import typing as t
3-
from datetime import datetime, timedelta
3+
from datetime import timedelta
4+
from enum import Enum
45

6+
import arrow
57
import discord
8+
from arrow import Arrow
69

710
import bot
811
from bot import constants
@@ -15,6 +18,17 @@
1518
EXCLUDED_CHANNELS = (constants.Channels.cooldown,)
1619

1720

21+
class ClosingReason(Enum):
22+
"""All possible closing reasons for help channels."""
23+
24+
COMMAND = "command"
25+
LATEST_MESSSAGE = "auto.latest_message"
26+
CLAIMANT_TIMEOUT = "auto.claimant_timeout"
27+
OTHER_TIMEOUT = "auto.other_timeout"
28+
DELETED = "auto.deleted"
29+
CLEANUP = "auto.cleanup"
30+
31+
1832
def get_category_channels(category: discord.CategoryChannel) -> t.Iterable[discord.TextChannel]:
1933
"""Yield the text channels of the `category` in an unsorted manner."""
2034
log.trace(f"Getting text channels in the category '{category}' ({category.id}).")
@@ -25,23 +39,68 @@ def get_category_channels(category: discord.CategoryChannel) -> t.Iterable[disco
2539
yield channel
2640

2741

28-
async def get_idle_time(channel: discord.TextChannel) -> t.Optional[int]:
42+
async def get_closing_time(channel: discord.TextChannel, init_done: bool) -> t.Tuple[Arrow, ClosingReason]:
2943
"""
30-
Return the time elapsed, in seconds, since the last message sent in the `channel`.
44+
Return the time at which the given help `channel` should be closed along with the reason.
3145
32-
Return None if the channel has no messages.
46+
`init_done` is True if the cog has finished loading and False otherwise.
47+
48+
The time is calculated as follows:
49+
50+
* If `init_done` is True or the cached time for the claimant's last message is unavailable,
51+
add the configured `idle_minutes_claimant` to the time the most recent message was sent.
52+
* If the help session is empty (see `is_empty`), do the above but with `deleted_idle_minutes`.
53+
* If either of the above is attempted but the channel is completely empty, close the channel
54+
immediately.
55+
* Otherwise, retrieve the times of the claimant's and non-claimant's last messages from the
56+
cache. Add the configured `idle_minutes_claimant` and idle_minutes_others`, respectively, and
57+
choose the time which is furthest in the future.
3358
"""
34-
log.trace(f"Getting the idle time for #{channel} ({channel.id}).")
59+
log.trace(f"Getting the closing time for #{channel} ({channel.id}).")
60+
61+
is_empty = await _message.is_empty(channel)
62+
if is_empty:
63+
idle_minutes_claimant = constants.HelpChannels.deleted_idle_minutes
64+
else:
65+
idle_minutes_claimant = constants.HelpChannels.idle_minutes_claimant
66+
67+
claimant_time = await _caches.claimant_last_message_times.get(channel.id)
68+
69+
# The current session lacks messages, the cog is still starting, or the cache is empty.
70+
if is_empty or not init_done or claimant_time is None:
71+
msg = await _message.get_last_message(channel)
72+
if not msg:
73+
log.debug(f"No idle time available; #{channel} ({channel.id}) has no messages, closing now.")
74+
return Arrow.min, ClosingReason.DELETED
75+
76+
# Use the greatest offset to avoid the possibility of prematurely closing the channel.
77+
time = Arrow.fromdatetime(msg.created_at) + timedelta(minutes=idle_minutes_claimant)
78+
return time, ClosingReason.LATEST_MESSSAGE
79+
80+
claimant_time = Arrow.utcfromtimestamp(claimant_time)
81+
others_time = await _caches.non_claimant_last_message_times.get(channel.id)
82+
83+
if others_time:
84+
others_time = Arrow.utcfromtimestamp(others_time)
85+
else:
86+
# The help session hasn't received any answers (messages from non-claimants) yet.
87+
# Set to min value so it isn't considered when calculating the closing time.
88+
others_time = Arrow.min
3589

36-
msg = await _message.get_last_message(channel)
37-
if not msg:
38-
log.debug(f"No idle time available; #{channel} ({channel.id}) has no messages.")
39-
return None
90+
# Offset the cached times by the configured values.
91+
others_time += timedelta(minutes=constants.HelpChannels.idle_minutes_others)
92+
claimant_time += timedelta(minutes=idle_minutes_claimant)
4093

41-
idle_time = (datetime.utcnow() - msg.created_at).seconds
94+
# Use the time which is the furthest into the future.
95+
if claimant_time >= others_time:
96+
closing_time = claimant_time
97+
reason = ClosingReason.CLAIMANT_TIMEOUT
98+
else:
99+
closing_time = others_time
100+
reason = ClosingReason.OTHER_TIMEOUT
42101

43-
log.trace(f"#{channel} ({channel.id}) has been idle for {idle_time} seconds.")
44-
return idle_time
102+
log.trace(f"#{channel} ({channel.id}) should be closed at {closing_time} due to {reason}.")
103+
return closing_time, reason
45104

46105

47106
async def get_in_use_time(channel_id: int) -> t.Optional[timedelta]:
@@ -50,8 +109,8 @@ async def get_in_use_time(channel_id: int) -> t.Optional[timedelta]:
50109

51110
claimed_timestamp = await _caches.claim_times.get(channel_id)
52111
if claimed_timestamp:
53-
claimed = datetime.utcfromtimestamp(claimed_timestamp)
54-
return datetime.utcnow() - claimed
112+
claimed = Arrow.utcfromtimestamp(claimed_timestamp)
113+
return arrow.utcnow() - claimed
55114

56115

57116
def is_excluded_channel(channel: discord.abc.GuildChannel) -> bool:

0 commit comments

Comments
 (0)