1- __version__ = "3.3.1-dev1 "
1+ __version__ = "3.3.1-dev2 "
22
33import asyncio
44import logging
@@ -529,122 +529,171 @@ async def retrieve_emoji(self) -> typing.Tuple[str, str]:
529529
530530 return sent_emoji , blocked_emoji
531531
532- async def _process_blocked (self , message : discord .Message ) -> typing .Tuple [bool , str ]:
533- sent_emoji , blocked_emoji = await self .retrieve_emoji ()
534-
535- if str (message .author .id ) in self .blocked_whitelisted_users :
536- if str (message .author .id ) in self .blocked_users :
537- self .blocked_users .pop (str (message .author .id ))
538- await self .config .update ()
539-
540- return False , sent_emoji
541-
542- now = datetime .utcnow ()
543-
532+ def check_account_age (self , author : discord .Member ) -> bool :
544533 account_age = self .config .get ("account_age" )
545- guild_age = self .config .get ("guild_age" )
546-
547- if account_age is None :
548- account_age = isodate .Duration ()
549- if guild_age is None :
550- guild_age = isodate .Duration ()
551-
552- reason = self .blocked_users .get (str (message .author .id )) or ""
553- min_guild_age = min_account_age = now
534+ now = datetime .utcnow ()
554535
555536 try :
556- min_account_age = message . author .created_at + account_age
537+ min_account_age = author .created_at + account_age
557538 except ValueError :
558539 logger .warning ("Error with 'account_age'." , exc_info = True )
559- self .config .remove ("account_age" )
560-
561- try :
562- joined_at = getattr (message .author , "joined_at" , None )
563- if joined_at is not None :
564- min_guild_age = joined_at + guild_age
565- except ValueError :
566- logger .warning ("Error with 'guild_age'." , exc_info = True )
567- self .config .remove ("guild_age" )
540+ min_account_age = author .created_at + self .config .remove ("account_age" )
568541
569542 if min_account_age > now :
570543 # User account has not reached the required time
571- reaction = blocked_emoji
572- changed = False
573544 delta = human_timedelta (min_account_age )
574- logger .debug ("Blocked due to account age, user %s." , message . author .name )
545+ logger .debug ("Blocked due to account age, user %s." , author .name )
575546
576- if str (message . author .id ) not in self .blocked_users :
547+ if str (author .id ) not in self .blocked_users :
577548 new_reason = f"System Message: New Account. Required to wait for { delta } ."
578- self .blocked_users [str (message .author .id )] = new_reason
579- changed = True
549+ self .blocked_users [str (author .id )] = new_reason
580550
581- if reason .startswith ("System Message: New Account." ) or changed :
582- await message .channel .send (
583- embed = discord .Embed (
584- title = "Message not sent!" ,
585- description = f"Your must wait for { delta } before you can contact me." ,
586- color = self .error_color ,
587- )
588- )
551+ return False
552+ return True
553+
554+ def check_guild_age (self , author : discord .Member ) -> bool :
555+ guild_age = self .config .get ("guild_age" )
556+ now = datetime .utcnow ()
557+
558+ if not hasattr (author , "joined_at" ):
559+ logger .warning ("Not in guild, cannot verify guild_age, %s." , author .name )
560+ return True
589561
590- elif min_guild_age > now :
562+ try :
563+ min_guild_age = author .joined_at + guild_age
564+ except ValueError :
565+ logger .warning ("Error with 'guild_age'." , exc_info = True )
566+ min_guild_age = author .joined_at + self .config .remove ("guild_age" )
567+
568+ if min_guild_age > now :
591569 # User has not stayed in the guild for long enough
592- reaction = blocked_emoji
593- changed = False
594570 delta = human_timedelta (min_guild_age )
595- logger .debug ("Blocked due to guild age, user %s." , message . author .name )
571+ logger .debug ("Blocked due to guild age, user %s." , author .name )
596572
597- if str (message . author .id ) not in self .blocked_users :
573+ if str (author .id ) not in self .blocked_users :
598574 new_reason = f"System Message: Recently Joined. Required to wait for { delta } ."
599- self .blocked_users [str (message .author .id )] = new_reason
600- changed = True
575+ self .blocked_users [str (author .id )] = new_reason
601576
602- if reason .startswith ("System Message: Recently Joined." ) or changed :
603- await message .channel .send (
604- embed = discord .Embed (
605- title = "Message not sent!" ,
606- description = f"Your must wait for { delta } before you can contact me." ,
607- color = self .error_color ,
608- )
577+ return False
578+ return True
579+
580+ def check_manual_blocked (self , author : discord .Member ) -> bool :
581+ if str (author .id ) not in self .blocked_users :
582+ return True
583+
584+ blocked_reason = self .blocked_users .get (str (author .id )) or ""
585+ now = datetime .utcnow ()
586+
587+ if blocked_reason .startswith ("System Message:" ):
588+ # Met the limits already, otherwise it would've been caught by the previous checks
589+ logger .debug ("No longer internally blocked, user %s." , author .name )
590+ self .blocked_users .pop (str (author .id ))
591+ return True
592+ # etc "blah blah blah... until 2019-10-14T21:12:45.559948."
593+ end_time = re .search (r"until ([^`]+?)\.$" , blocked_reason )
594+ if end_time is None :
595+ # backwards compat
596+ end_time = re .search (r"%([^%]+?)%" , blocked_reason )
597+ if end_time is not None :
598+ logger .warning (
599+ r"Deprecated time message for user %s, block and unblock again to update." ,
600+ author .name ,
609601 )
610602
611- elif str (message .author .id ) in self .blocked_users :
612- if reason .startswith ("System Message: New Account." ) or reason .startswith (
613- "System Message: Recently Joined."
614- ):
615- # Met the age limit already, otherwise it would've been caught by the previous if's
616- reaction = sent_emoji
617- logger .debug ("No longer internally blocked, user %s." , message .author .name )
618- self .blocked_users .pop (str (message .author .id ))
619- else :
620- reaction = blocked_emoji
621- # etc "blah blah blah... until 2019-10-14T21:12:45.559948."
622- end_time = re .search (r"until ([^`]+?)\.$" , reason )
623- if end_time is None :
624- # backwards compat
625- end_time = re .search (r"%([^%]+?)%" , reason )
626- if end_time is not None :
627- logger .warning (
628- r"Deprecated time message for user %s, block and unblock again to update." ,
629- message .author ,
603+ if end_time is not None :
604+ after = (datetime .fromisoformat (end_time .group (1 )) - now ).total_seconds ()
605+ if after <= 0 :
606+ # No longer blocked
607+ self .blocked_users .pop (str (author .id ))
608+ logger .debug ("No longer blocked, user %s." , author .name )
609+ return True
610+ logger .debug ("User blocked, user %s." , author .name )
611+ return False
612+
613+ async def _process_blocked (self , message ):
614+ sent_emoji , blocked_emoji = await self .retrieve_emoji ()
615+ if await self .is_blocked (message .author , channel = message .channel , send_message = True ):
616+ await self .add_reaction (message , blocked_emoji )
617+ return True
618+ return False
619+
620+ async def is_blocked (
621+ self ,
622+ author : discord .User ,
623+ * ,
624+ channel : discord .TextChannel = None ,
625+ send_message : bool = False ,
626+ ) -> typing .Tuple [bool , str ]:
627+
628+ member = self .guild .get_member (author .id )
629+ if member is None :
630+ logger .debug ("User not in guild, %s." , author .id )
631+ else :
632+ author = member
633+
634+ if str (author .id ) in self .blocked_whitelisted_users :
635+ if str (author .id ) in self .blocked_users :
636+ self .blocked_users .pop (str (author .id ))
637+ await self .config .update ()
638+ return False
639+
640+ blocked_reason = self .blocked_users .get (str (author .id )) or ""
641+
642+ if (
643+ not self .check_account_age (author )
644+ or not self .check_guild_age (author )
645+ ):
646+ new_reason = self .blocked_users .get (str (author .id ))
647+ if new_reason != blocked_reason :
648+ if send_message :
649+ await channel .send (
650+ embed = discord .Embed (
651+ title = "Message not sent!" ,
652+ description = new_reason ,
653+ color = self .error_color ,
630654 )
655+ )
656+ return True
631657
632- if end_time is not None :
633- after = (datetime .fromisoformat (end_time .group (1 )) - now ).total_seconds ()
634- if after <= 0 :
635- # No longer blocked
636- reaction = sent_emoji
637- self .blocked_users .pop (str (message .author .id ))
638- logger .debug ("No longer blocked, user %s." , message .author .name )
639- else :
640- logger .debug ("User blocked, user %s." , message .author .name )
641- else :
642- logger .debug ("User blocked, user %s." , message .author .name )
643- else :
644- reaction = sent_emoji
658+ if not self .check_manual_blocked (author ):
659+ return True
645660
646661 await self .config .update ()
647- return str (message .author .id ) in self .blocked_users , reaction
662+ return False
663+
664+ async def get_thread_cooldown (self , author : discord .Member ):
665+ thread_cooldown = self .config .get ("thread_cooldown" )
666+ now = datetime .utcnow ()
667+
668+ if thread_cooldown == isodate .Duration ():
669+ return
670+
671+ last_log = await self .api .get_latest_user_logs (author .id )
672+
673+ if last_log is None :
674+ logger .debug ("Last thread wasn't found, %s." , author .name )
675+ return
676+
677+ last_log_closed_at = last_log .get ("closed_at" )
678+
679+ if not last_log_closed_at :
680+ logger .debug ("Last thread was not closed, %s." , author .name )
681+ return
682+
683+ try :
684+ cooldown = datetime .fromisoformat (last_log_closed_at ) + thread_cooldown
685+ except ValueError :
686+ logger .warning ("Error with 'thread_cooldown'." , exc_info = True )
687+ cooldown = datetime .fromisoformat (last_log_closed_at ) + self .config .remove (
688+ "thread_cooldown"
689+ )
690+
691+ if cooldown > now :
692+ # User messaged before thread cooldown ended
693+ delta = human_timedelta (cooldown )
694+ logger .debug ("Blocked due to thread cooldown, user %s." , author .name )
695+ return delta
696+ return
648697
649698 @staticmethod
650699 async def add_reaction (msg , reaction ):
@@ -656,11 +705,24 @@ async def add_reaction(msg, reaction):
656705
657706 async def process_dm_modmail (self , message : discord .Message ) -> None :
658707 """Processes messages sent to the bot."""
659- blocked , reaction = await self ._process_blocked (message )
708+ blocked = await self ._process_blocked (message )
660709 if blocked :
661- return await self .add_reaction (message , reaction )
710+ return
711+ sent_emoji , blocked_emoji = await self .retrieve_emoji ()
712+
662713 thread = await self .threads .find (recipient = message .author )
663714 if thread is None :
715+ delta = await self .get_thread_cooldown (message .author )
716+ if delta :
717+ await message .channel .send (
718+ embed = discord .Embed (
719+ title = "Message not sent!" ,
720+ description = f"You must wait for { delta } before you can contact me again." ,
721+ color = self .error_color ,
722+ )
723+ )
724+ return
725+
664726 if self .config ["dm_disabled" ] >= 1 :
665727 embed = discord .Embed (
666728 title = self .config ["disabled_new_thread_title" ],
@@ -673,9 +735,9 @@ async def process_dm_modmail(self, message: discord.Message) -> None:
673735 logger .info (
674736 "A new thread was blocked from %s due to disabled Modmail." , message .author
675737 )
676- _ , blocked_emoji = await self .retrieve_emoji ()
677738 await self .add_reaction (message , blocked_emoji )
678739 return await message .channel .send (embed = embed )
740+
679741 thread = self .threads .create (message .author )
680742 else :
681743 if self .config ["dm_disabled" ] == 2 :
@@ -691,12 +753,16 @@ async def process_dm_modmail(self, message: discord.Message) -> None:
691753 logger .info (
692754 "A message was blocked from %s due to disabled Modmail." , message .author
693755 )
694- _ , blocked_emoji = await self .retrieve_emoji ()
695756 await self .add_reaction (message , blocked_emoji )
696757 return await message .channel .send (embed = embed )
697758
698- await self .add_reaction (message , reaction )
699- await thread .send (message )
759+ try :
760+ await thread .send (message )
761+ except Exception :
762+ logger .error ("Failed to send message:" , exc_info = True )
763+ await self .add_reaction (message , blocked_emoji )
764+ else :
765+ await self .add_reaction (message , sent_emoji )
700766
701767 async def get_contexts (self , message , * , cls = commands .Context ):
702768 """
@@ -849,9 +915,6 @@ async def on_typing(self, channel, user, _):
849915 if user .bot :
850916 return
851917
852- async def _void (* _args , ** _kwargs ):
853- pass
854-
855918 if isinstance (channel , discord .DMChannel ):
856919 if not self .config .get ("user_typing" ):
857920 return
@@ -866,13 +929,7 @@ async def _void(*_args, **_kwargs):
866929
867930 thread = await self .threads .find (channel = channel )
868931 if thread is not None and thread .recipient :
869- if (
870- await self ._process_blocked (
871- SimpleNamespace (
872- author = thread .recipient , channel = SimpleNamespace (send = _void )
873- )
874- )
875- )[0 ]:
932+ if await self .is_blocked (thread .recipient ):
876933 return
877934 await thread .recipient .trigger_typing ()
878935
0 commit comments