@@ -5,15 +5,18 @@ import {
5
5
listener ,
6
6
CommonInhibitors ,
7
7
} from 'cookiecord' ;
8
+ import { ThreadAutoArchiveDuration } from 'discord-api-types' ;
8
9
import {
9
10
Message ,
10
11
MessageReaction ,
11
12
GuildMember ,
12
13
User ,
13
14
ReactionEmoji ,
14
15
TextChannel ,
16
+ ThreadChannel ,
15
17
} from 'discord.js' ;
16
- import { MessageChannel } from 'worker_threads' ;
18
+ import { MessageChannel , threadId } from 'worker_threads' ;
19
+ import { suggestionsChannelId } from '../env' ;
17
20
import {
18
21
clearMessageOwnership ,
19
22
DELETE_EMOJI ,
@@ -22,6 +25,8 @@ import {
22
25
23
26
const emojiRegex = / < : \w + ?: ( \d + ?) > | ( \p{ Emoji_Presentation} ) / gu;
24
27
28
+ const defaultPollEmojis = [ '✅' , '❌' , '🤷' ] ;
29
+
25
30
export class EtcModule extends Module {
26
31
constructor ( client : CookiecordClient ) {
27
32
super ( client ) ;
@@ -33,18 +38,67 @@ export class EtcModule extends Module {
33
38
}
34
39
35
40
@listener ( { event : 'messageCreate' } )
36
- async onMessage ( msg : Message ) {
41
+ async onPoll ( msg : Message ) {
37
42
if ( msg . author . bot || ! msg . content . toLowerCase ( ) . startsWith ( 'poll:' ) )
38
43
return ;
39
44
let emojis = [
40
45
...new Set (
41
46
[ ...msg . content . matchAll ( emojiRegex ) ] . map ( x => x [ 1 ] ?? x [ 2 ] ) ,
42
47
) ,
43
48
] ;
44
- if ( ! emojis . length ) emojis = [ '✅' , '❌' , '🤷' ] ;
49
+ if ( ! emojis . length ) emojis = defaultPollEmojis ;
45
50
for ( const emoji of emojis ) await msg . react ( emoji ) ;
46
51
}
47
52
53
+ @listener ( { event : 'messageCreate' } )
54
+ async onSuggestion ( msg : Message ) {
55
+ if ( msg . author . bot || msg . channelId !== suggestionsChannelId ) return ;
56
+ // First 50 characters of the first line of the content (without cutting off a word)
57
+ const title =
58
+ msg . content
59
+ . split ( '\n' ) [ 0 ]
60
+ . split ( / ( ^ .{ 0 , 50 } \b ) / )
61
+ . find ( x => x ) ?? 'Suggestion' ;
62
+ await msg . startThread ( {
63
+ name : title ,
64
+ autoArchiveDuration : ThreadAutoArchiveDuration . OneDay ,
65
+ } ) ;
66
+ for ( let emoji of defaultPollEmojis ) await msg . react ( emoji ) ;
67
+ }
68
+
69
+ @listener ( { event : 'threadUpdate' } )
70
+ async onSuggestionClose ( thread : ThreadChannel ) {
71
+ if (
72
+ thread . parentId !== suggestionsChannelId ||
73
+ ! ( ( await thread . fetch ( ) ) as ThreadChannel ) . archived
74
+ )
75
+ return ;
76
+ const channel = thread . parent ! ;
77
+ let lastMessage = null ;
78
+ let suggestion : Message ;
79
+ while ( ! suggestion ! ) {
80
+ const msgs = await channel . messages . fetch ( {
81
+ before : lastMessage ?? undefined ,
82
+ limit : 5 ,
83
+ } ) ;
84
+ suggestion = msgs . find ( msg => msg . thread ?. id === thread . id ) ! ;
85
+ lastMessage = msgs . last ( ) ! . id as string ;
86
+ }
87
+ const pollingResults = defaultPollEmojis . map ( emoji => {
88
+ const reactions = suggestion . reactions . resolve ( emoji ) ;
89
+ // Subtract the bot's vote
90
+ const count = ( reactions ?. count ?? 0 ) - 1 ;
91
+ return [ emoji , count ] as const ;
92
+ } ) ;
93
+ const pollingResultStr = pollingResults
94
+ . sort ( ( a , b ) => b [ 1 ] - a [ 1 ] )
95
+ . map ( ( [ emoji , count ] , i ) => `${ count } ${ emoji } ` )
96
+ . join ( ' ' ) ;
97
+ await suggestion . reply ( {
98
+ content : `Polling finished; result: ${ pollingResultStr } ` ,
99
+ } ) ;
100
+ }
101
+
48
102
@listener ( { event : 'messageReactionAdd' } )
49
103
async onReact ( reaction : MessageReaction , member : GuildMember ) {
50
104
if ( reaction . partial ) return ;
0 commit comments