19
19
from aiohttp import ClientSession
20
20
from emoji import UNICODE_EMOJI
21
21
from motor .motor_asyncio import AsyncIOMotorClient
22
+ from pymongo .errors import ConfigurationError
22
23
23
24
try :
24
25
from colorama import init
@@ -65,6 +66,8 @@ def __init__(self):
65
66
super ().__init__ (command_prefix = None ) # implemented in `get_prefix`
66
67
self ._session = None
67
68
self ._api = None
69
+ self .metadata_loop = None
70
+
68
71
self ._connected = asyncio .Event ()
69
72
self .start_time = datetime .utcnow ()
70
73
@@ -77,13 +80,17 @@ def __init__(self):
77
80
78
81
mongo_uri = self .config ["mongo_uri" ]
79
82
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
84
85
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 )
86
92
93
+ self .plugin_db = PluginDatabaseClient (self )
87
94
self ._load_extensions ()
88
95
89
96
@property
@@ -134,7 +141,8 @@ def _configure_logging(self):
134
141
logger .info ("Logging level: %s" , level_text )
135
142
else :
136
143
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." )
138
146
139
147
@property
140
148
def version (self ) -> str :
@@ -198,11 +206,21 @@ def run(self, *args, **kwargs):
198
206
self .loop .run_until_complete (self .session .close ())
199
207
logger .error (" - Shutting down bot - " )
200
208
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
+
201
221
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
206
224
return await super ().is_owner (user )
207
225
208
226
@property
@@ -212,18 +230,18 @@ def log_channel(self) -> typing.Optional[discord.TextChannel]:
212
230
channel = self .get_channel (int (channel_id ))
213
231
if channel is not None :
214
232
return channel
233
+ logger .debug ('LOG_CHANNEL_ID was invalid, removed.' )
215
234
self .config .remove ("log_channel_id" )
216
235
if self .main_category is not None :
217
236
try :
218
237
channel = self .main_category .channels [0 ]
219
238
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 )
221
240
return channel
222
241
except IndexError :
223
242
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>`." ,
227
245
self .prefix ,
228
246
self .prefix ,
229
247
)
@@ -250,7 +268,8 @@ def aliases(self) -> typing.Dict[str, str]:
250
268
def token (self ) -> str :
251
269
token = self .config ["token" ]
252
270
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 )
254
273
return token
255
274
256
275
@property
@@ -260,7 +279,7 @@ def guild_id(self) -> typing.Optional[int]:
260
279
try :
261
280
return int (str (guild_id ))
262
281
except ValueError :
263
- raise ValueError ("Invalid guild_id set." )
282
+ logger . critical ("Invalid GUILD_ID set." )
264
283
return None
265
284
266
285
@property
@@ -284,7 +303,7 @@ def modmail_guild(self) -> typing.Optional[discord.Guild]:
284
303
if guild is not None :
285
304
return guild
286
305
self .config .remove ("modmail_guild_id" )
287
- logger .error ("Invalid modmail_guild_id set." )
306
+ logger .critical ("Invalid MODMAIL_GUILD_ID set." )
288
307
return self .guild
289
308
290
309
@property
@@ -302,10 +321,11 @@ def main_category(self) -> typing.Optional[discord.CategoryChannel]:
302
321
if cat is not None :
303
322
return cat
304
323
self .config .remove ("main_category_id" )
324
+ logger .debug ('MAIN_CATEGORY_ID was invalid, removed.' )
305
325
cat = discord .utils .get (self .modmail_guild .categories , name = "Modmail" )
306
326
if cat is not None :
307
327
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 ." )
309
329
return cat
310
330
return None
311
331
@@ -384,28 +404,34 @@ async def setup_indexes(self):
384
404
("key" , "text" ),
385
405
]
386
406
)
407
+ logger .debug ('Successfully set up database indexes.' )
387
408
388
409
async def on_ready (self ):
389
410
"""Bot startup, sets uptime."""
390
411
391
412
# Wait until config cache is populated with stuff from db and on_connect ran
392
413
await self .wait_for_connected ()
393
414
415
+ if self .guild is None :
416
+ logger .debug ('Logging out due to invalid GUILD_ID.' )
417
+ return await self .logout ()
418
+
394
419
logger .line ()
395
420
logger .info ("Client ready." )
396
421
logger .line ()
397
422
logger .info ("Logged in as: %s" , self .user )
398
423
logger .info ("User ID: %s" , self .user .id )
399
424
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 )
402
427
logger .line ()
403
428
404
429
await self .threads .populate_cache ()
405
430
406
431
# closures
407
432
closures = self .config ["closures" ]
408
433
logger .info ("There are %d thread(s) pending to be closed." , len (closures ))
434
+ logger .line ()
409
435
410
436
for recipient_id , items in tuple (closures .items ()):
411
437
after = (
@@ -418,10 +444,13 @@ async def on_ready(self):
418
444
419
445
if not thread :
420
446
# If the channel is deleted
447
+ logger .debug ('Failed to close thread for recipient %s.' , recipient_id )
421
448
self .config ["closures" ].pop (recipient_id )
422
449
await self .config .update ()
423
450
continue
424
451
452
+ logger .debug ('Closing thread for recipient %s.' , recipient_id )
453
+
425
454
await thread .close (
426
455
closer = self .get_user (items ["closer_id" ]),
427
456
after = after ,
@@ -431,8 +460,6 @@ async def on_ready(self):
431
460
auto_close = items .get ("auto_close" , False ),
432
461
)
433
462
434
- logger .line ()
435
-
436
463
self .metadata_loop = tasks .Loop (
437
464
self .post_metadata ,
438
465
seconds = 0 ,
@@ -552,6 +579,7 @@ async def _process_blocked(self, message: discord.Message) -> bool:
552
579
reaction = blocked_emoji
553
580
changed = False
554
581
delta = human_timedelta (min_account_age )
582
+ logger .debug ('Blocked due to account age, user %s.' , message .author .name )
555
583
556
584
if str (message .author .id ) not in self .blocked_users :
557
585
new_reason = (
@@ -565,7 +593,7 @@ async def _process_blocked(self, message: discord.Message) -> bool:
565
593
embed = discord .Embed (
566
594
title = "Message not sent!" ,
567
595
description = f"Your must wait for { delta } "
568
- f"before you can contact { self . user . mention } ." ,
596
+ f"before you can contact me ." ,
569
597
color = discord .Color .red (),
570
598
)
571
599
)
@@ -575,6 +603,7 @@ async def _process_blocked(self, message: discord.Message) -> bool:
575
603
reaction = blocked_emoji
576
604
changed = False
577
605
delta = human_timedelta (min_guild_age )
606
+ logger .debug ('Blocked due to guild age, user %s.' , message .author .name )
578
607
579
608
if str (message .author .id ) not in self .blocked_users :
580
609
new_reason = (
@@ -588,29 +617,33 @@ async def _process_blocked(self, message: discord.Message) -> bool:
588
617
embed = discord .Embed (
589
618
title = "Message not sent!" ,
590
619
description = f"Your must wait for { delta } "
591
- f"before you can contact { self . user . mention } ." ,
620
+ f"before you can contact me ." ,
592
621
color = discord .Color .red (),
593
622
)
594
623
)
595
624
596
625
elif str (message .author .id ) in self .blocked_users :
597
- reaction = blocked_emoji
598
626
if reason .startswith ("System Message: New Account." ) or reason .startswith (
599
627
"System Message: Recently Joined."
600
628
):
601
629
# Met the age limit already, otherwise it would've been caught by the previous if's
602
630
reaction = sent_emoji
631
+ logger .debug ('No longer internally blocked, user %s.' , message .author .name )
603
632
self .blocked_users .pop (str (message .author .id ))
604
633
else :
634
+ reaction = blocked_emoji
605
635
end_time = re .search (r"%(.+?)%$" , reason )
606
636
if end_time is not None :
637
+ logger .debug ('No longer blocked, user %s.' , message .author .name )
607
638
after = (
608
639
datetime .fromisoformat (end_time .group (1 )) - now
609
640
).total_seconds ()
610
641
if after <= 0 :
611
642
# No longer blocked
612
643
reaction = sent_emoji
613
644
self .blocked_users .pop (str (message .author .id ))
645
+ else :
646
+ logger .debug ('User blocked, user %s.' , message .author .name )
614
647
else :
615
648
reaction = sent_emoji
616
649
@@ -619,7 +652,7 @@ async def _process_blocked(self, message: discord.Message) -> bool:
619
652
try :
620
653
await message .add_reaction (reaction )
621
654
except (discord .HTTPException , discord .InvalidArgument ):
622
- pass
655
+ logger . warning ( 'Failed to add reaction %s.' , reaction , exc_info = True )
623
656
return str (message .author .id ) in self .blocked_users
624
657
625
658
async def process_modmail (self , message : discord .Message ) -> None :
@@ -805,20 +838,17 @@ async def on_raw_reaction_add(self, payload):
805
838
806
839
if isinstance (channel , discord .DMChannel ):
807
840
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
808
847
thread = await self .threads .find (recipient = user )
809
848
ts = message .embeds [0 ].timestamp if message .embeds else None
810
849
if thread and ts == thread .channel .created_at :
811
850
# 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 )
822
852
else :
823
853
if not message .embeds :
824
854
return
@@ -845,6 +875,7 @@ async def on_guild_channel_delete(self, channel):
845
875
846
876
if isinstance (channel , discord .CategoryChannel ):
847
877
if self .main_category .id == channel .id :
878
+ logger .debug ('Main category was deleted.' )
848
879
self .config .remove ("main_category_id" )
849
880
await self .config .update ()
850
881
return
@@ -853,17 +884,19 @@ async def on_guild_channel_delete(self, channel):
853
884
return
854
885
855
886
if self .log_channel is None or self .log_channel .id == channel .id :
887
+ logger .info ('Log channel deleted.' )
856
888
self .config .remove ("log_channel_id" )
857
889
await self .config .update ()
858
890
return
859
891
860
892
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 )
865
896
866
897
async def on_member_remove (self , member ):
898
+ if member .guild != self .guild :
899
+ return
867
900
thread = await self .threads .find (recipient = member )
868
901
if thread :
869
902
embed = discord .Embed (
@@ -873,6 +906,8 @@ async def on_member_remove(self, member):
873
906
await thread .channel .send (embed = embed )
874
907
875
908
async def on_member_join (self , member ):
909
+ if member .guild != self .guild :
910
+ return
876
911
thread = await self .threads .find (recipient = member )
877
912
if thread :
878
913
embed = discord .Embed (
@@ -980,11 +1015,14 @@ async def validate_database_connection(self):
980
1015
981
1016
if "OperationFailure" in message :
982
1017
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."
984
1020
)
985
1021
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
+ ""
988
1026
)
989
1027
raise
990
1028
else :
@@ -1024,7 +1062,7 @@ async def after_post_metadata(self):
1024
1062
if __name__ == "__main__" :
1025
1063
try :
1026
1064
import uvloop
1027
-
1065
+ logger . debug ( 'Setting up with uvloop.' )
1028
1066
uvloop .install ()
1029
1067
except ImportError :
1030
1068
pass
0 commit comments