Skip to content

Commit fa718e4

Browse files
committed
Update to dpy@97fe07edb2f3d0f5a980917fb6ee479bf396fd94 & async changes
1 parent c16ff26 commit fa718e4

File tree

10 files changed

+466
-457
lines changed

10 files changed

+466
-457
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ however, insignificant breaking changes do not guarantee a major version bump, s
1010

1111
### Breaking
1212

13+
- Modmail now requires [`Message Content` privileged intent](https://support-dev.discord.com/hc/en-us/articles/4404772028055-Message-Content-Privileged-Intent-for-Verified-Bots).
1314
- Upgraded to discord.py v2.0 master ([internal changes](https://gist.github.com/apple502j/f75b4f24652f04de85c7084ffd73ec58), [GH #2990](https://github.com/kyb3r/modmail/issues/2990)).
1415
- Python 3.8 or higher is required.
16+
- Asyncio changes ([gist](https://gist.github.com/Rapptz/6706e1c8f23ac27c98cee4dd985c8120))
1517

1618
### Added
1719

@@ -41,11 +43,13 @@ however, insignificant breaking changes do not guarantee a major version bump, s
4143
- Support LOTTIE stickers. ([GH #3119](https://github.com/kyb3r/modmail/issues/3119))
4244
- Editing notes now work. ([GH #3094](https://github.com/kyb3r/modmail/issues/3094))
4345
- Commands now work in threads.
46+
- Audit log searching now properly works.
4447

4548
### Internal
4649

4750
- Improve regex parsing of channel topics. ([GH #3114](https://github.com/kyb3r/modmail/issues/3114), [PR #3111](https://github.com/kyb3r/modmail/pull/3111))
4851
- Add warning if deploying on a developmental version.
52+
- Extensions are now loaded `on_connect`.
4953

5054
# v3.10.3
5155

Pipfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pylint = "~=2.9.3"
1111
[packages]
1212
aiohttp = "==3.7.4.post0"
1313
colorama = "~=0.4.4" # Doesn't officially support Python 3.9 yet, v0.4.5 will support 3.9
14-
"discord.py" = {ref = "45d498c1b76deaf3b394d17ccf56112fa691d160", git = "https://github.com/Rapptz/discord.py.git"}
14+
"discord.py" = {ref = "97fe07edb2f3d0f5a980917fb6ee479bf396fd94", git = "https://github.com/Rapptz/discord.py.git"}
1515
emoji = "~=1.2.0"
1616
isodate = "~=0.6.0"
1717
motor = "~=2.4.0"

Pipfile.lock

Lines changed: 365 additions & 343 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bot.py

Lines changed: 71 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "4.0.0-dev6"
1+
__version__ = "4.0.0-dev7"
22

33

44
import asyncio
@@ -67,7 +67,7 @@ class ModmailBot(commands.Bot):
6767
def __init__(self):
6868
intents = discord.Intents.all()
6969
super().__init__(command_prefix=None, intents=intents) # implemented in `get_prefix`
70-
self._session = None
70+
self.session = None
7171
self._api = None
7272
self.formatter = SafeFormatter()
7373
self.loaded_cogs = ["cogs.modmail", "cogs.plugins", "cogs.utility"]
@@ -131,10 +131,11 @@ def startup(self):
131131
logger.info("discord.py: v%s", discord.__version__)
132132
logger.line()
133133

134+
async def load_extensions(self):
134135
for cog in self.loaded_cogs:
135136
logger.debug("Loading %s.", cog)
136137
try:
137-
self.load_extension(cog)
138+
await self.load_extension(cog)
138139
logger.debug("Successfully loaded %s.", cog)
139140
except Exception:
140141
logger.exception("Failed to load %s.", cog)
@@ -167,12 +168,6 @@ def _configure_logging(self):
167168
def version(self):
168169
return parse_version(__version__)
169170

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-
176171
@property
177172
def api(self) -> ApiClient:
178173
if self._api is None:
@@ -192,102 +187,85 @@ async def get_prefix(self, message=None):
192187
return [self.prefix, f"<@{self.user.id}> ", f"<@!{self.user.id}> "]
193188

194189
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-
203190
async def runner():
204-
try:
205-
retry_intents = False
191+
async with self:
206192
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)
208213
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."
221216
)
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
247235

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
266255

267-
future = asyncio.ensure_future(runner(), loop=loop)
268-
future.add_done_callback(stop_loop_on_completion)
269256
try:
270-
loop.run_forever()
271-
except KeyboardInterrupt:
257+
asyncio.run(runner())
258+
except (KeyboardInterrupt, SystemExit):
272259
logger.info("Received signal to terminate bot and event loop.")
273260
finally:
274-
future.remove_done_callback(stop_loop_on_completion)
275261
logger.info("Cleaning up tasks.")
276262

277263
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)
281266
finally:
282267
logger.info("Closing the event loop.")
283268

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-
291269
@property
292270
def bot_owner_ids(self):
293271
owner_ids = self.config["owners"]
@@ -517,6 +495,8 @@ async def on_connect(self):
517495
logger.debug("Connected to gateway.")
518496
await self.config.refresh()
519497
await self.api.setup_indexes()
498+
await self.load_extensions()
499+
self.session = ClientSession(loop=self.loop)
520500
self._connected.set()
521501

522502
async def on_ready(self):
@@ -1421,7 +1401,7 @@ async def on_message_delete(self, message):
14211401
if embed.footer.icon:
14221402
icon_url = embed.footer.icon.url
14231403
else:
1424-
icon_url = discord.Embed.Empty
1404+
icon_url = None
14251405

14261406
embed.set_footer(text=f"{embed.footer.text} (deleted)", icon_url=icon_url)
14271407
await message.edit(embed=embed)

cogs/modmail.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2091,5 +2091,5 @@ async def isenable(self, ctx):
20912091
return await ctx.send(embed=embed)
20922092

20932093

2094-
def setup(bot):
2095-
bot.add_cog(Modmail(bot))
2094+
async def setup(bot):
2095+
await bot.add_cog(Modmail(bot))

cogs/plugins.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,10 @@ def __init__(self, bot):
123123
self.loaded_plugins = set()
124124
self._ready_event = asyncio.Event()
125125

126-
self.bot.loop.create_task(self.populate_registry())
127-
126+
async def async_init(self):
127+
await self.populate_registry()
128128
if self.bot.config.get("enable_plugins"):
129-
self.bot.loop.create_task(self.initial_load_plugins())
129+
await self.initial_load_plugins()
130130
else:
131131
logger.info("Plugins not loaded since ENABLE_PLUGINS=false.")
132132

@@ -258,7 +258,7 @@ async def load_plugin(self, plugin):
258258
sys.path.insert(0, USER_SITE)
259259

260260
try:
261-
self.bot.load_extension(plugin.ext_string)
261+
await self.bot.load_extension(plugin.ext_string)
262262
logger.info("Loaded plugin: %s", plugin.ext_string.split(".")[-1])
263263
self.loaded_plugins.add(plugin)
264264

@@ -432,7 +432,7 @@ async def plugins_remove(self, ctx, *, plugin_name: str):
432432

433433
if self.bot.config.get("enable_plugins"):
434434
try:
435-
self.bot.unload_extension(plugin.ext_string)
435+
await self.bot.unload_extension(plugin.ext_string)
436436
self.loaded_plugins.remove(plugin)
437437
except (commands.ExtensionNotLoaded, KeyError):
438438
logger.warning("Plugin was never loaded.")
@@ -474,7 +474,7 @@ async def update_plugin(self, ctx, plugin_name):
474474
await self.download_plugin(plugin, force=True)
475475
if self.bot.config.get("enable_plugins"):
476476
try:
477-
self.bot.unload_extension(plugin.ext_string)
477+
await self.bot.unload_extension(plugin.ext_string)
478478
except commands.ExtensionError:
479479
logger.warning("Plugin unload fail.", exc_info=True)
480480
try:
@@ -525,7 +525,7 @@ async def plugins_reset(self, ctx):
525525
continue
526526
try:
527527
logger.error("Unloading plugin: %s.", ext)
528-
self.bot.unload_extension(ext)
528+
await self.bot.unload_extension(ext)
529529
except Exception:
530530
logger.error("Failed to unload plugin: %s.", ext)
531531
else:
@@ -737,5 +737,5 @@ async def plugins_registry_compact(self, ctx):
737737
await paginator.run()
738738

739739

740-
def setup(bot):
741-
bot.add_cog(Plugins(bot))
740+
async def setup(bot):
741+
await bot.add_cog(Plugins(bot))

cogs/utility.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -256,11 +256,13 @@ def __init__(self, bot):
256256
},
257257
)
258258
self.bot.help_command.cog = self
259-
self.loop_presence.start() # pylint: disable=no-member
260259
if not self.bot.config.get("enable_eval"):
261260
self.eval_.enabled = False
262261
logger.info("Eval disabled. enable_eval=False")
263262

263+
async def async_init(self):
264+
self.loop_presence.start() # pylint: disable=no-member
265+
264266
def cog_unload(self):
265267
self.bot.help_command = self._original_help_command
266268

@@ -2099,5 +2101,5 @@ def paginate(text: str):
20992101
await self.bot.add_reaction(ctx.message, "\u2705")
21002102

21012103

2102-
def setup(bot):
2103-
bot.add_cog(Utility(bot))
2104+
async def setup(bot):
2105+
await bot.add_cog(Utility(bot))

core/paginator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ def __init__(self, ctx: commands.Context, *embeds, **options):
308308
if embed.footer.icon:
309309
icon_url = embed.footer.icon.url
310310
else:
311-
icon_url = Embed.Empty
311+
icon_url = None
312312
embed.set_footer(text=footer_text, icon_url=icon_url)
313313

314314
# select menu
@@ -369,7 +369,7 @@ def _set_footer(self):
369369
if self.embed.footer.icon:
370370
icon_url = self.embed.footer.icon.url
371371
else:
372-
icon_url = Embed.Empty
372+
icon_url = None
373373

374374
self.embed.set_footer(text=footer_text, icon_url=icon_url)
375375

0 commit comments

Comments
 (0)