29
29
import os
30
30
import re
31
31
import sys
32
+ import typing
32
33
33
34
from datetime import datetime
34
35
from pkg_resources import parse_version
49
50
from core .clients import DatabaseClient , PluginDatabaseClient
50
51
from core .config import ConfigManager
51
52
from core .utils import info , error , human_join
52
- from core .models import Bot , PermissionLevel
53
+ from core .models import PermissionLevel
53
54
from core .thread import ThreadManager
54
55
from core .time import human_timedelta
55
56
@@ -93,14 +94,16 @@ def format(self, record):
93
94
Style .RESET_ALL
94
95
95
96
96
- class ModmailBot (Bot ):
97
+ class ModmailBot (commands . Bot ):
97
98
98
99
def __init__ (self ):
99
100
super ().__init__ (command_prefix = None ) # implemented in `get_prefix`
100
101
self ._threads = None
101
102
self ._session = None
102
103
self ._config = None
103
104
self ._db = None
105
+ self .start_time = datetime .utcnow ()
106
+ self ._connected = asyncio .Event ()
104
107
105
108
self ._configure_logging ()
106
109
# TODO: Raise fatal error if mongo_uri or other essentials are not found
@@ -112,6 +115,20 @@ def __init__(self):
112
115
self .autoupdate_task = self .loop .create_task (self .autoupdate_loop ())
113
116
self ._load_extensions ()
114
117
118
+ @property
119
+ def uptime (self ) -> str :
120
+ now = datetime .utcnow ()
121
+ delta = now - self .start_time
122
+ hours , remainder = divmod (int (delta .total_seconds ()), 3600 )
123
+ minutes , seconds = divmod (remainder , 60 )
124
+ days , hours = divmod (hours , 24 )
125
+
126
+ fmt = '{h}h {m}m {s}s'
127
+ if days :
128
+ fmt = '{d}d ' + fmt
129
+
130
+ return fmt .format (d = days , h = hours , m = minutes , s = seconds )
131
+
115
132
def _configure_logging (self ):
116
133
level_text = self .config .log_level .upper ()
117
134
logging_levels = {
@@ -133,31 +150,31 @@ def _configure_logging(self):
133
150
logger .info (info ('Using default logging level: INFO' ))
134
151
135
152
@property
136
- def version (self ):
153
+ def version (self ) -> str :
137
154
return __version__
138
155
139
156
@property
140
- def db (self ):
157
+ def db (self ) -> typing . Optional [ AsyncIOMotorClient ] :
141
158
return self ._db
142
159
143
160
@property
144
- def api (self ):
161
+ def api (self ) -> DatabaseClient :
145
162
return self ._api
146
163
147
164
@property
148
- def config (self ):
165
+ def config (self ) -> ConfigManager :
149
166
if self ._config is None :
150
167
self ._config = ConfigManager (self )
151
168
return self ._config
152
169
153
170
@property
154
- def session (self ):
171
+ def session (self ) -> ClientSession :
155
172
if self ._session is None :
156
173
self ._session = ClientSession (loop = self .loop )
157
174
return self ._session
158
175
159
176
@property
160
- def threads (self ):
177
+ def threads (self ) -> ThreadManager :
161
178
if self ._threads is None :
162
179
self ._threads = ThreadManager (self )
163
180
return self ._threads
@@ -220,13 +237,13 @@ def run(self, *args, **kwargs):
220
237
self .loop .close ()
221
238
logger .info (error (' - Shutting down bot - ' ))
222
239
223
- async def is_owner (self , user ) :
240
+ async def is_owner (self , user : discord . User ) -> bool :
224
241
raw = str (self .config .get ('owners' , '0' )).split (',' )
225
242
allowed = {int (x ) for x in raw }
226
243
return (user .id in allowed ) or await super ().is_owner (user )
227
244
228
245
@property
229
- def log_channel (self ):
246
+ def log_channel (self ) -> typing . Optional [ discord . TextChannel ] :
230
247
channel_id = self .config .get ('log_channel_id' )
231
248
if channel_id is not None :
232
249
return self .get_channel (int (channel_id ))
@@ -235,31 +252,31 @@ def log_channel(self):
235
252
return None
236
253
237
254
@property
238
- def snippets (self ):
255
+ def snippets (self ) -> typing . Dict [ str , str ] :
239
256
return {k : v for k , v in self .config .get ('snippets' , {}).items () if v }
240
257
241
258
@property
242
- def aliases (self ):
259
+ def aliases (self ) -> typing . Dict [ str , str ] :
243
260
return {k : v for k , v in self .config .get ('aliases' , {}).items () if v }
244
261
245
262
@property
246
- def token (self ):
263
+ def token (self ) -> str :
247
264
return self .config .token
248
265
249
266
@property
250
- def guild_id (self ):
267
+ def guild_id (self ) -> int :
251
268
return int (self .config .guild_id )
252
269
253
270
@property
254
- def guild (self ):
271
+ def guild (self ) -> discord . Guild :
255
272
"""
256
273
The guild that the bot is serving
257
274
(the server where users message it from)
258
275
"""
259
276
return discord .utils .get (self .guilds , id = self .guild_id )
260
277
261
278
@property
262
- def modmail_guild (self ):
279
+ def modmail_guild (self ) -> discord . Guild :
263
280
"""
264
281
The guild that the bot is operating in
265
282
(where the bot is creating threads)
@@ -270,11 +287,11 @@ def modmail_guild(self):
270
287
return discord .utils .get (self .guilds , id = int (modmail_guild_id ))
271
288
272
289
@property
273
- def using_multiple_server_setup (self ):
290
+ def using_multiple_server_setup (self ) -> bool :
274
291
return self .modmail_guild != self .guild
275
292
276
293
@property
277
- def main_category (self ):
294
+ def main_category (self ) -> typing . Optional [ discord . TextChannel ] :
278
295
category_id = self .config .get ('main_category_id' )
279
296
if category_id is not None :
280
297
return discord .utils .get (self .modmail_guild .categories ,
@@ -286,15 +303,15 @@ def main_category(self):
286
303
return None
287
304
288
305
@property
289
- def blocked_users (self ):
306
+ def blocked_users (self ) -> typing . Dict [ str , str ] :
290
307
return self .config .get ('blocked' , {})
291
308
292
309
@property
293
- def prefix (self ):
310
+ def prefix (self ) -> str :
294
311
return self .config .get ('prefix' , '?' )
295
312
296
313
@property
297
- def mod_color (self ):
314
+ def mod_color (self ) -> typing . Union [ discord . Color , int ] :
298
315
color = self .config .get ('mod_color' )
299
316
if not color :
300
317
return discord .Color .green ()
@@ -307,7 +324,7 @@ def mod_color(self):
307
324
return color
308
325
309
326
@property
310
- def recipient_color (self ):
327
+ def recipient_color (self ) -> typing . Union [ discord . Color , int ] :
311
328
color = self .config .get ('recipient_color' )
312
329
if not color :
313
330
return discord .Color .gold ()
@@ -320,7 +337,7 @@ def recipient_color(self):
320
337
return color
321
338
322
339
@property
323
- def main_color (self ):
340
+ def main_color (self ) -> typing . Union [ discord . Color , int ] :
324
341
color = self .config .get ('main_color' )
325
342
if not color :
326
343
return discord .Color .blurple ()
@@ -414,7 +431,7 @@ async def on_ready(self):
414
431
415
432
logger .info (LINE )
416
433
417
- async def convert_emoji (self , name ) :
434
+ async def convert_emoji (self , name : str ) -> str :
418
435
ctx = SimpleNamespace (bot = self , guild = self .modmail_guild )
419
436
converter = commands .EmojiConverter ()
420
437
@@ -425,7 +442,7 @@ async def convert_emoji(self, name):
425
442
logger .warning (f'{ name } is not a valid emoji.' )
426
443
return name
427
444
428
- async def retrieve_emoji (self ):
445
+ async def retrieve_emoji (self ) -> typing . Tuple [ str , str ] :
429
446
430
447
# TODO: use a function to convert emojis
431
448
@@ -461,7 +478,7 @@ async def retrieve_emoji(self):
461
478
462
479
return sent_emoji , blocked_emoji
463
480
464
- async def process_modmail (self , message ) :
481
+ async def process_modmail (self , message : discord . Message ) -> None :
465
482
"""Processes messages sent to the bot."""
466
483
sent_emoji , blocked_emoji = await self .retrieve_emoji ()
467
484
now = datetime .utcnow ()
@@ -631,7 +648,8 @@ async def get_context(self, message, *, cls=commands.Context):
631
648
632
649
return ctx
633
650
634
- async def update_perms (self , name , value , add = True ):
651
+ async def update_perms (self , name : typing .Union [PermissionLevel , str ],
652
+ value : int , add : bool = True ) -> None :
635
653
if isinstance (name , PermissionLevel ):
636
654
permissions = self .config .level_permissions
637
655
name = name .name
@@ -862,7 +880,7 @@ async def on_command_error(self, ctx, exception):
862
880
logger .error (error ('Unexpected exception:' ), exc_info = exception )
863
881
864
882
@staticmethod
865
- def overwrites (ctx ) :
883
+ def overwrites (ctx : commands . Context ) -> dict :
866
884
"""Permission overwrites for the guild."""
867
885
overwrites = {
868
886
ctx .guild .default_role : discord .PermissionOverwrite (
0 commit comments