1
- __version__ = "4.0.0-dev6 "
1
+ __version__ = "4.0.0-dev7 "
2
2
3
3
4
4
import asyncio
@@ -67,7 +67,7 @@ class ModmailBot(commands.Bot):
67
67
def __init__ (self ):
68
68
intents = discord .Intents .all ()
69
69
super ().__init__ (command_prefix = None , intents = intents ) # implemented in `get_prefix`
70
- self ._session = None
70
+ self .session = None
71
71
self ._api = None
72
72
self .formatter = SafeFormatter ()
73
73
self .loaded_cogs = ["cogs.modmail" , "cogs.plugins" , "cogs.utility" ]
@@ -131,10 +131,11 @@ def startup(self):
131
131
logger .info ("discord.py: v%s" , discord .__version__ )
132
132
logger .line ()
133
133
134
+ async def load_extensions (self ):
134
135
for cog in self .loaded_cogs :
135
136
logger .debug ("Loading %s." , cog )
136
137
try :
137
- self .load_extension (cog )
138
+ await self .load_extension (cog )
138
139
logger .debug ("Successfully loaded %s." , cog )
139
140
except Exception :
140
141
logger .exception ("Failed to load %s." , cog )
@@ -167,12 +168,6 @@ def _configure_logging(self):
167
168
def version (self ):
168
169
return parse_version (__version__ )
169
170
170
- @property
171
- def session (self ) -> ClientSession :
172
- if self ._session is None :
173
- self ._session = ClientSession (loop = self .loop )
174
- return self ._session
175
-
176
171
@property
177
172
def api (self ) -> ApiClient :
178
173
if self ._api is None :
@@ -192,102 +187,85 @@ async def get_prefix(self, message=None):
192
187
return [self .prefix , f"<@{ self .user .id } > " , f"<@!{ self .user .id } > " ]
193
188
194
189
def run (self ):
195
- loop = self .loop
196
-
197
- try :
198
- loop .add_signal_handler (signal .SIGINT , lambda : loop .stop ())
199
- loop .add_signal_handler (signal .SIGTERM , lambda : loop .stop ())
200
- except NotImplementedError :
201
- pass
202
-
203
190
async def runner ():
204
- try :
205
- retry_intents = False
191
+ async with self :
206
192
try :
207
- await self .start (self .token )
193
+ retry_intents = False
194
+ try :
195
+ await self .start (self .token )
196
+ except discord .PrivilegedIntentsRequired :
197
+ retry_intents = True
198
+ if retry_intents :
199
+ await self .http .close ()
200
+ if self .ws is not None and self .ws .open :
201
+ await self .ws .close (code = 1000 )
202
+ self ._ready .clear ()
203
+
204
+ intents = discord .Intents .default ()
205
+ intents .members = True
206
+ intents .message_content = True
207
+ # Try again with members intent
208
+ self ._connection ._intents = intents
209
+ logger .warning (
210
+ "Attempting to login with only the server members and message content privileged intent. Some plugins might not work correctly."
211
+ )
212
+ await self .start (self .token )
208
213
except discord .PrivilegedIntentsRequired :
209
- retry_intents = True
210
- if retry_intents :
211
- await self .http .close ()
212
- if self .ws is not None and self .ws .open :
213
- await self .ws .close (code = 1000 )
214
- self ._ready .clear ()
215
- intents = discord .Intents .default ()
216
- intents .members = True
217
- # Try again with members intent
218
- self ._connection ._intents = intents
219
- logger .warning (
220
- "Attempting to login with only the server members privileged intent. Some plugins might not work correctly."
214
+ logger .critical (
215
+ "Privileged intents are not explicitly granted in the discord developers dashboard."
221
216
)
222
- await self .start (self .token )
223
- except discord .PrivilegedIntentsRequired :
224
- logger .critical (
225
- "Privileged intents are not explicitly granted in the discord developers dashboard."
226
- )
227
- except discord .LoginFailure :
228
- logger .critical ("Invalid token" )
229
- except Exception :
230
- logger .critical ("Fatal exception" , exc_info = True )
231
- finally :
232
- if not self .is_closed ():
233
- await self .close ()
234
- if self ._session :
235
- await self ._session .close ()
236
-
237
- # noinspection PyUnusedLocal
238
- def stop_loop_on_completion (f ):
239
- loop .stop ()
240
-
241
- def _cancel_tasks ():
242
- task_retriever = asyncio .all_tasks
243
- tasks = {t for t in task_retriever (loop = loop ) if not t .done ()}
244
-
245
- if not tasks :
246
- return
217
+ except discord .LoginFailure :
218
+ logger .critical ("Invalid token" )
219
+ except Exception :
220
+ logger .critical ("Fatal exception" , exc_info = True )
221
+ finally :
222
+ if not self .is_closed ():
223
+ await self .close ()
224
+ if self .session :
225
+ await self .session .close ()
226
+
227
+ async def _cancel_tasks ():
228
+ async with self :
229
+ task_retriever = asyncio .all_tasks
230
+ loop = self .loop
231
+ tasks = {t for t in task_retriever () if not t .done () and t .get_coro () != cancel_tasks_coro }
232
+
233
+ if not tasks :
234
+ return
247
235
248
- logger .info ("Cleaning up after %d tasks." , len (tasks ))
249
- for task in tasks :
250
- task .cancel ()
251
-
252
- loop .run_until_complete (asyncio .gather (* tasks , return_exceptions = True ))
253
- logger .info ("All tasks finished cancelling." )
254
-
255
- for task in tasks :
256
- if task .cancelled ():
257
- continue
258
- if task .exception () is not None :
259
- loop .call_exception_handler (
260
- {
261
- "message" : "Unhandled exception during Client.run shutdown." ,
262
- "exception" : task .exception (),
263
- "task" : task ,
264
- }
265
- )
236
+ logger .info ("Cleaning up after %d tasks." , len (tasks ))
237
+ for task in tasks :
238
+ task .cancel ()
239
+
240
+ await asyncio .gather (* tasks , return_exceptions = True )
241
+ logger .info ("All tasks finished cancelling." )
242
+
243
+ for task in tasks :
244
+ try :
245
+ if task .exception () is not None :
246
+ loop .call_exception_handler (
247
+ {
248
+ "message" : "Unhandled exception during Client.run shutdown." ,
249
+ "exception" : task .exception (),
250
+ "task" : task ,
251
+ }
252
+ )
253
+ except (asyncio .InvalidStateError , asyncio .CancelledError ):
254
+ pass
266
255
267
- future = asyncio .ensure_future (runner (), loop = loop )
268
- future .add_done_callback (stop_loop_on_completion )
269
256
try :
270
- loop . run_forever ( )
271
- except KeyboardInterrupt :
257
+ asyncio . run ( runner () )
258
+ except ( KeyboardInterrupt , SystemExit ) :
272
259
logger .info ("Received signal to terminate bot and event loop." )
273
260
finally :
274
- future .remove_done_callback (stop_loop_on_completion )
275
261
logger .info ("Cleaning up tasks." )
276
262
277
263
try :
278
- _cancel_tasks ()
279
- if sys .version_info >= (3 , 6 ):
280
- loop .run_until_complete (loop .shutdown_asyncgens ())
264
+ cancel_tasks_coro = _cancel_tasks ()
265
+ asyncio .run (cancel_tasks_coro )
281
266
finally :
282
267
logger .info ("Closing the event loop." )
283
268
284
- if not future .cancelled ():
285
- try :
286
- return future .result ()
287
- except KeyboardInterrupt :
288
- # I am unsure why this gets raised here but suppress it anyway
289
- return None
290
-
291
269
@property
292
270
def bot_owner_ids (self ):
293
271
owner_ids = self .config ["owners" ]
@@ -517,6 +495,8 @@ async def on_connect(self):
517
495
logger .debug ("Connected to gateway." )
518
496
await self .config .refresh ()
519
497
await self .api .setup_indexes ()
498
+ await self .load_extensions ()
499
+ self .session = ClientSession (loop = self .loop )
520
500
self ._connected .set ()
521
501
522
502
async def on_ready (self ):
@@ -1421,7 +1401,7 @@ async def on_message_delete(self, message):
1421
1401
if embed .footer .icon :
1422
1402
icon_url = embed .footer .icon .url
1423
1403
else :
1424
- icon_url = discord . Embed . Empty
1404
+ icon_url = None
1425
1405
1426
1406
embed .set_footer (text = f"{ embed .footer .text } (deleted)" , icon_url = icon_url )
1427
1407
await message .edit (embed = embed )
0 commit comments