1919from aiohttp import ClientSession
2020from emoji import UNICODE_EMOJI
2121from motor .motor_asyncio import AsyncIOMotorClient
22+ from pymongo .errors import ConfigurationError
2223
2324try :
2425 from colorama import init
@@ -65,6 +66,8 @@ def __init__(self):
6566 super ().__init__ (command_prefix = None ) # implemented in `get_prefix`
6667 self ._session = None
6768 self ._api = None
69+ self .metadata_loop = None
70+
6871 self ._connected = asyncio .Event ()
6972 self .start_time = datetime .utcnow ()
7073
@@ -77,13 +80,17 @@ def __init__(self):
7780
7881 mongo_uri = self .config ["mongo_uri" ]
7982 if mongo_uri is None :
80- raise ValueError ("A Mongo URI is necessary for the bot to function." )
81-
82- self .db = AsyncIOMotorClient (mongo_uri ).modmail_bot
83- self .plugin_db = PluginDatabaseClient (self )
83+ logger .critical ("A Mongo URI is necessary for the bot to function." )
84+ raise RuntimeError
8485
85- self .metadata_loop = None
86+ try :
87+ self .db = AsyncIOMotorClient (mongo_uri ).modmail_bot
88+ except ConfigurationError as e :
89+ logger .critical ("Your MONGO_URI is copied wrong, try re-copying from the source again." )
90+ logger .critical (str (e ))
91+ sys .exit (0 )
8692
93+ self .plugin_db = PluginDatabaseClient (self )
8794 self ._load_extensions ()
8895
8996 @property
@@ -134,7 +141,8 @@ def _configure_logging(self):
134141 logger .info ("Logging level: %s" , level_text )
135142 else :
136143 logger .info ("Invalid logging level set." )
137- logger .warning ("Using default logging level: INFO." )
144+ logger .warning ("Using default logging level: %s." , level_text )
145+ logger .debug ("Successfully configured logging." )
138146
139147 @property
140148 def version (self ) -> str :
@@ -198,11 +206,21 @@ def run(self, *args, **kwargs):
198206 self .loop .run_until_complete (self .session .close ())
199207 logger .error (" - Shutting down bot - " )
200208
209+ @property
210+ def owner_ids (self ):
211+ owner_ids = self .config ["owners" ]
212+ if owner_ids is not None :
213+ owner_ids = set (map (int , str (owner_ids ).split ("," )))
214+ if self .owner_id is not None :
215+ owner_ids .add (self .owner_id )
216+ permissions = self .config ["level_permissions" ].get (PermissionLevel .OWNER .name , [])
217+ for perm in permissions :
218+ owner_ids .add (int (perm ))
219+ return owner_ids
220+
201221 async def is_owner (self , user : discord .User ) -> bool :
202- owners = self .config ["owners" ]
203- if owners is not None :
204- if user .id in set (map (int , str (owners ).split ("," ))):
205- return True
222+ if user .id in self .owner_ids :
223+ return True
206224 return await super ().is_owner (user )
207225
208226 @property
@@ -212,18 +230,18 @@ def log_channel(self) -> typing.Optional[discord.TextChannel]:
212230 channel = self .get_channel (int (channel_id ))
213231 if channel is not None :
214232 return channel
233+ logger .debug ('LOG_CHANNEL_ID was invalid, removed.' )
215234 self .config .remove ("log_channel_id" )
216235 if self .main_category is not None :
217236 try :
218237 channel = self .main_category .channels [0 ]
219238 self .config ["log_channel_id" ] = channel .id
220- logger .debug ("No log channel set, however, one was found. Setting..." )
239+ logger .warning ("No log channel set, setting #%s to be the log channel." , channel . name )
221240 return channel
222241 except IndexError :
223242 pass
224- logger .info (
225- "No log channel set, set one with `%ssetup` or "
226- "`%sconfig set log_channel_id <id>`." ,
243+ logger .warning (
244+ "No log channel set, set one with `%ssetup` or `%sconfig set log_channel_id <id>`." ,
227245 self .prefix ,
228246 self .prefix ,
229247 )
@@ -250,7 +268,8 @@ def aliases(self) -> typing.Dict[str, str]:
250268 def token (self ) -> str :
251269 token = self .config ["token" ]
252270 if token is None :
253- raise ValueError ("TOKEN must be set, this is your bot token." )
271+ logger .critical ("TOKEN must be set, set this as bot token found on the Discord Dev Portal." )
272+ sys .exit (0 )
254273 return token
255274
256275 @property
@@ -260,7 +279,7 @@ def guild_id(self) -> typing.Optional[int]:
260279 try :
261280 return int (str (guild_id ))
262281 except ValueError :
263- raise ValueError ("Invalid guild_id set." )
282+ logger . critical ("Invalid GUILD_ID set." )
264283 return None
265284
266285 @property
@@ -284,7 +303,7 @@ def modmail_guild(self) -> typing.Optional[discord.Guild]:
284303 if guild is not None :
285304 return guild
286305 self .config .remove ("modmail_guild_id" )
287- logger .error ("Invalid modmail_guild_id set." )
306+ logger .critical ("Invalid MODMAIL_GUILD_ID set." )
288307 return self .guild
289308
290309 @property
@@ -302,10 +321,11 @@ def main_category(self) -> typing.Optional[discord.CategoryChannel]:
302321 if cat is not None :
303322 return cat
304323 self .config .remove ("main_category_id" )
324+ logger .debug ('MAIN_CATEGORY_ID was invalid, removed.' )
305325 cat = discord .utils .get (self .modmail_guild .categories , name = "Modmail" )
306326 if cat is not None :
307327 self .config ["main_category_id" ] = cat .id
308- logger .debug ("No main category set, however, one was found. Setting.. ." )
328+ logger .debug ("No main category set explicitly, setting category \" Modmail \" as the main category ." )
309329 return cat
310330 return None
311331
@@ -384,28 +404,34 @@ async def setup_indexes(self):
384404 ("key" , "text" ),
385405 ]
386406 )
407+ logger .debug ('Successfully set up database indexes.' )
387408
388409 async def on_ready (self ):
389410 """Bot startup, sets uptime."""
390411
391412 # Wait until config cache is populated with stuff from db and on_connect ran
392413 await self .wait_for_connected ()
393414
415+ if self .guild is None :
416+ logger .debug ('Logging out due to invalid GUILD_ID.' )
417+ return await self .logout ()
418+
394419 logger .line ()
395420 logger .info ("Client ready." )
396421 logger .line ()
397422 logger .info ("Logged in as: %s" , self .user )
398423 logger .info ("User ID: %s" , self .user .id )
399424 logger .info ("Prefix: %s" , self .prefix )
400- logger .info ("Guild Name: %s" , self .guild .name if self . guild else "Invalid" )
401- logger .info ("Guild ID: %s" , self .guild .id if self . guild else "Invalid" )
425+ logger .info ("Guild Name: %s" , self .guild .name )
426+ logger .info ("Guild ID: %s" , self .guild .id )
402427 logger .line ()
403428
404429 await self .threads .populate_cache ()
405430
406431 # closures
407432 closures = self .config ["closures" ]
408433 logger .info ("There are %d thread(s) pending to be closed." , len (closures ))
434+ logger .line ()
409435
410436 for recipient_id , items in tuple (closures .items ()):
411437 after = (
@@ -418,10 +444,13 @@ async def on_ready(self):
418444
419445 if not thread :
420446 # If the channel is deleted
447+ logger .debug ('Failed to close thread for recipient %s.' , recipient_id )
421448 self .config ["closures" ].pop (recipient_id )
422449 await self .config .update ()
423450 continue
424451
452+ logger .debug ('Closing thread for recipient %s.' , recipient_id )
453+
425454 await thread .close (
426455 closer = self .get_user (items ["closer_id" ]),
427456 after = after ,
@@ -431,8 +460,6 @@ async def on_ready(self):
431460 auto_close = items .get ("auto_close" , False ),
432461 )
433462
434- logger .line ()
435-
436463 self .metadata_loop = tasks .Loop (
437464 self .post_metadata ,
438465 seconds = 0 ,
@@ -552,6 +579,7 @@ async def _process_blocked(self, message: discord.Message) -> bool:
552579 reaction = blocked_emoji
553580 changed = False
554581 delta = human_timedelta (min_account_age )
582+ logger .debug ('Blocked due to account age, user %s.' , message .author .name )
555583
556584 if str (message .author .id ) not in self .blocked_users :
557585 new_reason = (
@@ -565,7 +593,7 @@ async def _process_blocked(self, message: discord.Message) -> bool:
565593 embed = discord .Embed (
566594 title = "Message not sent!" ,
567595 description = f"Your must wait for { delta } "
568- f"before you can contact { self . user . mention } ." ,
596+ f"before you can contact me ." ,
569597 color = discord .Color .red (),
570598 )
571599 )
@@ -575,6 +603,7 @@ async def _process_blocked(self, message: discord.Message) -> bool:
575603 reaction = blocked_emoji
576604 changed = False
577605 delta = human_timedelta (min_guild_age )
606+ logger .debug ('Blocked due to guild age, user %s.' , message .author .name )
578607
579608 if str (message .author .id ) not in self .blocked_users :
580609 new_reason = (
@@ -588,29 +617,33 @@ async def _process_blocked(self, message: discord.Message) -> bool:
588617 embed = discord .Embed (
589618 title = "Message not sent!" ,
590619 description = f"Your must wait for { delta } "
591- f"before you can contact { self . user . mention } ." ,
620+ f"before you can contact me ." ,
592621 color = discord .Color .red (),
593622 )
594623 )
595624
596625 elif str (message .author .id ) in self .blocked_users :
597- reaction = blocked_emoji
598626 if reason .startswith ("System Message: New Account." ) or reason .startswith (
599627 "System Message: Recently Joined."
600628 ):
601629 # Met the age limit already, otherwise it would've been caught by the previous if's
602630 reaction = sent_emoji
631+ logger .debug ('No longer internally blocked, user %s.' , message .author .name )
603632 self .blocked_users .pop (str (message .author .id ))
604633 else :
634+ reaction = blocked_emoji
605635 end_time = re .search (r"%(.+?)%$" , reason )
606636 if end_time is not None :
637+ logger .debug ('No longer blocked, user %s.' , message .author .name )
607638 after = (
608639 datetime .fromisoformat (end_time .group (1 )) - now
609640 ).total_seconds ()
610641 if after <= 0 :
611642 # No longer blocked
612643 reaction = sent_emoji
613644 self .blocked_users .pop (str (message .author .id ))
645+ else :
646+ logger .debug ('User blocked, user %s.' , message .author .name )
614647 else :
615648 reaction = sent_emoji
616649
@@ -619,7 +652,7 @@ async def _process_blocked(self, message: discord.Message) -> bool:
619652 try :
620653 await message .add_reaction (reaction )
621654 except (discord .HTTPException , discord .InvalidArgument ):
622- pass
655+ logger . warning ( 'Failed to add reaction %s.' , reaction , exc_info = True )
623656 return str (message .author .id ) in self .blocked_users
624657
625658 async def process_modmail (self , message : discord .Message ) -> None :
@@ -805,20 +838,17 @@ async def on_raw_reaction_add(self, payload):
805838
806839 if isinstance (channel , discord .DMChannel ):
807840 if str (reaction ) == str (close_emoji ): # closing thread
841+ try :
842+ recipient_thread_close = strtobool (self .config ["recipient_thread_close" ])
843+ except ValueError :
844+ recipient_thread_close = self .config .remove ("recipient_thread_close" )
845+ if not recipient_thread_close :
846+ return
808847 thread = await self .threads .find (recipient = user )
809848 ts = message .embeds [0 ].timestamp if message .embeds else None
810849 if thread and ts == thread .channel .created_at :
811850 # the reacted message is the corresponding thread creation embed
812- try :
813- recipient_thread_close = strtobool (
814- self .config ["recipient_thread_close" ]
815- )
816- except ValueError :
817- recipient_thread_close = self .config .remove (
818- "recipient_thread_close"
819- )
820- if recipient_thread_close :
821- await thread .close (closer = user )
851+ await thread .close (closer = user )
822852 else :
823853 if not message .embeds :
824854 return
@@ -845,6 +875,7 @@ async def on_guild_channel_delete(self, channel):
845875
846876 if isinstance (channel , discord .CategoryChannel ):
847877 if self .main_category .id == channel .id :
878+ logger .debug ('Main category was deleted.' )
848879 self .config .remove ("main_category_id" )
849880 await self .config .update ()
850881 return
@@ -853,17 +884,19 @@ async def on_guild_channel_delete(self, channel):
853884 return
854885
855886 if self .log_channel is None or self .log_channel .id == channel .id :
887+ logger .info ('Log channel deleted.' )
856888 self .config .remove ("log_channel_id" )
857889 await self .config .update ()
858890 return
859891
860892 thread = await self .threads .find (channel = channel )
861- if not thread :
862- return
863-
864- await thread .close (closer = mod , silent = True , delete_channel = False )
893+ if thread :
894+ logger .debug ('Manually closed channel %s.' , channel .name )
895+ await thread .close (closer = mod , silent = True , delete_channel = False )
865896
866897 async def on_member_remove (self , member ):
898+ if member .guild != self .guild :
899+ return
867900 thread = await self .threads .find (recipient = member )
868901 if thread :
869902 embed = discord .Embed (
@@ -873,6 +906,8 @@ async def on_member_remove(self, member):
873906 await thread .channel .send (embed = embed )
874907
875908 async def on_member_join (self , member ):
909+ if member .guild != self .guild :
910+ return
876911 thread = await self .threads .find (recipient = member )
877912 if thread :
878913 embed = discord .Embed (
@@ -980,11 +1015,14 @@ async def validate_database_connection(self):
9801015
9811016 if "OperationFailure" in message :
9821017 logger .critical (
983- "This is due to having invalid credentials in your MONGO_URI."
1018+ "This is due to having invalid credentials in your MONGO_URI. "
1019+ "Remember you need to substitute `<password>` with your actual password."
9841020 )
9851021 logger .critical (
986- "Recheck the username/password and make sure to url encode them. "
987- "https://www.urlencoder.io/"
1022+ "Be sure to URL encode your username and password (not the entire URL!!), "
1023+ "https://www.urlencoder.io/, if this issue persists, try changing your username and password "
1024+ "to only include alphanumeric characters, no symbols."
1025+ ""
9881026 )
9891027 raise
9901028 else :
@@ -1024,7 +1062,7 @@ async def after_post_metadata(self):
10241062if __name__ == "__main__" :
10251063 try :
10261064 import uvloop
1027-
1065+ logger . debug ( 'Setting up with uvloop.' )
10281066 uvloop .install ()
10291067 except ImportError :
10301068 pass
0 commit comments