1
- import type { ChatInputCommandInteraction } from "discord.js" ;
1
+ import { TextChannel , type ChatInputCommandInteraction } from "discord.js" ;
2
2
import {
3
3
ChannelType ,
4
4
ComponentType ,
@@ -25,13 +25,38 @@ import type {
25
25
SlashCommand ,
26
26
} from "#~/helpers/discord" ;
27
27
import { quoteMessageContent } from "#~/helpers/discord" ;
28
+ import db from "#~/db.server.js" ;
28
29
29
30
const rest = new REST ( { version : "10" } ) . setToken ( discordToken ) ;
30
31
32
+ const DEFAULT_BUTTON_TEXT = "Open a private ticket with the moderators" ;
33
+
31
34
export default [
32
35
{
33
36
command : new SlashCommandBuilder ( )
34
37
. setName ( "tickets-channel" )
38
+ . addRoleOption ( ( o ) => {
39
+ o . setName ( "role" ) ;
40
+ o . setDescription (
41
+ "Which role (if any) should be pinged when a ticket is created?" ,
42
+ ) ;
43
+ o . setRequired ( false ) ;
44
+ return o ;
45
+ } )
46
+ . addStringOption ( ( o ) => {
47
+ o . setName ( "button-text" ) ;
48
+ o . setDescription (
49
+ `What should the button say? If left blank, it will say "${ DEFAULT_BUTTON_TEXT } "` ,
50
+ ) ;
51
+ return o ;
52
+ } )
53
+ . addChannelOption ( ( o ) => {
54
+ o . setName ( "channel" ) ;
55
+ o . setDescription (
56
+ "Which channel (if not this one) should tickets be created in?" ,
57
+ ) ;
58
+ return o ;
59
+ } )
35
60
. setDescription (
36
61
"Set up a new button for creating private tickets with moderators" ,
37
62
)
@@ -42,21 +67,56 @@ export default [
42
67
handler : async ( interaction : ChatInputCommandInteraction ) => {
43
68
if ( ! interaction . guild ) throw new Error ( "Interaction has no guild" ) ;
44
69
45
- await interaction . reply ( {
46
- components : [
47
- {
48
- type : ComponentType . ActionRow ,
49
- components : [
50
- {
51
- type : ComponentType . Button ,
52
- label : "Open a private ticket with the moderators" ,
53
- style : ButtonStyle . Primary ,
54
- customId : "open-ticket" ,
55
- } ,
56
- ] ,
57
- } ,
58
- ] ,
59
- } ) ;
70
+ const pingableRole = interaction . options . getRole ( "role" ) ;
71
+ const ticketChannel = interaction . options . getChannel ( "channel" ) ;
72
+ const buttonText =
73
+ interaction . options . getString ( "button-text" ) || DEFAULT_BUTTON_TEXT ;
74
+
75
+ if ( ticketChannel && ticketChannel . type !== ChannelType . GuildText ) {
76
+ await interaction . reply ( {
77
+ content : `The channel configured must be a text channel! Tickets will be created as private threads.` ,
78
+ } ) ;
79
+ return ;
80
+ }
81
+
82
+ try {
83
+ const interactionResponse = await interaction . reply ( {
84
+ components : [
85
+ {
86
+ type : ComponentType . ActionRow ,
87
+ components : [
88
+ {
89
+ type : ComponentType . Button ,
90
+ label : buttonText ,
91
+ style : ButtonStyle . Primary ,
92
+ customId : "open-ticket" ,
93
+ } ,
94
+ ] ,
95
+ } ,
96
+ ] ,
97
+ } ) ;
98
+ const producedMessage = await interactionResponse . fetch ( ) ;
99
+
100
+ let roleId = pingableRole ?. id ;
101
+ if ( ! roleId ) {
102
+ const { [ SETTINGS . moderator ] : mod } = await fetchSettings (
103
+ interaction . guild ,
104
+ [ SETTINGS . moderator , SETTINGS . modLog ] ,
105
+ ) ;
106
+ roleId = mod ;
107
+ }
108
+
109
+ await db
110
+ . insertInto ( "tickets_config" )
111
+ . values ( {
112
+ message_id : producedMessage . id ,
113
+ channel_id : ticketChannel ?. id ,
114
+ role_id : roleId ,
115
+ } )
116
+ . execute ( ) ;
117
+ } catch ( e ) {
118
+ console . error ( `error:` , e ) ;
119
+ }
60
120
} ,
61
121
} as SlashCommand ,
62
122
{
@@ -87,7 +147,8 @@ export default [
87
147
! interaction . channel ||
88
148
interaction . channel . type !== ChannelType . GuildText ||
89
149
! interaction . user ||
90
- ! interaction . guild
150
+ ! interaction . guild ||
151
+ ! interaction . message
91
152
) {
92
153
await interaction . reply ( {
93
154
content : "Something went wrong while creating a ticket" ,
@@ -98,19 +159,43 @@ export default [
98
159
const { channel, fields, user } = interaction ;
99
160
const concern = fields . getField ( "concern" ) . value ;
100
161
101
- const { [ SETTINGS . moderator ] : mod } = await fetchSettings (
102
- interaction . guild ,
103
- [ SETTINGS . moderator , SETTINGS . modLog ] ,
104
- ) ;
105
- const thread = await channel . threads . create ( {
162
+ let config = await db
163
+ . selectFrom ( "tickets_config" )
164
+ . selectAll ( )
165
+ . where ( "message_id" , "=" , interaction . message . id )
166
+ . executeTakeFirst ( ) ;
167
+ // If there's no config, that means that the button was set up before the db was set up. Add one with default values
168
+ if ( ! config ) {
169
+ const { [ SETTINGS . moderator ] : mod } = await fetchSettings (
170
+ interaction . guild ,
171
+ [ SETTINGS . moderator , SETTINGS . modLog ] ,
172
+ ) ;
173
+ config = await db
174
+ . insertInto ( "tickets_config" )
175
+ . returningAll ( )
176
+ . values ( { message_id : interaction . message . id , role_id : mod } )
177
+ . executeTakeFirst ( ) ;
178
+ if ( ! config ) {
179
+ throw new Error ( "Something went wrong while fixing tickets config" ) ;
180
+ }
181
+ }
182
+
183
+ const ticketsChannel = config . channel_id
184
+ ? ( ( await interaction . guild . channels . fetch (
185
+ config . channel_id ,
186
+ ) ) as TextChannel ) || channel
187
+ : channel ;
188
+
189
+ const thread = await ticketsChannel . threads . create ( {
106
190
name : `${ user . username } – ${ format ( new Date ( ) , "PP kk:mmX" ) } ` ,
107
191
autoArchiveDuration : 60 * 24 * 7 ,
108
192
type : ChannelType . PrivateThread ,
109
193
} ) ;
110
194
await thread . send ( {
111
- content : `<@${ user . id } >, this is a private space only visible to you and the <@&${ mod } > role.` ,
195
+ content : `<@${ user . id } >, this is a private space only visible to you and the <@&${ config . role_id } > role.` ,
112
196
} ) ;
113
- await thread . send ( quoteMessageContent ( concern ) ) ;
197
+ await thread . send ( `${ user . displayName } said:
198
+ ${ quoteMessageContent ( concern ) } `) ;
114
199
await thread . send ( {
115
200
content : "When you’ve finished, please close the ticket." ,
116
201
components : [
@@ -143,7 +228,6 @@ export default [
143
228
command : { type : InteractionType . MessageComponent , name : "close-ticket" } ,
144
229
handler : async ( interaction ) => {
145
230
const [ , ticketOpenerUserId , feedback ] = interaction . customId . split ( "||" ) ;
146
- console . log ( ticketOpenerUserId , feedback , interaction . customId ) ;
147
231
const threadId = interaction . channelId ;
148
232
if ( ! interaction . member || ! interaction . guild ) {
149
233
console . error (
0 commit comments