9
9
10
10
from core .models import Bot , ThreadManagerABC , ThreadABC
11
11
from core .utils import is_image_url , days , match_user_id , truncate
12
+ from core .decorators import ignore , queued
12
13
13
14
14
15
class Thread (ThreadABC ):
@@ -18,7 +19,7 @@ def __init__(self, manager: 'ThreadManager',
18
19
recipient : typing .Union [discord .Member , discord .User ,
19
20
int ],
20
21
channel : typing .Union [discord .DMChannel ,
21
- discord .TextChannel ]):
22
+ discord .TextChannel ]= None ):
22
23
self .manager = manager
23
24
self .bot = manager .bot
24
25
if isinstance (recipient , int ):
@@ -31,7 +32,9 @@ def __init__(self, manager: 'ThreadManager',
31
32
self ._recipient = recipient
32
33
self ._channel = channel
33
34
self ._ready_event = asyncio .Event ()
35
+ self ._message_queue = asyncio .Queue ()
34
36
self ._close_task = None
37
+ self .bot .loop .create_task (self .process_messages ())
35
38
36
39
def __repr__ (self ):
37
40
return (f'Thread(recipient="{ self .recipient or self .id } ", '
@@ -71,6 +74,80 @@ def ready(self, flag):
71
74
self ._ready_event .set ()
72
75
else :
73
76
self ._ready_event .clear ()
77
+
78
+ async def process_messages (self ):
79
+ """Guarantees order of thread messages sent"""
80
+ while True :
81
+ task = await self ._message_queue .get ()
82
+ await task
83
+
84
+ async def setup (self , * , creator = None , category = None ):
85
+ """Create the thread channel and other io related initilisation tasks"""
86
+
87
+ recipient = self .recipient
88
+
89
+ # in case it creates a channel outside of category
90
+ overwrites = {
91
+ self .bot .modmail_guild .default_role :
92
+ discord .PermissionOverwrite (read_messages = False )
93
+ }
94
+
95
+ category = category or self .bot .main_category
96
+
97
+ if category is not None :
98
+ overwrites = None
99
+
100
+ channel = await self .bot .modmail_guild .create_text_channel (
101
+ name = self .manager ._format_channel_name (recipient ),
102
+ category = category ,
103
+ overwrites = overwrites ,
104
+ reason = 'Creating a thread channel'
105
+ )
106
+
107
+ self ._channel = channel
108
+
109
+ log_url , log_data = await asyncio .gather (
110
+ self .bot .api .create_log_entry (recipient , channel ,
111
+ creator or recipient ),
112
+ self .bot .api .get_user_logs (recipient .id )
113
+ )
114
+
115
+ log_count = sum (1 for log in log_data if not log ['open' ])
116
+ info_embed = self .manager ._format_info_embed (recipient , log_url , log_count ,
117
+ discord .Color .green ())
118
+
119
+ topic = f'User ID: { recipient .id } '
120
+ if creator :
121
+ mention = None
122
+ else :
123
+ mention = self .bot .config .get ('mention' , '@here' )
124
+
125
+ _ , msg = await asyncio .gather (
126
+ channel .edit (topic = topic ),
127
+ channel .send (mention , embed = info_embed )
128
+ )
129
+
130
+ self .ready = True
131
+
132
+ # Once thread is ready, tell the recipient.
133
+ thread_creation_response = self .bot .config .get (
134
+ 'thread_creation_response' ,
135
+ 'The moderation team will get back to you as soon as possible!'
136
+ )
137
+
138
+ embed = discord .Embed (
139
+ color = self .bot .mod_color ,
140
+ description = thread_creation_response ,
141
+ timestamp = datetime .utcnow (),
142
+ )
143
+ embed .set_footer (text = 'Your message has been sent' ,
144
+ icon_url = self .bot .guild .icon_url )
145
+ embed .set_author (name = 'Thread Created' )
146
+
147
+ if creator is None :
148
+ self .bot .loop .create_task (recipient .send (embed = embed ))
149
+
150
+ await msg .pin ()
74
151
75
152
def _close_after (self , closer , silent , delete_channel , message ):
76
153
return self .bot .loop .create_task (
@@ -231,33 +308,45 @@ async def reply(self, message, anonymous=False):
231
308
if not message .content and not message .attachments :
232
309
raise UserInputError
233
310
if all (not g .get_member (self .id ) for g in self .bot .guilds ):
234
- await message .channel .send (
311
+ return await message .channel .send (
235
312
embed = discord .Embed (
236
313
color = discord .Color .red (),
237
- description = 'This user shares no servers with '
238
- 'me and is thus unreachable.'
314
+ description = 'Your message could not be delivered since'
315
+ 'the recipient shares no servers with the bot'
316
+ ))
317
+
318
+ tasks = []
319
+
320
+ try :
321
+ await self .send (message ,
322
+ destination = self .recipient ,
323
+ from_mod = True ,
324
+ anonymous = anonymous )
325
+ except Exception as e :
326
+ print (e )
327
+ tasks .append (message .channel .send (
328
+ embed = discord .Embed (
329
+ color = discord .Color .red (),
330
+ description = 'Your message could not be delivered because '
331
+ 'the recipient is only accepting direct '
332
+ 'messages from friends, or the bot was '
333
+ 'blocked by the recipient.'
334
+ )
335
+ ))
336
+ else :
337
+ # Send the same thing in the thread channel.
338
+ tasks .append (
339
+ self .send (message ,
340
+ destination = self .channel ,
341
+ from_mod = True ,
342
+ anonymous = anonymous )
239
343
)
240
- )
241
- return
242
-
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
344
256
- await self . bot . api . append_log (
257
- message ,
258
- self .channel .id ,
259
- type_ = 'anonymous' if anonymous else 'thread_message'
260
- )
345
+ tasks . append (
346
+ self . bot . api . append_log ( message ,
347
+ self .channel .id ,
348
+ type_ = 'anonymous' if anonymous else 'thread_message'
349
+ ) )
261
350
262
351
if self .close_task is not None :
263
352
# cancel closing if a thread message is sent.
@@ -273,21 +362,23 @@ async def reply(self, message, anonymous=False):
273
362
274
363
await asyncio .gather (* tasks )
275
364
365
+ @queued ()
276
366
async def send (self , message , destination = None ,
277
367
from_mod = False , note = False , anonymous = False ):
278
368
if self .close_task is not None :
279
369
# 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
- ))
370
+ tasks = asyncio .gather (
371
+ self .cancel_closure (),
372
+ self .channel .send (embed = discord .Embed (
373
+ color = discord .Color .red (),
374
+ description = 'Scheduled close has been cancelled.'
375
+ )))
376
+ self .bot .loop .create_task (tasks )
285
377
286
378
if not from_mod and not note :
287
- await self .bot .api .append_log (message , self .channel .id )
288
-
289
- if not self .ready :
290
- await self .wait_until_ready ()
379
+ self .bot .loop .create_task (
380
+ self .bot .api .append_log (message , self .channel .id )
381
+ )
291
382
292
383
destination = destination or self .channel
293
384
@@ -408,17 +499,11 @@ async def send(self, message, destination=None,
408
499
mentions = None
409
500
410
501
await destination .send (mentions , embed = embed )
411
-
412
502
if additional_images :
413
- self .ready = False
414
503
await asyncio .gather (* additional_images )
415
- self .ready = True
416
504
417
505
if delete_message :
418
- try :
419
- await message .delete ()
420
- except discord .HTTPException :
421
- pass
506
+ self .bot .loop .create_task (ignore (message .delete ()))
422
507
423
508
def get_notifications (self ):
424
509
config = self .bot .config
@@ -517,75 +602,21 @@ async def _find_from_channel(self, channel):
517
602
518
603
return thread
519
604
520
- async def create (self , recipient , * , creator = None , category = None ):
605
+ def create (self , recipient , * , creator = None , category = None ):
521
606
"""Creates a Modmail thread"""
522
-
523
- thread_creation_response = self .bot .config .get (
524
- 'thread_creation_response' ,
525
- 'The moderation team will get back to you as soon as possible!'
526
- )
527
-
528
- embed = discord .Embed (
529
- color = self .bot .mod_color ,
530
- description = thread_creation_response ,
531
- timestamp = datetime .utcnow (),
532
- )
533
- embed .set_footer (text = 'Your message has been sent' ,
534
- icon_url = self .bot .guild .icon_url )
535
- embed .set_author (name = 'Thread Created' )
536
-
537
- if creator is None :
538
- self .bot .loop .create_task (recipient .send (embed = embed ))
539
-
540
- # in case it creates a channel outside of category
541
- overwrites = {
542
- self .bot .modmail_guild .default_role :
543
- discord .PermissionOverwrite (read_messages = False )
544
- }
545
-
546
- category = category or self .bot .main_category
547
-
548
- if category is not None :
549
- overwrites = None
550
-
551
- channel = await self .bot .modmail_guild .create_text_channel (
552
- name = self ._format_channel_name (recipient ),
553
- category = category ,
554
- overwrites = overwrites ,
555
- reason = 'Creating a thread channel'
556
- )
557
-
558
- thread = Thread (self , recipient , channel )
607
+ # create thread immediately so messages can be processed
608
+ thread = Thread (self , recipient )
559
609
self .cache [recipient .id ] = thread
560
610
561
- log_url , log_data = await asyncio .gather (
562
- self .bot .api .create_log_entry (recipient , channel ,
563
- creator or recipient ),
564
- self .bot .api .get_user_logs (recipient .id )
565
- )
566
-
567
- log_count = sum (1 for log in log_data if not log ['open' ])
568
- info_embed = self ._format_info_embed (recipient , log_url , log_count ,
569
- discord .Color .green ())
570
-
571
- topic = f'User ID: { recipient .id } '
572
- if creator :
573
- mention = None
574
- else :
575
- mention = self .bot .config .get ('mention' , '@here' )
576
-
577
- _ , msg = await asyncio .gather (
578
- channel .edit (topic = topic ),
579
- channel .send (mention , embed = info_embed )
580
- )
581
-
582
- thread .ready = True
583
- await msg .pin ()
611
+ # Schedule thread setup for later
612
+ self .bot .loop .create_task (thread .setup (
613
+ creator = creator ,
614
+ category = category
615
+ ))
584
616
return thread
585
617
586
618
async def find_or_create (self , recipient ):
587
- return await self .find (recipient = recipient ) or \
588
- await self .create (recipient )
619
+ return await self .find (recipient = recipient ) or self .create (recipient )
589
620
590
621
def _format_channel_name (self , author ):
591
622
"""Sanitises a username for use with text channel names"""
0 commit comments