1
- from datetime import UTC , datetime , timedelta
1
+ from datetime import datetime
2
2
from typing import Literal
3
3
4
4
from async_rediscache import RedisCache
9
9
10
10
from bot .bot import Bot
11
11
from bot .constants import Channels , Emojis , MODERATION_ROLES
12
- from bot .converters import DurationDelta
12
+ from bot .converters import Duration , DurationDelta
13
13
from bot .log import get_logger
14
14
from bot .utils import time
15
15
from bot .utils .time import TimestampFormats , discord_timestamp
30
30
class Slowmode (Cog ):
31
31
"""Commands for getting and setting slowmode delays of text channels."""
32
32
33
- # Stores the expiration timestamp in POSIX format for active slowmodes, keyed by channel ID.
34
- slowmode_expiration_cache = RedisCache ()
35
-
36
- # Stores the original slowmode interval by channel ID, allowing its restoration after temporary slowmode expires.
37
- original_slowmode_cache = RedisCache ()
33
+ # RedisCache[discord.channel.id : f"{delay}, {expiry}"]
34
+ # `delay` is the slowmode delay assigned to the text channel.
35
+ # `expiry` is a naïve ISO 8601 string which describes when the slowmode should be removed.
36
+ slowmode_cache = RedisCache ()
38
37
39
38
def __init__ (self , bot : Bot ) -> None :
40
39
self .bot = bot
@@ -53,8 +52,8 @@ async def get_slowmode(self, ctx: Context, channel: MessageHolder) -> None:
53
52
channel = ctx .channel
54
53
55
54
humanized_delay = time .humanize_delta (seconds = channel .slowmode_delay )
56
- if await self .slowmode_expiration_cache .contains (channel .id ):
57
- expiration_time = await self .slowmode_expiration_cache .get (channel .id )
55
+ if await self .slowmode_cache .contains (channel .id ):
56
+ expiration_time = await self .slowmode_cache .get (channel .id ). split ( ", " )[ 1 ]
58
57
expiration_timestamp = discord_timestamp (expiration_time , TimestampFormats .RELATIVE )
59
58
await ctx .send (
60
59
f"The slowmode delay for { channel .mention } is { humanized_delay } and expires in { expiration_timestamp } ."
@@ -68,12 +67,12 @@ async def set_slowmode(
68
67
ctx : Context ,
69
68
channel : MessageHolder ,
70
69
delay : DurationDelta | Literal ["0s" , "0seconds" ],
71
- duration : DurationDelta | None = None
70
+ expiry : Duration | None = None
72
71
) -> None :
73
72
"""
74
73
Set the slowmode delay for a text channel.
75
74
76
- Supports temporary slowmodes with the `duration ` argument that automatically
75
+ Supports temporary slowmodes with the `expiry ` argument that automatically
77
76
revert to the original delay after expiration.
78
77
"""
79
78
# Use the channel this command was invoked in if one was not given
@@ -100,32 +99,31 @@ async def set_slowmode(
100
99
)
101
100
return
102
101
103
- if duration is not None :
104
- slowmode_duration = time .relativedelta_to_timedelta (duration ).total_seconds ()
105
- humanized_duration = time .humanize_delta (duration )
106
-
107
- expiration_time = datetime .now (tz = UTC ) + timedelta (seconds = slowmode_duration )
108
- expiration_timestamp = discord_timestamp (expiration_time , TimestampFormats .RELATIVE )
102
+ if expiry is not None :
103
+ humanized_expiry = time .humanize_delta (expiry )
104
+ expiration_timestamp = discord_timestamp (expiry , TimestampFormats .RELATIVE )
109
105
110
- # Only update original_slowmode_cache if the last slowmode was not temporary.
111
- if not await self .slowmode_expiration_cache .contains (channel .id ):
112
- await self .original_slowmode_cache .set (channel .id , channel .slowmode_delay )
113
- await self .slowmode_expiration_cache .set (channel .id , expiration_time .timestamp ())
106
+ # Only cache the original slowmode delay if there is not already an ongoing temporary slowmode.
107
+ if not await self .slowmode_cache .contains (channel .id ):
108
+ await self .slowmode_cache .set (channel .id , f"{ channel .slowmode_delay } , { expiry } " )
109
+ else :
110
+ cached_delay = await self .slowmode_cache .get (channel .id )
111
+ await self .slowmode_cache .set (channel .id , f"{ cached_delay } , { expiry } " )
112
+ self .scheduler .cancel (channel .id )
114
113
115
- self .scheduler .schedule_at (expiration_time , channel .id , self ._revert_slowmode (channel .id ))
114
+ self .scheduler .schedule_at (expiry , channel .id , self ._revert_slowmode (channel .id ))
116
115
log .info (
117
116
f"{ ctx .author } set the slowmode delay for #{ channel } to"
118
- f"{ humanized_delay } which expires in { humanized_duration } ."
117
+ f"{ humanized_delay } which expires in { humanized_expiry } ."
119
118
)
120
119
await channel .edit (slowmode_delay = slowmode_delay )
121
120
await ctx .send (
122
121
f"{ Emojis .check_mark } The slowmode delay for { channel .mention } "
123
122
f" is now { humanized_delay } and expires in { expiration_timestamp } ."
124
123
)
125
124
else :
126
- if await self .slowmode_expiration_cache .contains (channel .id ):
127
- await self .slowmode_expiration_cache .delete (channel .id )
128
- await self .original_slowmode_cache .delete (channel .id )
125
+ if await self .slowmode_cache .contains (channel .id ):
126
+ await self .slowmode_cache .delete (channel .id )
129
127
self .scheduler .cancel (channel .id )
130
128
131
129
log .info (f"{ ctx .author } set the slowmode delay for #{ channel } to { humanized_delay } ." )
@@ -139,33 +137,33 @@ async def set_slowmode(
139
137
140
138
async def _reschedule (self ) -> None :
141
139
log .trace ("Rescheduling the expiration of temporary slowmodes from cache." )
142
- for channel_id , expiration in await self .slowmode_expiration_cache .items ():
143
- expiration_datetime = datetime .fromtimestamp (expiration , tz = UTC )
140
+ for channel_id , cached_data in await self .slowmode_cache .items ():
141
+ expiration = cached_data .split (", " )[1 ]
142
+ expiration_datetime = datetime .fromisoformat (expiration )
144
143
channel = self .bot .get_channel (channel_id )
145
144
log .info (f"Rescheduling slowmode expiration for #{ channel } ({ channel_id } )." )
146
145
self .scheduler .schedule_at (expiration_datetime , channel_id , self ._revert_slowmode (channel_id ))
147
146
148
147
async def _revert_slowmode (self , channel_id : int ) -> None :
149
- original_slowmode = await self .original_slowmode_cache .get (channel_id )
148
+ cached_data = await self .slowmode_cache .get (channel_id )
149
+ original_slowmode = int (cached_data .split (", " )[0 ])
150
150
slowmode_delay = time .humanize_delta (seconds = original_slowmode )
151
151
channel = self .bot .get_channel (channel_id )
152
152
log .info (f"Slowmode in #{ channel } ({ channel .id } ) has expired and has reverted to { slowmode_delay } ." )
153
153
await channel .edit (slowmode_delay = original_slowmode )
154
154
await channel .send (
155
155
f"{ Emojis .check_mark } A previously applied slowmode has expired and has been reverted to { slowmode_delay } ."
156
156
)
157
- await self .slowmode_expiration_cache .delete (channel .id )
158
- await self .original_slowmode_cache .delete (channel .id )
157
+ await self .slowmode_cache .delete (channel .id )
159
158
160
159
@slowmode_group .command (name = "reset" , aliases = ["r" ])
161
160
async def reset_slowmode (self , ctx : Context , channel : MessageHolder ) -> None :
162
161
"""Reset the slowmode delay for a text channel to 0 seconds."""
163
162
await self .set_slowmode (ctx , channel , relativedelta (seconds = 0 ))
164
163
if channel is None :
165
164
channel = ctx .channel
166
- if await self .slowmode_expiration_cache .contains (channel .id ):
167
- await self .slowmode_expiration_cache .delete (channel .id )
168
- await self .original_slowmode_cache .delete (channel .id )
165
+ if await self .slowmode_cache .contains (channel .id ):
166
+ await self .slowmode_cache .delete (channel .id )
169
167
self .scheduler .cancel (channel .id )
170
168
171
169
async def cog_check (self , ctx : Context ) -> bool :
0 commit comments