Skip to content

Commit 818968c

Browse files
authored
Merge pull request #172 from kyb3r/queue
Enhance thread creation performance
2 parents 5d03a04 + 9c324fc commit 818968c

File tree

3 files changed

+142
-104
lines changed

3 files changed

+142
-104
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
# [Unreleased]
8+
9+
### Fixed
10+
- `thread.create` is now synchronous so that the first message sent can be queued to be sent as soon as a thread is created.
11+
- This fixes a problem where if multiple messages are sent in quick succession, the first message sent (which triggers the thread creation) is not sent in order.
12+
713
# v2.13.4
814

915
### Changed
@@ -41,7 +47,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4147

4248
- `config del` command will now work properly on self-hosted db bots.
4349

44-
4550
# v2.12.4
4651

4752
### Added

core/thread.py

Lines changed: 129 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,16 @@
88
from discord.ext.commands import UserInputError, CommandError
99

1010
from core.models import Bot, ThreadManagerABC, ThreadABC
11-
from core.utils import is_image_url, days, match_user_id, truncate
11+
from core.utils import is_image_url, days, match_user_id, truncate, ignore
1212

1313

1414
class Thread(ThreadABC):
1515
"""Represents a discord Modmail thread"""
1616

1717
def __init__(self, manager: 'ThreadManager',
18-
recipient: typing.Union[discord.Member, discord.User,
19-
int],
18+
recipient: typing.Union[discord.Member, discord.User, int],
2019
channel: typing.Union[discord.DMChannel,
21-
discord.TextChannel]):
20+
discord.TextChannel] = None):
2221
self.manager = manager
2322
self.bot = manager.bot
2423
if isinstance(recipient, int):
@@ -72,6 +71,75 @@ def ready(self, flag):
7271
else:
7372
self._ready_event.clear()
7473

74+
async def setup(self, *, creator=None, category=None):
75+
"""Create the thread channel and other io related initialisation tasks"""
76+
77+
recipient = self.recipient
78+
79+
# in case it creates a channel outside of category
80+
overwrites = {
81+
self.bot.modmail_guild.default_role:
82+
discord.PermissionOverwrite(read_messages=False)
83+
}
84+
85+
category = category or self.bot.main_category
86+
87+
if category is not None:
88+
overwrites = None
89+
90+
channel = await self.bot.modmail_guild.create_text_channel(
91+
name=self.manager._format_channel_name(recipient),
92+
category=category,
93+
overwrites=overwrites,
94+
reason='Creating a thread channel'
95+
)
96+
97+
self._channel = channel
98+
99+
log_url, log_data = await asyncio.gather(
100+
self.bot.api.create_log_entry(recipient, channel,
101+
creator or recipient),
102+
self.bot.api.get_user_logs(recipient.id)
103+
)
104+
105+
log_count = sum(1 for log in log_data if not log['open'])
106+
info_embed = self.manager._format_info_embed(recipient, log_url,
107+
log_count,
108+
discord.Color.green())
109+
110+
topic = f'User ID: {recipient.id}'
111+
if creator:
112+
mention = None
113+
else:
114+
mention = self.bot.config.get('mention', '@here')
115+
116+
_, msg = await asyncio.gather(
117+
channel.edit(topic=topic),
118+
channel.send(mention, embed=info_embed)
119+
)
120+
121+
self.ready = True
122+
123+
# Once thread is ready, tell the recipient.
124+
thread_creation_response = self.bot.config.get(
125+
'thread_creation_response',
126+
'The moderation team will get back to you as soon as possible!'
127+
)
128+
129+
embed = discord.Embed(
130+
color=self.bot.mod_color,
131+
description=thread_creation_response,
132+
timestamp=datetime.utcnow(),
133+
)
134+
embed.set_footer(text='Your message has been sent',
135+
icon_url=self.bot.guild.icon_url)
136+
embed.set_author(name='Thread Created')
137+
138+
if creator is None:
139+
self.bot.loop.create_task(recipient.send(embed=embed))
140+
141+
await msg.pin()
142+
75143
def _close_after(self, closer, silent, delete_channel, message):
76144
return self.bot.loop.create_task(
77145
self._close(closer, silent, delete_channel, message, True)
@@ -144,7 +212,7 @@ async def _close(self, closer, silent=False, delete_channel=True,
144212
sneak_peak = 'No content'
145213

146214
desc = f"[`{log_data['key']}`]({log_url}): "
147-
desc += truncate(sneak_peak, max=75-13)
215+
desc += truncate(sneak_peak, max=75 - 13)
148216
else:
149217
desc = "Could not resolve log url."
150218

@@ -231,33 +299,45 @@ async def reply(self, message, anonymous=False):
231299
if not message.content and not message.attachments:
232300
raise UserInputError
233301
if all(not g.get_member(self.id) for g in self.bot.guilds):
234-
await message.channel.send(
302+
return await message.channel.send(
235303
embed=discord.Embed(
236304
color=discord.Color.red(),
237-
description='This user shares no servers with '
238-
'me and is thus unreachable.'
305+
description='Your message could not be delivered since'
306+
'the recipient shares no servers with the bot'
307+
))
308+
309+
tasks = []
310+
311+
try:
312+
await self.send(message,
313+
destination=self.recipient,
314+
from_mod=True,
315+
anonymous=anonymous)
316+
except Exception as e:
317+
print(e)
318+
tasks.append(message.channel.send(
319+
embed=discord.Embed(
320+
color=discord.Color.red(),
321+
description='Your message could not be delivered because '
322+
'the recipient is only accepting direct '
323+
'messages from friends, or the bot was '
324+
'blocked by the recipient.'
239325
)
326+
))
327+
else:
328+
# Send the same thing in the thread channel.
329+
tasks.append(
330+
self.send(message,
331+
destination=self.channel,
332+
from_mod=True,
333+
anonymous=anonymous)
240334
)
241-
return
242335

243-
tasks = [
244-
# in thread channel
245-
self.send(message,
246-
destination=self.channel,
247-
from_mod=True,
248-
anonymous=anonymous),
249-
# to user
250-
self.send(message,
251-
destination=self.recipient,
252-
from_mod=True,
253-
anonymous=anonymous)
254-
]
255-
256-
await self.bot.api.append_log(
257-
message,
258-
self.channel.id,
259-
type_='anonymous' if anonymous else 'thread_message'
260-
)
336+
tasks.append(
337+
self.bot.api.append_log(message,
338+
self.channel.id,
339+
type_='anonymous' if anonymous else 'thread_message'
340+
))
261341

262342
if self.close_task is not None:
263343
# cancel closing if a thread message is sent.
@@ -277,14 +357,18 @@ async def send(self, message, destination=None,
277357
from_mod=False, note=False, anonymous=False):
278358
if self.close_task is not None:
279359
# cancel closing if a thread message is sent.
280-
await self.cancel_closure()
281-
await self.channel.send(embed=discord.Embed(
282-
color=discord.Color.red(),
283-
description='Scheduled close has been cancelled.'
284-
))
360+
tasks = asyncio.gather(
361+
self.cancel_closure(),
362+
self.channel.send(embed=discord.Embed(
363+
color=discord.Color.red(),
364+
description='Scheduled close has been cancelled.'
365+
)))
366+
self.bot.loop.create_task(tasks)
285367

286368
if not from_mod and not note:
287-
await self.bot.api.append_log(message, self.channel.id)
369+
self.bot.loop.create_task(
370+
self.bot.api.append_log(message, self.channel.id)
371+
)
288372

289373
if not self.ready:
290374
await self.wait_until_ready()
@@ -410,17 +494,13 @@ async def send(self, message, destination=None,
410494
mentions = None
411495

412496
await destination.send(mentions, embed=embed)
413-
414497
if additional_images:
415-
self.ready = False
416-
await asyncio.gather(*additional_images)
417498
self.ready = True
499+
await asyncio.gather(*additional_images)
500+
self.ready = False
418501

419502
if delete_message:
420-
try:
421-
await message.delete()
422-
except discord.HTTPException:
423-
pass
503+
self.bot.loop.create_task(ignore(message.delete()))
424504

425505
def get_notifications(self):
426506
config = self.bot.config
@@ -519,75 +599,21 @@ async def _find_from_channel(self, channel):
519599

520600
return thread
521601

522-
async def create(self, recipient, *, creator=None, category=None):
602+
def create(self, recipient, *, creator=None, category=None):
523603
"""Creates a Modmail thread"""
524-
525-
thread_creation_response = self.bot.config.get(
526-
'thread_creation_response',
527-
'The moderation team will get back to you as soon as possible!'
528-
)
529-
530-
embed = discord.Embed(
531-
color=self.bot.mod_color,
532-
description=thread_creation_response,
533-
timestamp=datetime.utcnow(),
534-
)
535-
embed.set_footer(text='Your message has been sent',
536-
icon_url=self.bot.guild.icon_url)
537-
embed.set_author(name='Thread Created')
538-
539-
if creator is None:
540-
self.bot.loop.create_task(recipient.send(embed=embed))
541-
542-
# in case it creates a channel outside of category
543-
overwrites = {
544-
self.bot.modmail_guild.default_role:
545-
discord.PermissionOverwrite(read_messages=False)
546-
}
547-
548-
category = category or self.bot.main_category
549-
550-
if category is not None:
551-
overwrites = None
552-
553-
channel = await self.bot.modmail_guild.create_text_channel(
554-
name=self._format_channel_name(recipient),
555-
category=category,
556-
overwrites=overwrites,
557-
reason='Creating a thread channel'
558-
)
559-
560-
thread = Thread(self, recipient, channel)
604+
# create thread immediately so messages can be processed
605+
thread = Thread(self, recipient)
561606
self.cache[recipient.id] = thread
562607

563-
log_url, log_data = await asyncio.gather(
564-
self.bot.api.create_log_entry(recipient, channel,
565-
creator or recipient),
566-
self.bot.api.get_user_logs(recipient.id)
567-
)
568-
569-
log_count = sum(1 for log in log_data if not log['open'])
570-
info_embed = self._format_info_embed(recipient, log_url, log_count,
571-
discord.Color.green())
572-
573-
topic = f'User ID: {recipient.id}'
574-
if creator:
575-
mention = None
576-
else:
577-
mention = self.bot.config.get('mention', '@here')
578-
579-
_, msg = await asyncio.gather(
580-
channel.edit(topic=topic),
581-
channel.send(mention, embed=info_embed)
582-
)
583-
584-
thread.ready = True
585-
await msg.pin()
608+
# Schedule thread setup for later
609+
self.bot.loop.create_task(thread.setup(
610+
creator=creator,
611+
category=category
612+
))
586613
return thread
587614

588615
async def find_or_create(self, recipient):
589-
return await self.find(recipient=recipient) or \
590-
await self.create(recipient)
616+
return await self.find(recipient=recipient) or self.create(recipient)
591617

592618
def _format_channel_name(self, author):
593619
"""Sanitises a username for use with text channel names"""

core/utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,10 @@ def match_user_id(text: str) -> int:
185185
if match is not None:
186186
return int(match.group(1))
187187
return -1
188+
189+
190+
async def ignore(coro):
191+
try:
192+
await coro
193+
except Exception:
194+
pass

0 commit comments

Comments
 (0)