11import asyncio
22import re
3+ import time
34from datetime import datetime , timedelta , timezone
45from itertools import batched
56from typing import List , Literal , Optional , Tuple , Union
2627class Modmail (commands .Cog ):
2728 """Commands directly related to Modmail functionality."""
2829
29- def __init__ (self , bot ):
30+ def __init__ (self , bot : ModmailBot ):
3031 self .bot : ModmailBot = bot
3132 self ._snoozed_cache = []
3233 self ._auto_unsnooze_task = self .bot .loop .create_task (self .auto_unsnooze_task ())
3334
3435 @staticmethod
35- def _to_utc_datetime (value ):
36+ def _to_utc_datetime (value ) -> Optional [ datetime ] :
3637 if value is None :
3738 return None
3839 if isinstance (value , datetime ):
@@ -47,21 +48,18 @@ def _to_utc_datetime(value):
4748
4849 async def auto_unsnooze_task (self ):
4950 await self .bot .wait_until_ready ()
50- last_db_query = 0
51+ logger .debug ("auto_unsnooze_task" )
52+ last_db_query : int | float = 0
5153 while not self .bot .is_closed ():
52- now = datetime . now ( timezone . utc )
54+ now = time . monotonic ( )
5355 try :
5456 # Query DB every 2 minutes
55- if (now .timestamp () - last_db_query ) > 120 :
56- snoozed_threads = await self .bot .api .logs .find (
57- {
58- "$or" : [
59- {"snooze_until" : {"$gte" : now }},
60- ]
61- }
62- ).to_list (None )
57+ if (now - last_db_query ) > 120 :
58+ snoozed_threads = await self .bot .api .logs .find ({"snoozed" : True , "open" : True }).to_list (
59+ None
60+ )
6361 self ._snoozed_cache = snoozed_threads or []
64- last_db_query = now . timestamp ()
62+ last_db_query = time . monotonic ()
6563 # Check cache every 10 seconds
6664 to_unsnooze = []
6765 for thread_data in list (self ._snoozed_cache ):
@@ -74,7 +72,7 @@ async def auto_unsnooze_task(self):
7472 dt = self ._to_utc_datetime (snooze_until )
7573 if dt is None :
7674 continue
77- if now >= dt :
75+ if datetime . now ( timezone . utc ) >= dt :
7876 to_unsnooze .append (thread_data )
7977 for thread_data in to_unsnooze :
8078 recipient = thread_data .get ("recipient" )
@@ -92,7 +90,7 @@ async def auto_unsnooze_task(self):
9290 if channel :
9391 await channel .send ("⏰ This thread has been automatically unsnoozed." )
9492 except Exception as e :
95- logger .info (
93+ logger .error (
9694 "Failed to notify channel after auto-unsnooze: %s" ,
9795 e ,
9896 )
@@ -101,7 +99,7 @@ async def auto_unsnooze_task(self):
10199 logger .error (f"Error in auto_unsnooze_task: { e } " )
102100 await asyncio .sleep (10 )
103101
104- def _resolve_user (self , user_str ) :
102+ def _resolve_user (self , user_str : str ) -> Optional [ int ] :
105103 """Helper to resolve a user from mention, ID, or username."""
106104 import re
107105
@@ -319,11 +317,11 @@ async def snippet_add(self, ctx, name: str.lower, *, value: commands.clean_conte
319317 """
320318 Add a snippet.
321319
322- Simply to add a snippet, do:
320+ Simply to add a snippet, do:
323321 `{prefix}snippet add hey hello there :)`
324322 then when you type `{prefix}hey`, "hello there :)" will get sent to the recipient.
325323
326- To add a multi-word snippet name, use quotes:
324+ To add a multi-word snippet name, use quotes:
327325 `{prefix}snippet add "two word" this is a two word snippet.`
328326 """
329327 if self ._validate_snippet_name (name ):
@@ -1640,14 +1638,17 @@ async def pareply(self, ctx: ModmailCommandContext, *, msg: str = ""):
16401638 async with safe_typing (ctx ):
16411639 await ctx .thread .reply (ctx .message , msg , anonymous = True , plain = True )
16421640
1643-
1644- async def _create_note (self , message : discord .Message , msg : str , thread : Thread , persistent : bool ) -> Optional [discord .Message ]:
1641+ async def _create_note (
1642+ self , message : discord .Message , msg : str , thread : Thread , persistent : bool
1643+ ) -> Optional [discord .Message ]:
16451644 message .content = msg
16461645 async with safe_typing (message .channel ):
16471646 note_message = await thread .note (message , persistent )
16481647 await note_message .pin ()
16491648 if persistent :
1650- await self .bot .api .create_note (recipient = thread .recipient , message = message , message_id = note_message .id )
1649+ await self .bot .api .create_note (
1650+ recipient = thread .recipient , message = message , message_id = note_message .id
1651+ )
16511652 # Acknowledge and clean up the invoking command message
16521653 sent_emoji , _ = await self .bot .retrieve_emoji ()
16531654 await self .bot .add_reaction (message , sent_emoji )
@@ -1829,7 +1830,9 @@ async def contact(
18291830 try :
18301831 await msg .delete (delay = 10 )
18311832 except (discord .Forbidden , discord .NotFound ) as e :
1832- logger .debug (f"Failed to delete message (likely already deleted or lacking permissions): { e } " )
1833+ logger .debug (
1834+ f"Failed to delete message (likely already deleted or lacking permissions): { e } "
1835+ )
18331836 # Don't try to create a new thread - we just unsnoozed existing ones
18341837 return
18351838
@@ -2448,9 +2451,9 @@ async def snooze(self, ctx: ModmailCommandContext, *, duration: UserFriendlyTime
24482451 try :
24492452 logger .debug ("Auto-creating snoozed category for move-based snoozing." )
24502453 # Hide category by default; only bot can view/manage
2451- overwrites : dict [Union [ discord . Role , discord . Member , discord . Object ], discord . PermissionOverwrite ] = {
2452- self . bot . modmail_guild . default_role : discord .PermissionOverwrite ( view_channel = False )
2453- }
2454+ overwrites : dict [
2455+ Union [ discord . Role , discord . Member , discord . Object ], discord .PermissionOverwrite
2456+ ] = { self . bot . modmail_guild . default_role : discord . PermissionOverwrite ( view_channel = False ) }
24542457 bot_member = self .bot .modmail_guild .me
24552458 if bot_member is not None :
24562459 overwrites [bot_member ] = discord .PermissionOverwrite (
@@ -2573,7 +2576,7 @@ async def unsnooze(self, ctx: ModmailCommandContext, *, user: str | None = None)
25732576 assert self .bot .threads is not None
25742577
25752578 thread : Thread | None = None
2576-
2579+
25772580 user_obj = None
25782581 if user is not None :
25792582 user_id = self ._resolve_user (user )
@@ -2607,7 +2610,9 @@ async def unsnooze(self, ctx: ModmailCommandContext, *, user: str | None = None)
26072610
26082611 # Manually fetch snooze_data if the thread object doesn't have it
26092612 if not thread .snooze_data :
2610- log_entry = await self .bot .api .logs .find_one ({"recipient.id" : str (thread .id ), "snoozed" : True , "_id" : str (thread .key )})
2613+ log_entry = await self .bot .api .logs .find_one (
2614+ {"recipient.id" : str (thread .id ), "snoozed" : True , "_id" : str (thread .key )}
2615+ )
26112616 if log_entry :
26122617 thread .snooze_data = log_entry .get ("snooze_data" )
26132618 else :
@@ -2637,7 +2642,6 @@ async def snoozed(self, ctx: commands.Context):
26372642 await ctx .send ("No threads are currently snoozed." )
26382643 return
26392644
2640-
26412645 # TODO this should really be a
26422646 lines = []
26432647 now = datetime .now (timezone .utc )
@@ -2667,9 +2671,7 @@ async def snoozed(self, ctx: commands.Context):
26672671 until_dt = since_dt + timedelta (seconds = int (duration ))
26682672 until_str = f"<t:{ int (until_dt .timestamp ())} :R>"
26692673 except (ValueError , TypeError ) as e :
2670- logger .warning (
2671- f"Invalid until time for { user_id } : { since } + { duration } ({ e } )"
2672- )
2674+ logger .warning (f"Invalid until time for { user_id } : { since } + { duration } ({ e } )" )
26732675
26742676 lines .append (f"- { user } (`{ user_id } `) since { since_str } , until { until_str } " )
26752677
@@ -2680,8 +2682,11 @@ async def cog_load(self):
26802682
26812683 @tasks .loop (seconds = 10 )
26822684 async def snooze_auto_unsnooze (self ):
2685+ logger .debug ("snooze_auto_unsnooze" )
26832686 now = datetime .now (timezone .utc )
2684- snoozed = await self .bot .api .logs .find ({"snoozed" : True , "open" : True , "snooze_until" : {"$lte" : now }}).to_list (None )
2687+ snoozed = await self .bot .api .logs .find (
2688+ {"snoozed" : True , "open" : True , "snooze_until" : {"$lte" : now }}
2689+ ).to_list (None )
26852690 for entry in snoozed :
26862691 snooze_until = entry .get ("snooze_until" )
26872692 if snooze_until :
0 commit comments