1- from datetime import UTC , datetime , timedelta
1+ from datetime import datetime
22from typing import Literal
33
44from async_rediscache import RedisCache
99
1010from bot .bot import Bot
1111from bot .constants import Channels , Emojis , MODERATION_ROLES
12- from bot .converters import DurationDelta
12+ from bot .converters import Duration , DurationDelta
1313from bot .log import get_logger
1414from bot .utils import time
1515from bot .utils .time import TimestampFormats , discord_timestamp
3030class Slowmode (Cog ):
3131 """Commands for getting and setting slowmode delays of text channels."""
3232
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 ()
3837
3938 def __init__ (self , bot : Bot ) -> None :
4039 self .bot = bot
@@ -53,8 +52,8 @@ async def get_slowmode(self, ctx: Context, channel: MessageHolder) -> None:
5352 channel = ctx .channel
5453
5554 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 ]
5857 expiration_timestamp = discord_timestamp (expiration_time , TimestampFormats .RELATIVE )
5958 await ctx .send (
6059 f"The slowmode delay for { channel .mention } is { humanized_delay } and expires in { expiration_timestamp } ."
@@ -68,12 +67,12 @@ async def set_slowmode(
6867 ctx : Context ,
6968 channel : MessageHolder ,
7069 delay : DurationDelta | Literal ["0s" , "0seconds" ],
71- duration : DurationDelta | None = None
70+ expiry : Duration | None = None
7271 ) -> None :
7372 """
7473 Set the slowmode delay for a text channel.
7574
76- Supports temporary slowmodes with the `duration ` argument that automatically
75+ Supports temporary slowmodes with the `expiry ` argument that automatically
7776 revert to the original delay after expiration.
7877 """
7978 # Use the channel this command was invoked in if one was not given
@@ -100,32 +99,31 @@ async def set_slowmode(
10099 )
101100 return
102101
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 )
109105
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 )
114113
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 ))
116115 log .info (
117116 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 } ."
119118 )
120119 await channel .edit (slowmode_delay = slowmode_delay )
121120 await ctx .send (
122121 f"{ Emojis .check_mark } The slowmode delay for { channel .mention } "
123122 f" is now { humanized_delay } and expires in { expiration_timestamp } ."
124123 )
125124 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 )
129127 self .scheduler .cancel (channel .id )
130128
131129 log .info (f"{ ctx .author } set the slowmode delay for #{ channel } to { humanized_delay } ." )
@@ -139,33 +137,33 @@ async def set_slowmode(
139137
140138 async def _reschedule (self ) -> None :
141139 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 )
144143 channel = self .bot .get_channel (channel_id )
145144 log .info (f"Rescheduling slowmode expiration for #{ channel } ({ channel_id } )." )
146145 self .scheduler .schedule_at (expiration_datetime , channel_id , self ._revert_slowmode (channel_id ))
147146
148147 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 ])
150150 slowmode_delay = time .humanize_delta (seconds = original_slowmode )
151151 channel = self .bot .get_channel (channel_id )
152152 log .info (f"Slowmode in #{ channel } ({ channel .id } ) has expired and has reverted to { slowmode_delay } ." )
153153 await channel .edit (slowmode_delay = original_slowmode )
154154 await channel .send (
155155 f"{ Emojis .check_mark } A previously applied slowmode has expired and has been reverted to { slowmode_delay } ."
156156 )
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 )
159158
160159 @slowmode_group .command (name = "reset" , aliases = ["r" ])
161160 async def reset_slowmode (self , ctx : Context , channel : MessageHolder ) -> None :
162161 """Reset the slowmode delay for a text channel to 0 seconds."""
163162 await self .set_slowmode (ctx , channel , relativedelta (seconds = 0 ))
164163 if channel is None :
165164 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 )
169167 self .scheduler .cancel (channel .id )
170168
171169 async def cog_check (self , ctx : Context ) -> bool :
0 commit comments