@open-discord-bots/framework 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/LICENSE.md +713 -0
  2. package/README.md +104 -0
  3. package/dist/api/api.d.ts +26 -0
  4. package/dist/api/api.js +44 -0
  5. package/dist/api/main.d.ts +133 -0
  6. package/dist/api/main.js +87 -0
  7. package/dist/api/modules/action.d.ts +34 -0
  8. package/dist/api/modules/action.js +58 -0
  9. package/dist/api/modules/base.d.ts +329 -0
  10. package/dist/api/modules/base.js +804 -0
  11. package/dist/api/modules/builder.d.ts +647 -0
  12. package/dist/api/modules/builder.js +1441 -0
  13. package/dist/api/modules/checker.d.ts +648 -0
  14. package/dist/api/modules/checker.js +1324 -0
  15. package/dist/api/modules/client.d.ts +768 -0
  16. package/dist/api/modules/client.js +1859 -0
  17. package/dist/api/modules/code.d.ts +33 -0
  18. package/dist/api/modules/code.js +57 -0
  19. package/dist/api/modules/config.d.ts +70 -0
  20. package/dist/api/modules/config.js +206 -0
  21. package/dist/api/modules/console.d.ts +305 -0
  22. package/dist/api/modules/console.js +598 -0
  23. package/dist/api/modules/cooldown.d.ts +138 -0
  24. package/dist/api/modules/cooldown.js +359 -0
  25. package/dist/api/modules/database.d.ts +135 -0
  26. package/dist/api/modules/database.js +271 -0
  27. package/dist/api/modules/event.d.ts +43 -0
  28. package/dist/api/modules/event.js +100 -0
  29. package/dist/api/modules/flag.d.ts +40 -0
  30. package/dist/api/modules/flag.js +72 -0
  31. package/dist/api/modules/fuse.d.ts +218 -0
  32. package/dist/api/modules/fuse.js +123 -0
  33. package/dist/api/modules/helpmenu.d.ts +106 -0
  34. package/dist/api/modules/helpmenu.js +167 -0
  35. package/dist/api/modules/language.d.ts +85 -0
  36. package/dist/api/modules/language.js +195 -0
  37. package/dist/api/modules/permission.d.ts +121 -0
  38. package/dist/api/modules/permission.js +314 -0
  39. package/dist/api/modules/plugin.d.ts +128 -0
  40. package/dist/api/modules/plugin.js +168 -0
  41. package/dist/api/modules/post.d.ts +44 -0
  42. package/dist/api/modules/post.js +92 -0
  43. package/dist/api/modules/progressbar.d.ts +108 -0
  44. package/dist/api/modules/progressbar.js +233 -0
  45. package/dist/api/modules/responder.d.ts +506 -0
  46. package/dist/api/modules/responder.js +1468 -0
  47. package/dist/api/modules/session.d.ts +58 -0
  48. package/dist/api/modules/session.js +171 -0
  49. package/dist/api/modules/startscreen.d.ts +165 -0
  50. package/dist/api/modules/startscreen.js +293 -0
  51. package/dist/api/modules/stat.d.ts +142 -0
  52. package/dist/api/modules/stat.js +293 -0
  53. package/dist/api/modules/verifybar.d.ts +54 -0
  54. package/dist/api/modules/verifybar.js +60 -0
  55. package/dist/api/modules/worker.d.ts +41 -0
  56. package/dist/api/modules/worker.js +93 -0
  57. package/dist/api/utils.d.ts +61 -0
  58. package/dist/api/utils.js +254 -0
  59. package/dist/index.d.ts +4 -1
  60. package/dist/index.js +40 -0
  61. package/dist/startup/dump.d.ts +14 -0
  62. package/dist/startup/dump.js +79 -0
  63. package/dist/startup/errorHandling.d.ts +2 -0
  64. package/dist/startup/errorHandling.js +43 -0
  65. package/dist/startup/pluginLauncher.d.ts +2 -0
  66. package/dist/startup/pluginLauncher.js +202 -0
  67. package/package.json +9 -3
  68. package/src/api/api.ts +29 -0
  69. package/src/api/main.ts +189 -0
  70. package/src/api/modules/action.ts +58 -0
  71. package/src/api/modules/base.ts +811 -0
  72. package/src/api/modules/builder.ts +1554 -0
  73. package/src/api/modules/checker.ts +1549 -0
  74. package/src/api/modules/client.ts +2247 -0
  75. package/src/api/modules/code.ts +58 -0
  76. package/src/api/modules/config.ts +159 -0
  77. package/src/api/modules/console.ts +665 -0
  78. package/src/api/modules/cooldown.ts +348 -0
  79. package/src/api/modules/database.ts +278 -0
  80. package/src/api/modules/event.ts +99 -0
  81. package/src/api/modules/flag.ts +73 -0
  82. package/src/api/modules/fuse.ts +348 -0
  83. package/src/api/modules/helpmenu.ts +216 -0
  84. package/src/api/modules/language.ts +201 -0
  85. package/src/api/modules/permission.ts +340 -0
  86. package/src/api/modules/plugin.ts +242 -0
  87. package/src/api/modules/post.ts +90 -0
  88. package/src/api/modules/progressbar.ts +232 -0
  89. package/src/api/modules/responder.ts +1420 -0
  90. package/src/api/modules/session.ts +155 -0
  91. package/src/api/modules/startscreen.ts +320 -0
  92. package/src/api/modules/stat.ts +313 -0
  93. package/src/api/modules/verifybar.ts +61 -0
  94. package/src/api/modules/worker.ts +93 -0
  95. package/src/api/utils.ts +206 -0
  96. package/src/cli/cli.ts +151 -0
  97. package/src/cli/editConfig.ts +943 -0
  98. package/src/index.ts +6 -1
  99. package/src/startup/compilation.ts +186 -0
  100. package/src/startup/dump.ts +45 -0
  101. package/src/startup/errorHandling.ts +38 -0
  102. package/src/startup/pluginLauncher.ts +261 -0
  103. package/LICENSE +0 -21
@@ -0,0 +1,1420 @@
1
+ ///////////////////////////////////////
2
+ //RESPONDER MODULE
3
+ ///////////////////////////////////////
4
+ import { ODId, ODManager, ODValidId, ODSystemError, ODManagerData } from "./base"
5
+ import * as discord from "discord.js"
6
+ import { ODWorkerManager, ODWorkerCallback, ODWorker } from "./worker"
7
+ import { ODDebugger } from "./console"
8
+ import { ODClientManager, ODContextMenu, ODSlashCommand, ODTextCommand, ODTextCommandInteractionOption } from "./client"
9
+ import { ODDropdownData, ODMessageBuildResult, ODMessageBuildSentResult, ODModalBuildResult } from "./builder"
10
+
11
+ /**## ODResponderImplementation `class`
12
+ * This is an Open Discord responder implementation.
13
+ *
14
+ * It is a basic implementation of the `ODWorkerManager` used by all `ODResponder` classes.
15
+ *
16
+ * This class can't be used stand-alone & needs to be extended from!
17
+ */
18
+ export class ODResponderImplementation<Instance,Source extends string,Params> extends ODManagerData {
19
+ /**The manager that has all workers of this implementation */
20
+ workers: ODWorkerManager<Instance,Source,Params>
21
+ /**The `commandName` or `customId` needs to match this string or regex for this responder to be executed. */
22
+ match: string|RegExp
23
+
24
+ constructor(id:ODValidId, match:string|RegExp, callback?:ODWorkerCallback<Instance,Source,Params>, priority?:number, callbackId?:ODValidId){
25
+ super(id)
26
+ this.match = match
27
+ this.workers = new ODWorkerManager("descending")
28
+ if (callback) this.workers.add(new ODWorker(callbackId ? callbackId : id,priority ?? 0,callback))
29
+ }
30
+ /**Execute all workers & return the result. */
31
+ async respond(instance:Instance, source:Source, params:Params): Promise<void> {
32
+ throw new ODSystemError("Tried to build an unimplemented ODResponderImplementation")
33
+ }
34
+ }
35
+
36
+ /**## ODResponderTimeoutErrorCallback `type`
37
+ * This is the callback for the responder timeout function. It will be executed when something went wrong or the action takes too much time.
38
+ */
39
+ export type ODResponderTimeoutErrorCallback<Instance, Source extends "slash"|"text"|"button"|"dropdown"|"modal"|"other"|"context-menu"|"autocomplete"> = (instance:Instance, source:Source) => void|Promise<void>
40
+
41
+ /**## ODResponderManager `class`
42
+ * This is an Open Discord responder manager.
43
+ *
44
+ * It contains all Open Discord responders. Responders can respond to an interaction, button, dropdown, modal or command.
45
+ *
46
+ * Using the Open Discord responder system has a few advantages compared to vanilla discord.js:
47
+ * - plugins can extend/edit replies
48
+ * - automatically reply on error
49
+ * - independent workers (with priority)
50
+ * - fail-safe design using try-catch
51
+ * - write code once => reply to both slash & text commands at the same time!
52
+ * - know where the request came from & parse options/subcommands & without errors!
53
+ * - And so much more!
54
+ */
55
+ export class ODResponderManager {
56
+ /**A manager for all (text & slash) command responders. */
57
+ commands: ODCommandResponderManager
58
+ /**A manager for all button responders. */
59
+ buttons: ODButtonResponderManager
60
+ /**A manager for all dropdown/select menu responders. */
61
+ dropdowns: ODDropdownResponderManager
62
+ /**A manager for all modal responders. */
63
+ modals: ODModalResponderManager
64
+ /**A manager for all context menu responders. */
65
+ contextMenus: ODContextMenuResponderManager
66
+ /**A manager for all autocomplete responders. */
67
+ autocomplete: ODAutocompleteResponderManager
68
+
69
+ constructor(debug:ODDebugger, client:ODClientManager){
70
+ this.commands = new ODCommandResponderManager(debug,"command responder",client)
71
+ this.buttons = new ODButtonResponderManager(debug,"button responder",client)
72
+ this.dropdowns = new ODDropdownResponderManager(debug,"dropdown responder",client)
73
+ this.modals = new ODModalResponderManager(debug,"modal responder",client)
74
+ this.contextMenus = new ODContextMenuResponderManager(debug,"context menu responder",client)
75
+ this.autocomplete = new ODAutocompleteResponderManager(debug,"autocomplete responder",client)
76
+ }
77
+ }
78
+
79
+ /**## ODCommandResponderManager `class`
80
+ * This is an Open Discord command responder manager.
81
+ *
82
+ * It contains all Open Discord command responders. These can respond to text & slash commands.
83
+ *
84
+ * Using the Open Discord responder system has a few advantages compared to vanilla discord.js:
85
+ * - plugins can extend/edit replies
86
+ * - automatically reply on error
87
+ * - independent workers (with priority)
88
+ * - fail-safe design using try-catch
89
+ * - write code once => reply to both slash & text commands at the same time!
90
+ * - know where the request came from & parse options/subcommands & without errors!
91
+ * - And so much more!
92
+ */
93
+ export class ODCommandResponderManager extends ODManager<ODCommandResponder<"slash"|"text",any>> {
94
+ /**An alias to the Open Discord client manager. */
95
+ #client: ODClientManager
96
+ /**The callback executed when the default workers take too much time to reply. */
97
+ #timeoutErrorCallback: ODResponderTimeoutErrorCallback<ODCommandResponderInstance,"slash"|"text">|null = null
98
+ /**The amount of milliseconds before the timeout error callback is executed. */
99
+ #timeoutMs: number|null = null
100
+
101
+ constructor(debug:ODDebugger, debugname:string, client:ODClientManager){
102
+ super(debug,debugname)
103
+ this.#client = client
104
+ }
105
+
106
+ /**Set the message to send when the response times out! */
107
+ setTimeoutErrorCallback(callback:ODResponderTimeoutErrorCallback<ODCommandResponderInstance,"slash"|"text">|null, ms:number|null){
108
+ this.#timeoutErrorCallback = callback
109
+ this.#timeoutMs = ms
110
+ }
111
+
112
+ add(data:ODCommandResponder<"slash"|"text",any>, overwrite?:boolean){
113
+ const res = super.add(data,overwrite)
114
+
115
+ //add the callback to the slash command manager
116
+ this.#client.slashCommands.onInteraction(data.match,(interaction,cmd) => {
117
+ const newData = this.get(data.id)
118
+ if (!newData) return
119
+ newData.respond(new ODCommandResponderInstance(interaction,cmd,this.#timeoutErrorCallback,this.#timeoutMs),"slash",{})
120
+ })
121
+
122
+ //add the callback to the text command manager
123
+ this.#client.textCommands.onInteraction(data.prefix,data.match,(interaction,cmd,options) => {
124
+ const newData = this.get(data.id)
125
+ if (!newData) return
126
+ newData.respond(new ODCommandResponderInstance(interaction,cmd,this.#timeoutErrorCallback,this.#timeoutMs,options),"text",{})
127
+ })
128
+
129
+ return res
130
+ }
131
+ }
132
+
133
+ /**## ODCommandResponderInstanceOptions `class`
134
+ * This is an Open Discord command responder instance options manager.
135
+ *
136
+ * This class will manage all options & subcommands from slash & text commands.
137
+ */
138
+ export class ODCommandResponderInstanceOptions {
139
+ /**The interaction to get data from. */
140
+ #interaction: discord.ChatInputCommandInteraction|discord.Message
141
+ /**The command which is related to the interaction. */
142
+ #cmd:ODSlashCommand|ODTextCommand
143
+ /**A list of options which have been parsed by the text command parser. */
144
+ #options: ODTextCommandInteractionOption[]
145
+
146
+ constructor(interaction:discord.ChatInputCommandInteraction|discord.Message, cmd:ODSlashCommand|ODTextCommand, options?:ODTextCommandInteractionOption[]){
147
+ this.#interaction = interaction
148
+ this.#cmd = cmd
149
+ this.#options = options ?? []
150
+ }
151
+
152
+ /**Get a string option. */
153
+ getString(name:string,required:true): string
154
+ getString(name:string,required:false): string|null
155
+ getString(name:string,required:boolean){
156
+ if (this.#interaction instanceof discord.ChatInputCommandInteraction){
157
+ try {
158
+ return this.#interaction.options.getString(name,required)
159
+ }catch{
160
+ throw new ODSystemError("ODCommandResponderInstanceOptions:getString() slash command option not found!")
161
+ }
162
+
163
+ }else if (this.#interaction instanceof discord.Message){
164
+ const opt = this.#options.find((opt) => opt.type == "string" && opt.name == name)
165
+ if (opt && typeof opt.value == "string") return opt.value
166
+ else return null
167
+
168
+ }else return null
169
+ }
170
+ /**Get a boolean option. */
171
+ getBoolean(name:string,required:true): boolean
172
+ getBoolean(name:string,required:false): boolean|null
173
+ getBoolean(name:string,required:boolean){
174
+ if (this.#interaction instanceof discord.ChatInputCommandInteraction){
175
+ try {
176
+ return this.#interaction.options.getBoolean(name,required)
177
+ }catch{
178
+ throw new ODSystemError("ODCommandResponderInstanceOptions:getBoolean() slash command option not found!")
179
+ }
180
+
181
+ }else if (this.#interaction instanceof discord.Message){
182
+ const opt = this.#options.find((opt) => opt.type == "boolean" && opt.name == name)
183
+ if (opt && typeof opt.value == "boolean") return opt.value
184
+ else return null
185
+
186
+ }else return null
187
+ }
188
+ /**Get a number option. */
189
+ getNumber(name:string,required:true): number
190
+ getNumber(name:string,required:false): number|null
191
+ getNumber(name:string,required:boolean){
192
+ if (this.#interaction instanceof discord.ChatInputCommandInteraction){
193
+ try {
194
+ return this.#interaction.options.getNumber(name,required)
195
+ }catch{
196
+ throw new ODSystemError("ODCommandResponderInstanceOptions:getNumber() slash command option not found!")
197
+ }
198
+
199
+ }else if (this.#interaction instanceof discord.Message){
200
+ const opt = this.#options.find((opt) => opt.type == "number" && opt.name == name)
201
+ if (opt && typeof opt.value == "number") return opt.value
202
+ else return null
203
+
204
+ }else return null
205
+ }
206
+ /**Get a channel option. */
207
+ getChannel(name:string,required:true): discord.TextChannel|discord.VoiceChannel|discord.StageChannel|discord.NewsChannel|discord.MediaChannel|discord.ForumChannel|discord.CategoryChannel
208
+ getChannel(name:string,required:false): discord.TextChannel|discord.VoiceChannel|discord.StageChannel|discord.NewsChannel|discord.MediaChannel|discord.ForumChannel|discord.CategoryChannel|null
209
+ getChannel(name:string,required:boolean){
210
+ if (this.#interaction instanceof discord.ChatInputCommandInteraction){
211
+ try {
212
+ return this.#interaction.options.getChannel(name,required)
213
+ }catch{
214
+ throw new ODSystemError("ODCommandResponderInstanceOptions:getChannel() slash command option not found!")
215
+ }
216
+
217
+ }else if (this.#interaction instanceof discord.Message){
218
+ const opt = this.#options.find((opt) => opt.type == "channel" && opt.name == name)
219
+ if (opt && (opt.value instanceof discord.TextChannel || opt.value instanceof discord.VoiceChannel || opt.value instanceof discord.StageChannel || opt.value instanceof discord.NewsChannel || opt.value instanceof discord.MediaChannel || opt.value instanceof discord.ForumChannel || opt.value instanceof discord.CategoryChannel)) return opt.value
220
+ else return null
221
+
222
+ }else return null
223
+ }
224
+ /**Get a role option. */
225
+ getRole(name:string,required:true): discord.Role
226
+ getRole(name:string,required:false): discord.Role|null
227
+ getRole(name:string,required:boolean){
228
+ if (this.#interaction instanceof discord.ChatInputCommandInteraction){
229
+ try {
230
+ return this.#interaction.options.getRole(name,required)
231
+ }catch{
232
+ throw new ODSystemError("ODCommandResponderInstanceOptions:getRole() slash command option not found!")
233
+ }
234
+
235
+ }else if (this.#interaction instanceof discord.Message){
236
+ const opt = this.#options.find((opt) => opt.type == "role" && opt.name == name)
237
+ if (opt && opt.value instanceof discord.Role) return opt.value
238
+ else return null
239
+
240
+ }else return null
241
+ }
242
+ /**Get a user option. */
243
+ getUser(name:string,required:true): discord.User
244
+ getUser(name:string,required:false): discord.User|null
245
+ getUser(name:string,required:boolean){
246
+ if (this.#interaction instanceof discord.ChatInputCommandInteraction){
247
+ try {
248
+ return this.#interaction.options.getUser(name,required)
249
+ }catch{
250
+ throw new ODSystemError("ODCommandResponderInstanceOptions:getUser() slash command option not found!")
251
+ }
252
+
253
+ }else if (this.#interaction instanceof discord.Message){
254
+ const opt = this.#options.find((opt) => opt.type == "user" && opt.name == name)
255
+ if (opt && opt.value instanceof discord.User) return opt.value
256
+ else return null
257
+
258
+ }else return null
259
+ }
260
+ /**Get a guild member option. */
261
+ getGuildMember(name:string,required:true): discord.GuildMember
262
+ getGuildMember(name:string,required:false): discord.GuildMember|null
263
+ getGuildMember(name:string,required:boolean){
264
+ if (this.#interaction instanceof discord.ChatInputCommandInteraction){
265
+ try {
266
+ const member = this.#interaction.options.getMember(name)
267
+ if (!member && required) throw new ODSystemError("ODCommandResponderInstanceOptions:getGuildMember() slash command option not found!")
268
+ return member
269
+ }catch{
270
+ throw new ODSystemError("ODCommandResponderInstanceOptions:getGuildMember() slash command option not found!")
271
+ }
272
+
273
+ }else if (this.#interaction instanceof discord.Message){
274
+ const opt = this.#options.find((opt) => opt.type == "guildmember" && opt.name == name)
275
+ if (opt && opt.value instanceof discord.GuildMember) return opt.value
276
+ else return null
277
+
278
+ }else return null
279
+ }
280
+ /**Get a mentionable option. */
281
+ getMentionable(name:string,required:true): discord.User|discord.GuildMember|discord.Role
282
+ getMentionable(name:string,required:false): discord.User|discord.GuildMember|discord.Role|null
283
+ getMentionable(name:string,required:boolean){
284
+ if (this.#interaction instanceof discord.ChatInputCommandInteraction){
285
+ try {
286
+ return this.#interaction.options.getMentionable(name,required)
287
+ }catch{
288
+ throw new ODSystemError("ODCommandResponderInstanceOptions:getGuildMember() slash command option not found!")
289
+ }
290
+
291
+ }else if (this.#interaction instanceof discord.Message){
292
+ const opt = this.#options.find((opt) => opt.type == "mentionable" && opt.name == name)
293
+ if (opt && (opt.value instanceof discord.User || opt.value instanceof discord.GuildMember || opt.value instanceof discord.Role)) return opt.value
294
+ else return null
295
+
296
+ }else return null
297
+ }
298
+ /**Get a subgroup. */
299
+ getSubGroup(): string|null
300
+ getSubGroup(){
301
+ if (this.#interaction instanceof discord.ChatInputCommandInteraction){
302
+ try {
303
+ return this.#interaction.options.getSubcommandGroup(true)
304
+ }catch{
305
+ throw new ODSystemError("ODCommandResponderInstanceOptions:getSubGroup() slash command option not found!")
306
+ }
307
+
308
+ }else if (this.#interaction instanceof discord.Message && this.#cmd instanceof ODTextCommand){
309
+ //0: name, 1:sub/group, 2:sub
310
+ const splittedName: string[] = this.#cmd.builder.name.split(" ")
311
+ return splittedName[1] ?? null
312
+
313
+ }else return null
314
+ }
315
+ /**Get a subcommand. */
316
+ getSubCommand(): string|null
317
+ getSubCommand(){
318
+ if (this.#interaction instanceof discord.ChatInputCommandInteraction){
319
+ try {
320
+ return this.#interaction.options.getSubcommand(true)
321
+ }catch{
322
+ throw new ODSystemError("ODCommandResponderInstanceOptions:getSubCommand() slash command option not found!")
323
+ }
324
+
325
+ }else if (this.#interaction instanceof discord.Message && this.#cmd instanceof ODTextCommand){
326
+ //0: name, 1:sub/group, 2:sub
327
+ const splittedName: string[] = this.#cmd.builder.name.split(" ")
328
+
329
+ //return the second subcommand when there is a subgroup
330
+ if (splittedName.length > 2){
331
+ return splittedName[2] ?? null
332
+ }else return splittedName[1] ?? null
333
+
334
+ }else return null
335
+ }
336
+ }
337
+
338
+
339
+ /**## ODCommandResponderInstance `class`
340
+ * This is an Open Discord command responder instance.
341
+ *
342
+ * An instance is an active slash interaction or used text command. You can reply to the command using `reply()` for both slash & text commands.
343
+ */
344
+ export class ODCommandResponderInstance {
345
+ /**The interaction which is the source of this instance. */
346
+ interaction: discord.ChatInputCommandInteraction|discord.Message
347
+ /**The command wich is the source of this instance. */
348
+ cmd:ODSlashCommand|ODTextCommand
349
+ /**The type/source of instance. (from text or slash command) */
350
+ type: "message"|"interaction"
351
+ /**Did a worker already reply to this instance/interaction? */
352
+ didReply: boolean = false
353
+ /**The manager for all options of this command. */
354
+ options: ODCommandResponderInstanceOptions
355
+ /**The user who triggered this command. */
356
+ user: discord.User
357
+ /**The guild member who triggered this command. */
358
+ member: discord.GuildMember|null
359
+ /**The guild where this command was triggered. */
360
+ guild: discord.Guild|null
361
+ /**The channel where this command was triggered. */
362
+ channel: discord.TextBasedChannel
363
+
364
+ constructor(interaction:discord.ChatInputCommandInteraction|discord.Message, cmd:ODSlashCommand|ODTextCommand, errorCallback:ODResponderTimeoutErrorCallback<ODCommandResponderInstance,"slash"|"text">|null, timeoutMs:number|null, options?:ODTextCommandInteractionOption[]){
365
+ if (!interaction.channel) throw new ODSystemError("ODCommandResponderInstance: Unable to find interaction channel!")
366
+ this.interaction = interaction
367
+ this.cmd = cmd
368
+ this.type = (interaction instanceof discord.Message) ? "message" : "interaction"
369
+ this.options = new ODCommandResponderInstanceOptions(interaction,cmd,options)
370
+ this.user = (interaction instanceof discord.Message) ? interaction.author : interaction.user
371
+ this.member = (interaction.member instanceof discord.GuildMember) ? interaction.member : null
372
+ this.guild = interaction.guild
373
+ this.channel = interaction.channel
374
+
375
+
376
+ setTimeout(async () => {
377
+ if (!this.didReply){
378
+ try {
379
+ if (!errorCallback){
380
+ this.reply({id:new ODId("looks-like-we-got-an-error-here"), ephemeral:true, message:{
381
+ content:":x: **Something went wrong while replying to this command!**"
382
+ }})
383
+ }else{
384
+ await errorCallback(this,(this.type == "interaction") ? "slash" : "text")
385
+ }
386
+
387
+ }catch(err){
388
+ process.emit("uncaughtException",err)
389
+ }
390
+ }
391
+ },timeoutMs ?? 2500)
392
+ }
393
+
394
+ /**Reply to this command. */
395
+ async reply(msg:ODMessageBuildResult): Promise<ODMessageBuildSentResult<boolean>> {
396
+ try {
397
+ const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : []
398
+ if (this.type == "interaction" && this.interaction instanceof discord.ChatInputCommandInteraction){
399
+ if (this.interaction.replied || this.interaction.deferred){
400
+ const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags}))
401
+ this.didReply = true
402
+ return {success:true,message:sent}
403
+ }else{
404
+ const sent = await this.interaction.reply(Object.assign(msg.message,{flags:msgFlags}))
405
+ this.didReply = true
406
+ return {success:true,message:await sent.fetch()}
407
+ }
408
+ }else if (this.type == "message" && this.interaction instanceof discord.Message && this.interaction.channel.type != discord.ChannelType.GroupDM){
409
+ const sent = await this.interaction.channel.send(msg.message)
410
+ this.didReply = true
411
+ return {success:true,message:sent}
412
+ }else return {success:false,message:null}
413
+ }catch{
414
+ return {success:false,message:null}
415
+ }
416
+ }
417
+ /**Defer this command. */
418
+ async defer(ephemeral:boolean){
419
+ if (this.type != "interaction" || !(this.interaction instanceof discord.ChatInputCommandInteraction)) return false
420
+ if (this.interaction.deferred || this.interaction.replied) return false
421
+ const msgFlags: number[] = ephemeral ? [discord.MessageFlags.Ephemeral] : []
422
+ await this.interaction.deferReply({flags:msgFlags})
423
+ this.didReply = true
424
+ return true
425
+ }
426
+ /**Show a modal as reply to this command. */
427
+ async modal(modal:ODModalBuildResult){
428
+ if (this.type != "interaction" || !(this.interaction instanceof discord.ChatInputCommandInteraction)) return false
429
+ if (this.interaction.deferred || this.interaction.replied) return false
430
+ await this.interaction.showModal(modal.modal)
431
+ this.didReply = true
432
+ return true
433
+ }
434
+ }
435
+
436
+ /**## ODCommandResponder `class`
437
+ * This is an Open Discord command responder.
438
+ *
439
+ * This class manages all workers which are executed when the related command is triggered.
440
+ */
441
+ export class ODCommandResponder<Source extends "slash"|"text",Params> extends ODResponderImplementation<ODCommandResponderInstance,Source,Params> {
442
+ /**The prefix of the text command needs to match this */
443
+ prefix: string
444
+
445
+ constructor(id:ODValidId, prefix:string, match:string|RegExp, callback?:ODWorkerCallback<ODCommandResponderInstance,Source,Params>, priority?:number, callbackId?:ODValidId){
446
+ super(id,match,callback,priority,callbackId)
447
+ this.prefix = prefix
448
+ }
449
+
450
+ /**Respond to this command */
451
+ async respond(instance:ODCommandResponderInstance, source:Source, params:Params){
452
+ //wait for workers to finish
453
+ await this.workers.executeWorkers(instance,source,params)
454
+ }
455
+ }
456
+
457
+ /**## ODButtonResponderManager `class`
458
+ * This is an Open Discord button responder manager.
459
+ *
460
+ * It contains all Open Discord button responders. These can respond to button interactions.
461
+ *
462
+ * Using the Open Discord responder system has a few advantages compared to vanilla discord.js:
463
+ * - plugins can extend/edit replies
464
+ * - automatically reply on error
465
+ * - independent workers (with priority)
466
+ * - fail-safe design using try-catch
467
+ * - know where the request came from!
468
+ * - And so much more!
469
+ */
470
+ export class ODButtonResponderManager extends ODManager<ODButtonResponder<"button",any>> {
471
+ /**An alias to the Open Discord client manager. */
472
+ #client: ODClientManager
473
+ /**The callback executed when the default workers take too much time to reply. */
474
+ #timeoutErrorCallback: ODResponderTimeoutErrorCallback<ODButtonResponderInstance,"button">|null = null
475
+ /**The amount of milliseconds before the timeout error callback is executed. */
476
+ #timeoutMs: number|null = null
477
+ /**A list of listeners which will listen to the raw interactionCreate event from discord.js */
478
+ #listeners: ((interaction:discord.ButtonInteraction) => void)[] = []
479
+
480
+ constructor(debug:ODDebugger, debugname:string, client:ODClientManager){
481
+ super(debug,debugname)
482
+ this.#client = client
483
+
484
+ this.#client.client.on("interactionCreate",(interaction) => {
485
+ if (!interaction.isButton()) return
486
+ this.#listeners.forEach((cb) => cb(interaction))
487
+ })
488
+ }
489
+
490
+ /**Set the message to send when the response times out! */
491
+ setTimeoutErrorCallback(callback:ODResponderTimeoutErrorCallback<ODButtonResponderInstance,"button">|null, ms:number|null){
492
+ this.#timeoutErrorCallback = callback
493
+ this.#timeoutMs = ms
494
+ }
495
+
496
+ add(data:ODButtonResponder<"button",any>, overwrite?:boolean){
497
+ const res = super.add(data,overwrite)
498
+
499
+ this.#listeners.push((interaction) => {
500
+ const newData = this.get(data.id)
501
+ if (!newData) return
502
+ if ((typeof newData.match == "string") ? interaction.customId == newData.match : newData.match.test(interaction.customId)) newData.respond(new ODButtonResponderInstance(interaction,this.#timeoutErrorCallback,this.#timeoutMs),"button",{})
503
+ })
504
+
505
+ return res
506
+ }
507
+ }
508
+
509
+ /**## ODButtonResponderInstance `class`
510
+ * This is an Open Discord button responder instance.
511
+ *
512
+ * An instance is an active button interaction. You can reply to the button using `reply()`.
513
+ */
514
+ export class ODButtonResponderInstance {
515
+ /**The interaction which is the source of this instance. */
516
+ interaction: discord.ButtonInteraction
517
+ /**Did a worker already reply to this instance/interaction? */
518
+ didReply: boolean = false
519
+ /**The user who triggered this button. */
520
+ user: discord.User
521
+ /**The guild member who triggered this button. */
522
+ member: discord.GuildMember|null
523
+ /**The guild where this button was triggered. */
524
+ guild: discord.Guild|null
525
+ /**The channel where this button was triggered. */
526
+ channel: discord.TextBasedChannel
527
+ /**The message this button originates from. */
528
+ message: discord.Message
529
+
530
+ constructor(interaction:discord.ButtonInteraction, errorCallback:ODResponderTimeoutErrorCallback<ODButtonResponderInstance,"button">|null, timeoutMs:number|null){
531
+ if (!interaction.channel) throw new ODSystemError("ODButtonResponderInstance: Unable to find interaction channel!")
532
+ this.interaction = interaction
533
+ this.user = interaction.user
534
+ this.member = (interaction.member instanceof discord.GuildMember) ? interaction.member : null
535
+ this.guild = interaction.guild
536
+ this.channel = interaction.channel
537
+ this.message = interaction.message
538
+
539
+ setTimeout(async () => {
540
+ if (!this.didReply){
541
+ try {
542
+ if (!errorCallback){
543
+ this.reply({id:new ODId("looks-like-we-got-an-error-here"), ephemeral:true, message:{
544
+ content:":x: **Something went wrong while replying to this button!**"
545
+ }})
546
+ }else{
547
+ await errorCallback(this,"button")
548
+ }
549
+
550
+ }catch(err){
551
+ process.emit("uncaughtException",err)
552
+ }
553
+ }
554
+ },timeoutMs ?? 2500)
555
+ }
556
+
557
+ /**Reply to this button. */
558
+ async reply(msg:ODMessageBuildResult): Promise<ODMessageBuildSentResult<boolean>> {
559
+ try{
560
+ const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : []
561
+ if (this.interaction.replied || this.interaction.deferred){
562
+ const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags}))
563
+ this.didReply = true
564
+ return {success:true,message:sent}
565
+ }else{
566
+ const sent = await this.interaction.reply(Object.assign(msg.message,{flags:msgFlags}))
567
+ this.didReply = true
568
+ return {success:true,message:await sent.fetch()}
569
+ }
570
+ }catch{
571
+ return {success:false,message:null}
572
+ }
573
+ }
574
+ /**Update the message of this button. */
575
+ async update(msg:ODMessageBuildResult): Promise<ODMessageBuildSentResult<boolean>> {
576
+ try{
577
+ const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : []
578
+ if (this.interaction.replied || this.interaction.deferred){
579
+ const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags}))
580
+ this.didReply = true
581
+ return {success:true,message:await sent.fetch()}
582
+ }else{
583
+ const sent = await this.interaction.update(Object.assign(msg.message,{flags:msgFlags}))
584
+ this.didReply = true
585
+ return {success:true,message:await sent.fetch()}
586
+ }
587
+ }catch{
588
+ return {success:false,message:null}
589
+ }
590
+ }
591
+ /**Defer this button. */
592
+ async defer(type:"reply"|"update", ephemeral:boolean){
593
+ if (this.interaction.deferred || this.interaction.replied) return false
594
+ if (type == "reply"){
595
+ const msgFlags: number[] = ephemeral ? [discord.MessageFlags.Ephemeral] : []
596
+ await this.interaction.deferReply({flags:msgFlags})
597
+ }else{
598
+ await this.interaction.deferUpdate()
599
+ }
600
+ this.didReply = true
601
+ return true
602
+ }
603
+ /**Show a modal as reply to this button. */
604
+ async modal(modal:ODModalBuildResult){
605
+ if (this.interaction.deferred || this.interaction.replied) return false
606
+ await this.interaction.showModal(modal.modal)
607
+ this.didReply = true
608
+ return true
609
+ }
610
+
611
+ /**Get a component from the original message of this button. */
612
+ getMessageComponent(type:"button",id:string|RegExp): discord.ButtonComponent|null
613
+ getMessageComponent(type:"string-dropdown",id:string|RegExp): discord.StringSelectMenuComponent|null
614
+ getMessageComponent(type:"user-dropdown",id:string|RegExp): discord.UserSelectMenuComponent|null
615
+ getMessageComponent(type:"channel-dropdown",id:string|RegExp): discord.ChannelSelectMenuComponent|null
616
+ getMessageComponent(type:"role-dropdown",id:string|RegExp): discord.RoleSelectMenuComponent|null
617
+ getMessageComponent(type:"mentionable-dropdown",id:string|RegExp): discord.MentionableSelectMenuComponent|null
618
+
619
+ getMessageComponent(type:"button"|"string-dropdown"|"user-dropdown"|"channel-dropdown"|"role-dropdown"|"mentionable-dropdown", id:string|RegExp): discord.ButtonComponent|discord.StringSelectMenuComponent|discord.RoleSelectMenuComponent|discord.ChannelSelectMenuComponent|discord.MentionableSelectMenuComponent|discord.UserSelectMenuComponent|null {
620
+ let result: discord.ButtonComponent|discord.StringSelectMenuComponent|discord.RoleSelectMenuComponent|discord.ChannelSelectMenuComponent|discord.MentionableSelectMenuComponent|discord.UserSelectMenuComponent|null = null
621
+ this.message.components.forEach((row) => {
622
+ if (row.type != discord.ComponentType.ActionRow) return
623
+ row.components.forEach((component) => {
624
+ if (type == "button" && component.type == discord.ComponentType.Button && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component
625
+ else if (type == "string-dropdown" && component.type == discord.ComponentType.StringSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component
626
+ else if (type == "user-dropdown" && component.type == discord.ComponentType.UserSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component
627
+ else if (type == "channel-dropdown" && component.type == discord.ComponentType.ChannelSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component
628
+ else if (type == "role-dropdown" && component.type == discord.ComponentType.RoleSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component
629
+ else if (type == "mentionable-dropdown" && component.type == discord.ComponentType.MentionableSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component
630
+ })
631
+ })
632
+
633
+ return result
634
+ }
635
+
636
+ /**Get the first embed of the original message if it exists. */
637
+ getMessageEmbed(): discord.Embed|null {
638
+ return this.message.embeds[0] ?? null
639
+ }
640
+ }
641
+
642
+ /**## ODButtonResponder `class`
643
+ * This is an Open Discord button responder.
644
+ *
645
+ * This class manages all workers which are executed when the related button is triggered.
646
+ */
647
+ export class ODButtonResponder<Source extends string,Params> extends ODResponderImplementation<ODButtonResponderInstance,Source,Params> {
648
+ /**Respond to this button */
649
+ async respond(instance:ODButtonResponderInstance, source:Source, params:Params){
650
+ //wait for workers to finish
651
+ await this.workers.executeWorkers(instance,source,params)
652
+ }
653
+ }
654
+
655
+ /**## ODDropdownResponderManager `class`
656
+ * This is an Open Discord dropdown responder manager.
657
+ *
658
+ * It contains all Open Discord dropdown responders. These can respond to dropdown interactions.
659
+ *
660
+ * Using the Open Discord responder system has a few advantages compared to vanilla discord.js:
661
+ * - plugins can extend/edit replies
662
+ * - automatically reply on error
663
+ * - independent workers (with priority)
664
+ * - fail-safe design using try-catch
665
+ * - know where the request came from!
666
+ * - And so much more!
667
+ */
668
+ export class ODDropdownResponderManager extends ODManager<ODDropdownResponder<"dropdown",any>> {
669
+ /**An alias to the Open Discord client manager. */
670
+ #client: ODClientManager
671
+ /**The callback executed when the default workers take too much time to reply. */
672
+ #timeoutErrorCallback: ODResponderTimeoutErrorCallback<ODDropdownResponderInstance,"dropdown">|null = null
673
+ /**The amount of milliseconds before the timeout error callback is executed. */
674
+ #timeoutMs: number|null = null
675
+ /**A list of listeners which will listen to the raw interactionCreate event from discord.js */
676
+ #listeners: ((interaction:discord.AnySelectMenuInteraction) => void)[] = []
677
+
678
+ constructor(debug:ODDebugger, debugname:string, client:ODClientManager){
679
+ super(debug,debugname)
680
+ this.#client = client
681
+
682
+ this.#client.client.on("interactionCreate",(interaction) => {
683
+ if (!interaction.isAnySelectMenu()) return
684
+ this.#listeners.forEach((cb) => cb(interaction))
685
+ })
686
+ }
687
+
688
+ /**Set the message to send when the response times out! */
689
+ setTimeoutErrorCallback(callback:ODResponderTimeoutErrorCallback<ODDropdownResponderInstance,"dropdown">|null, ms:number|null){
690
+ this.#timeoutErrorCallback = callback
691
+ this.#timeoutMs = ms
692
+ }
693
+
694
+ add(data:ODDropdownResponder<"dropdown",any>, overwrite?:boolean){
695
+ const res = super.add(data,overwrite)
696
+
697
+ this.#listeners.push((interaction) => {
698
+ const newData = this.get(data.id)
699
+ if (!newData) return
700
+ if ((typeof newData.match == "string") ? interaction.customId == newData.match : newData.match.test(interaction.customId)) newData.respond(new ODDropdownResponderInstance(interaction,this.#timeoutErrorCallback,this.#timeoutMs),"dropdown",{})
701
+ })
702
+
703
+ return res
704
+ }
705
+ }
706
+
707
+ /**## ODDropdownResponderInstanceValues `class`
708
+ * This is an Open Discord dropdown responder instance values manager.
709
+ *
710
+ * This class will manage all values from the dropdowns & select menus.
711
+ */
712
+ export class ODDropdownResponderInstanceValues {
713
+ /**The interaction to get data from. */
714
+ #interaction: discord.AnySelectMenuInteraction
715
+ /**The type of this dropdown. */
716
+ #type: ODDropdownData["type"]
717
+
718
+ constructor(interaction:discord.AnySelectMenuInteraction, type:ODDropdownData["type"]){
719
+ this.#interaction = interaction
720
+ this.#type = type
721
+
722
+ if (interaction.isChannelSelectMenu()){
723
+ interaction.values
724
+ }
725
+ }
726
+
727
+ /**Get the selected values. */
728
+ getStringValues(): string[] {
729
+ try {
730
+ return this.#interaction.values
731
+ }catch{
732
+ throw new ODSystemError("ODDropdownResponderInstanceValues:getStringValues() invalid values!")
733
+ }
734
+ }
735
+ /**Get the selected roles. */
736
+ async getRoleValues(): Promise<discord.Role[]> {
737
+ if (this.#type != "role") throw new ODSystemError("ODDropdownResponderInstanceValues:getRoleValues() dropdown type isn't role!")
738
+ try {
739
+ const result: discord.Role[] = []
740
+ for (const id of this.#interaction.values){
741
+ if (!this.#interaction.guild) break
742
+ const role = await this.#interaction.guild.roles.fetch(id)
743
+ if (role) result.push(role)
744
+ }
745
+ return result
746
+ }catch{
747
+ throw new ODSystemError("ODDropdownResponderInstanceValues:getRoleValues() invalid values!")
748
+ }
749
+ }
750
+ /**Get the selected users. */
751
+ async getUserValues(): Promise<discord.User[]> {
752
+ if (this.#type != "role") throw new ODSystemError("ODDropdownResponderInstanceValues:getUserValues() dropdown type isn't user!")
753
+ try {
754
+ const result: discord.User[] = []
755
+ for (const id of this.#interaction.values){
756
+ const user = await this.#interaction.client.users.fetch(id)
757
+ if (user) result.push(user)
758
+ }
759
+ return result
760
+ }catch{
761
+ throw new ODSystemError("ODDropdownResponderInstanceValues:getUserValues() invalid values!")
762
+ }
763
+ }
764
+ /**Get the selected channels. */
765
+ async getChannelValues(): Promise<discord.GuildBasedChannel[]> {
766
+ if (this.#type != "role") throw new ODSystemError("ODDropdownResponderInstanceValues:getChannelValues() dropdown type isn't channel!")
767
+ try {
768
+ const result: discord.GuildBasedChannel[] = []
769
+ for (const id of this.#interaction.values){
770
+ if (!this.#interaction.guild) break
771
+ const guild = await this.#interaction.guild.channels.fetch(id)
772
+ if (guild) result.push(guild)
773
+ }
774
+ return result
775
+ }catch{
776
+ throw new ODSystemError("ODDropdownResponderInstanceValues:getChannelValues() invalid values!")
777
+ }
778
+ }
779
+ }
780
+
781
+ /**## ODDropdownResponderInstance `class`
782
+ * This is an Open Discord dropdown responder instance.
783
+ *
784
+ * An instance is an active dropdown interaction. You can reply to the dropdown using `reply()`.
785
+ */
786
+ export class ODDropdownResponderInstance {
787
+ /**The interaction which is the source of this instance. */
788
+ interaction: discord.AnySelectMenuInteraction
789
+ /**Did a worker already reply to this instance/interaction? */
790
+ didReply: boolean = false
791
+ /**The dropdown type. */
792
+ type: ODDropdownData["type"]
793
+ /**The manager for all values of this dropdown. */
794
+ values: ODDropdownResponderInstanceValues
795
+ /**The user who triggered this dropdown. */
796
+ user: discord.User
797
+ /**The guild member who triggered this dropdown. */
798
+ member: discord.GuildMember|null
799
+ /**The guild where this dropdown was triggered. */
800
+ guild: discord.Guild|null
801
+ /**The channel where this dropdown was triggered. */
802
+ channel: discord.TextBasedChannel
803
+ /**The message this dropdown originates from. */
804
+ message: discord.Message
805
+
806
+ constructor(interaction:discord.AnySelectMenuInteraction, errorCallback:ODResponderTimeoutErrorCallback<ODDropdownResponderInstance,"dropdown">|null, timeoutMs:number|null){
807
+ if (!interaction.channel) throw new ODSystemError("ODDropdownResponderInstance: Unable to find interaction channel!")
808
+ this.interaction = interaction
809
+ if (interaction.isStringSelectMenu()){
810
+ this.type = "string"
811
+ }else if (interaction.isRoleSelectMenu()){
812
+ this.type = "role"
813
+ }else if (interaction.isUserSelectMenu()){
814
+ this.type = "user"
815
+ }else if (interaction.isChannelSelectMenu()){
816
+ this.type = "channel"
817
+ }else if (interaction.isMentionableSelectMenu()){
818
+ this.type = "mentionable"
819
+ }else throw new ODSystemError("ODDropdownResponderInstance: invalid dropdown type!")
820
+
821
+ this.values = new ODDropdownResponderInstanceValues(interaction,this.type)
822
+ this.user = interaction.user
823
+ this.member = (interaction.member instanceof discord.GuildMember) ? interaction.member : null
824
+ this.guild = interaction.guild
825
+ this.channel = interaction.channel
826
+ this.message = interaction.message
827
+
828
+ setTimeout(async () => {
829
+ if (!this.didReply){
830
+ try {
831
+ if (!errorCallback){
832
+ this.reply({id:new ODId("looks-like-we-got-an-error-here"), ephemeral:true, message:{
833
+ content:":x: **Something went wrong while replying to this dropdown!**"
834
+ }})
835
+ }else{
836
+ await errorCallback(this,"dropdown")
837
+ }
838
+
839
+ }catch(err){
840
+ process.emit("uncaughtException",err)
841
+ }
842
+ }
843
+ },timeoutMs ?? 2500)
844
+ }
845
+
846
+ /**Reply to this dropdown. */
847
+ async reply(msg:ODMessageBuildResult): Promise<ODMessageBuildSentResult<boolean>> {
848
+ try {
849
+ const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : []
850
+ if (this.interaction.replied || this.interaction.deferred){
851
+ const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags}))
852
+ this.didReply = true
853
+ return {success:true,message:sent}
854
+ }else{
855
+ const sent = await this.interaction.reply(Object.assign(msg.message,{flags:msgFlags}))
856
+ this.didReply = true
857
+ return {success:true,message:await sent.fetch()}
858
+ }
859
+ }catch{
860
+ return {success:false,message:null}
861
+ }
862
+ }
863
+ /**Update the message of this dropdown. */
864
+ async update(msg:ODMessageBuildResult): Promise<ODMessageBuildSentResult<boolean>> {
865
+ try{
866
+ const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : []
867
+ if (this.interaction.replied || this.interaction.deferred){
868
+ const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags}))
869
+ this.didReply = true
870
+ return {success:true,message:await sent.fetch()}
871
+ }else{
872
+ const sent = await this.interaction.update(Object.assign(msg.message,{flags:msgFlags}))
873
+ this.didReply = true
874
+ return {success:true,message:await sent.fetch()}
875
+ }
876
+ }catch{
877
+ return {success:false,message:null}
878
+ }
879
+ }
880
+ /**Defer this dropdown. */
881
+ async defer(type:"reply"|"update", ephemeral:boolean){
882
+ if (this.interaction.deferred || this.interaction.replied) return false
883
+ if (type == "reply"){
884
+ const msgFlags: number[] = ephemeral ? [discord.MessageFlags.Ephemeral] : []
885
+ await this.interaction.deferReply({flags:msgFlags})
886
+ }else{
887
+ await this.interaction.deferUpdate()
888
+ }
889
+ this.didReply = true
890
+ return true
891
+ }
892
+ /**Show a modal as reply to this dropdown. */
893
+ async modal(modal:ODModalBuildResult){
894
+ if (this.interaction.deferred || this.interaction.replied) return false
895
+ await this.interaction.showModal(modal.modal)
896
+ this.didReply = true
897
+ return true
898
+ }
899
+
900
+ /**Get a component from the original message of this dropdown. */
901
+ getMessageComponent(type:"button",id:string|RegExp): discord.ButtonComponent|null
902
+ getMessageComponent(type:"string-dropdown",id:string|RegExp): discord.StringSelectMenuComponent|null
903
+ getMessageComponent(type:"user-dropdown",id:string|RegExp): discord.UserSelectMenuComponent|null
904
+ getMessageComponent(type:"channel-dropdown",id:string|RegExp): discord.ChannelSelectMenuComponent|null
905
+ getMessageComponent(type:"role-dropdown",id:string|RegExp): discord.RoleSelectMenuComponent|null
906
+ getMessageComponent(type:"mentionable-dropdown",id:string|RegExp): discord.MentionableSelectMenuComponent|null
907
+
908
+ getMessageComponent(type:"button"|"string-dropdown"|"user-dropdown"|"channel-dropdown"|"role-dropdown"|"mentionable-dropdown", id:string|RegExp): discord.ButtonComponent|discord.StringSelectMenuComponent|discord.RoleSelectMenuComponent|discord.ChannelSelectMenuComponent|discord.MentionableSelectMenuComponent|discord.UserSelectMenuComponent|null {
909
+ let result: discord.ButtonComponent|discord.StringSelectMenuComponent|discord.RoleSelectMenuComponent|discord.ChannelSelectMenuComponent|discord.MentionableSelectMenuComponent|discord.UserSelectMenuComponent|null = null
910
+ this.message.components.forEach((row) => {
911
+ if (row.type != discord.ComponentType.ActionRow) return
912
+ row.components.forEach((component) => {
913
+ if (type == "button" && component.type == discord.ComponentType.Button && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component
914
+ else if (type == "string-dropdown" && component.type == discord.ComponentType.StringSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component
915
+ else if (type == "user-dropdown" && component.type == discord.ComponentType.UserSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component
916
+ else if (type == "channel-dropdown" && component.type == discord.ComponentType.ChannelSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component
917
+ else if (type == "role-dropdown" && component.type == discord.ComponentType.RoleSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component
918
+ else if (type == "mentionable-dropdown" && component.type == discord.ComponentType.MentionableSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component
919
+ })
920
+ })
921
+
922
+ return result
923
+ }
924
+
925
+ /**Get the first embed of the original message if it exists. */
926
+ getMessageEmbed(): discord.Embed|null {
927
+ return this.message.embeds[0] ?? null
928
+ }
929
+ }
930
+
931
+ /**## ODDropdownResponder `class`
932
+ * This is an Open Discord dropdown responder.
933
+ *
934
+ * This class manages all workers which are executed when the related dropdown is triggered.
935
+ */
936
+ export class ODDropdownResponder<Source extends string,Params> extends ODResponderImplementation<ODDropdownResponderInstance,Source,Params> {
937
+ /**Respond to this dropdown */
938
+ async respond(instance:ODDropdownResponderInstance, source:Source, params:Params){
939
+ //wait for workers to finish
940
+ await this.workers.executeWorkers(instance,source,params)
941
+ }
942
+ }
943
+
944
+ /**## ODModalResponderManager `class`
945
+ * This is an Open Discord modal responder manager.
946
+ *
947
+ * It contains all Open Discord modal responders. These can respond to modal interactions.
948
+ *
949
+ * Using the Open Discord responder system has a few advantages compared to vanilla discord.js:
950
+ * - plugins can extend/edit replies
951
+ * - automatically reply on error
952
+ * - independent workers (with priority)
953
+ * - fail-safe design using try-catch
954
+ * - know where the request came from!
955
+ * - And so much more!
956
+ */
957
+ export class ODModalResponderManager extends ODManager<ODModalResponder<"modal",any>> {
958
+ /**An alias to the Open Discord client manager. */
959
+ #client: ODClientManager
960
+ /**The callback executed when the default workers take too much time to reply. */
961
+ #timeoutErrorCallback: ODResponderTimeoutErrorCallback<ODModalResponderInstance,"modal">|null = null
962
+ /**The amount of milliseconds before the timeout error callback is executed. */
963
+ #timeoutMs: number|null = null
964
+ /**A list of listeners which will listen to the raw interactionCreate event from discord.js */
965
+ #listeners: ((interaction:discord.ModalSubmitInteraction) => void)[] = []
966
+
967
+ constructor(debug:ODDebugger, debugname:string, client:ODClientManager){
968
+ super(debug,debugname)
969
+ this.#client = client
970
+
971
+ this.#client.client.on("interactionCreate",(interaction) => {
972
+ if (!interaction.isModalSubmit()) return
973
+ this.#listeners.forEach((cb) => cb(interaction))
974
+ })
975
+ }
976
+
977
+ /**Set the message to send when the response times out! */
978
+ setTimeoutErrorCallback(callback:ODResponderTimeoutErrorCallback<ODModalResponderInstance,"modal">|null, ms:number|null){
979
+ this.#timeoutErrorCallback = callback
980
+ this.#timeoutMs = ms
981
+ }
982
+
983
+ add(data:ODModalResponder<"modal",any>, overwrite?:boolean){
984
+ const res = super.add(data,overwrite)
985
+
986
+ this.#listeners.push((interaction) => {
987
+ const newData = this.get(data.id)
988
+ if (!newData) return
989
+ if ((typeof newData.match == "string") ? interaction.customId == newData.match : newData.match.test(interaction.customId)) newData.respond(new ODModalResponderInstance(interaction,this.#timeoutErrorCallback,this.#timeoutMs),"modal",{})
990
+ })
991
+
992
+ return res
993
+ }
994
+ }
995
+
996
+ /**## ODModalResponderInstanceValues `class`
997
+ * This is an Open Discord modal responder instance values manager.
998
+ *
999
+ * This class will manage all fields from the modals.
1000
+ */
1001
+ export class ODModalResponderInstanceValues {
1002
+ /**The interaction to get data from. */
1003
+ #interaction: discord.ModalSubmitInteraction
1004
+
1005
+ constructor(interaction:discord.ModalSubmitInteraction){
1006
+ this.#interaction = interaction
1007
+ }
1008
+
1009
+ /**Get the value of a text field. */
1010
+ getTextField(name:string,required:true): string
1011
+ getTextField(name:string,required:false): string|null
1012
+ getTextField(name:string,required:boolean){
1013
+ try {
1014
+ const data = this.#interaction.fields.getField(name,discord.ComponentType.TextInput)
1015
+ if (!data && required) throw new ODSystemError("ODModalResponderInstanceValues:getTextField() field not found!")
1016
+ return (data) ? data.value : null
1017
+ }catch{
1018
+ throw new ODSystemError("ODModalResponderInstanceValues:getTextField() field not found!")
1019
+ }
1020
+ }
1021
+ }
1022
+
1023
+ /**## ODModalResponderInstance `class`
1024
+ * This is an Open Discord modal responder instance.
1025
+ *
1026
+ * An instance is an active modal interaction. You can reply to the modal using `reply()`.
1027
+ */
1028
+ export class ODModalResponderInstance {
1029
+ /**The interaction which is the source of this instance. */
1030
+ interaction: discord.ModalSubmitInteraction
1031
+ /**Did a worker already reply to this instance/interaction? */
1032
+ didReply: boolean = false
1033
+ /**The manager for all fields of this modal. */
1034
+ values: ODModalResponderInstanceValues
1035
+ /**The user who triggered this modal. */
1036
+ user: discord.User
1037
+ /**The guild member who triggered this modal. */
1038
+ member: discord.GuildMember|null
1039
+ /**The guild where this modal was triggered. */
1040
+ guild: discord.Guild|null
1041
+ /**The channel where this modal was triggered. */
1042
+ channel: discord.TextBasedChannel|null
1043
+
1044
+ constructor(interaction:discord.ModalSubmitInteraction, errorCallback:ODResponderTimeoutErrorCallback<ODModalResponderInstance,"modal">|null, timeoutMs:number|null){
1045
+ this.interaction = interaction
1046
+ this.values = new ODModalResponderInstanceValues(interaction)
1047
+ this.user = interaction.user
1048
+ this.member = (interaction.member instanceof discord.GuildMember) ? interaction.member : null
1049
+ this.guild = interaction.guild
1050
+ this.channel = interaction.channel
1051
+
1052
+ setTimeout(async () => {
1053
+ if (!this.didReply){
1054
+ try {
1055
+ if (!errorCallback){
1056
+ this.reply({id:new ODId("looks-like-we-got-an-error-here"), ephemeral:true, message:{
1057
+ content:":x: **Something went wrong while replying to this modal!**"
1058
+ }})
1059
+ }else{
1060
+ await errorCallback(this,"modal")
1061
+ }
1062
+
1063
+ }catch(err){
1064
+ process.emit("uncaughtException",err)
1065
+ }
1066
+ }
1067
+ },timeoutMs ?? 2500)
1068
+ }
1069
+
1070
+ /**Reply to this modal. */
1071
+ async reply(msg:ODMessageBuildResult): Promise<ODMessageBuildSentResult<boolean>> {
1072
+ try{
1073
+ const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : []
1074
+ if (this.interaction.replied || this.interaction.deferred){
1075
+ const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags}))
1076
+ this.didReply = true
1077
+ return {success:true,message:sent}
1078
+ }else{
1079
+ const sent = await this.interaction.reply(Object.assign(msg.message,{flags:msgFlags}))
1080
+ this.didReply = true
1081
+ return {success:true,message:await sent.fetch()}
1082
+ }
1083
+ }catch{
1084
+ return {success:false,message:null}
1085
+ }
1086
+ }
1087
+ /**Update the message of this modal. */
1088
+ async update(msg:ODMessageBuildResult): Promise<ODMessageBuildSentResult<boolean>> {
1089
+ try{
1090
+ const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : []
1091
+ if (this.interaction.replied || this.interaction.deferred){
1092
+ const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags}))
1093
+ this.didReply = true
1094
+ return {success:true,message:await sent.fetch()}
1095
+ }else throw new ODSystemError("Unable to update modal interaction!")
1096
+ }catch{
1097
+ return {success:false,message:null}
1098
+ }
1099
+ }
1100
+ /**Defer this modal. */
1101
+ async defer(type:"reply"|"update", ephemeral:boolean){
1102
+ if (this.interaction.deferred || this.interaction.replied) return false
1103
+ if (type == "reply"){
1104
+ const msgFlags: number[] = ephemeral ? [discord.MessageFlags.Ephemeral] : []
1105
+ await this.interaction.deferReply({flags:msgFlags})
1106
+ }else{
1107
+ await this.interaction.deferUpdate()
1108
+ }
1109
+ this.didReply = true
1110
+ return true
1111
+ }
1112
+ }
1113
+
1114
+ /**## ODModalResponder `class`
1115
+ * This is an Open Discord modal responder.
1116
+ *
1117
+ * This class manages all workers which are executed when the related modal is triggered.
1118
+ */
1119
+ export class ODModalResponder<Source extends string,Params> extends ODResponderImplementation<ODModalResponderInstance,Source,Params> {
1120
+ /**Respond to this modal */
1121
+ async respond(instance:ODModalResponderInstance, source:Source, params:Params){
1122
+ //wait for workers to finish
1123
+ await this.workers.executeWorkers(instance,source,params)
1124
+ }
1125
+ }
1126
+
1127
+ /**## ODContextMenuResponderManager `class`
1128
+ * This is an Open Discord context menu responder manager.
1129
+ *
1130
+ * It contains all Open Discord context menu responders. These can respond to user/message context menu interactions.
1131
+ *
1132
+ * Using the Open Discord responder system has a few advantages compared to vanilla discord.js:
1133
+ * - plugins can extend/edit replies
1134
+ * - automatically reply on error
1135
+ * - independent workers (with priority)
1136
+ * - fail-safe design using try-catch
1137
+ * - know where the request came from!
1138
+ * - And so much more!
1139
+ */
1140
+ export class ODContextMenuResponderManager extends ODManager<ODContextMenuResponder<"context-menu",any>> {
1141
+ /**An alias to the Open Discord client manager. */
1142
+ #client: ODClientManager
1143
+ /**The callback executed when the default workers take too much time to reply. */
1144
+ #timeoutErrorCallback: ODResponderTimeoutErrorCallback<ODContextMenuResponderInstance,"context-menu">|null = null
1145
+ /**The amount of milliseconds before the timeout error callback is executed. */
1146
+ #timeoutMs: number|null = null
1147
+
1148
+ constructor(debug:ODDebugger, debugname:string, client:ODClientManager){
1149
+ super(debug,debugname)
1150
+ this.#client = client
1151
+ }
1152
+
1153
+ /**Set the message to send when the response times out! */
1154
+ setTimeoutErrorCallback(callback:ODResponderTimeoutErrorCallback<ODContextMenuResponderInstance,"context-menu">|null, ms:number|null){
1155
+ this.#timeoutErrorCallback = callback
1156
+ this.#timeoutMs = ms
1157
+ }
1158
+
1159
+ add(data:ODContextMenuResponder<"context-menu",any>, overwrite?:boolean){
1160
+ const res = super.add(data,overwrite)
1161
+
1162
+ this.#client.contextMenus.onInteraction(data.match,(interaction,cmd) => {
1163
+ const newData = this.get(data.id)
1164
+ if (!newData) return
1165
+ newData.respond(new ODContextMenuResponderInstance(interaction,cmd,this.#timeoutErrorCallback,this.#timeoutMs),"context-menu",{})
1166
+ })
1167
+
1168
+ return res
1169
+ }
1170
+ }
1171
+
1172
+ /**## ODContextMenuResponderInstance `class`
1173
+ * This is an Open Discord context menu responder instance.
1174
+ *
1175
+ * An instance is an active context menu interaction. You can reply to the context menu using `reply()`.
1176
+ */
1177
+ export class ODContextMenuResponderInstance {
1178
+ /**The interaction which is the source of this instance. */
1179
+ interaction: discord.ContextMenuCommandInteraction
1180
+ /**Did a worker already reply to this instance/interaction? */
1181
+ didReply: boolean = false
1182
+ /**The context menu wich is the source of this instance. */
1183
+ menu:ODContextMenu
1184
+ /**The user who triggered this context menu. */
1185
+ user: discord.User
1186
+ /**The guild member who triggered this context menu. */
1187
+ member: discord.GuildMember|null
1188
+ /**The guild where this context menu was triggered. */
1189
+ guild: discord.Guild|null
1190
+ /**The channel where this context menu was triggered. */
1191
+ channel: discord.TextBasedChannel
1192
+ /**The target of this context menu (user or message). */
1193
+ target: discord.Message|discord.User
1194
+
1195
+ constructor(interaction:discord.ContextMenuCommandInteraction, menu:ODContextMenu, errorCallback:ODResponderTimeoutErrorCallback<ODContextMenuResponderInstance,"context-menu">|null, timeoutMs:number|null){
1196
+ if (!interaction.channel) throw new ODSystemError("ODContextMenuResponderInstance: Unable to find interaction channel!")
1197
+ this.interaction = interaction
1198
+ this.menu = menu
1199
+ this.user = interaction.user
1200
+ this.member = (interaction.member instanceof discord.GuildMember) ? interaction.member : null
1201
+ this.guild = interaction.guild
1202
+ this.channel = interaction.channel
1203
+ if (interaction.isMessageContextMenuCommand()) this.target = interaction.targetMessage
1204
+ else if (interaction.isUserContextMenuCommand()) this.target = interaction.targetUser
1205
+ else throw new ODSystemError("ODContextMenuResponderInstance: Invalid context menu type. Should be of the type User/Message!")
1206
+
1207
+ setTimeout(async () => {
1208
+ if (!this.didReply){
1209
+ try {
1210
+ if (!errorCallback){
1211
+ this.reply({id:new ODId("looks-like-we-got-an-error-here"), ephemeral:true, message:{
1212
+ content:":x: **Something went wrong while replying to this context menu!**"
1213
+ }})
1214
+ }else{
1215
+ await errorCallback(this,"context-menu")
1216
+ }
1217
+
1218
+ }catch(err){
1219
+ process.emit("uncaughtException",err)
1220
+ }
1221
+ }
1222
+ },timeoutMs ?? 2500)
1223
+ }
1224
+
1225
+ /**Reply to this context menu. */
1226
+ async reply(msg:ODMessageBuildResult): Promise<ODMessageBuildSentResult<boolean>> {
1227
+ try{
1228
+ const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : []
1229
+ if (this.interaction.replied || this.interaction.deferred){
1230
+ const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags}))
1231
+ this.didReply = true
1232
+ return {success:true,message:sent}
1233
+ }else{
1234
+ const sent = await this.interaction.reply(Object.assign(msg.message,{flags:msgFlags}))
1235
+ this.didReply = true
1236
+ return {success:true,message:await sent.fetch()}
1237
+ }
1238
+ }catch{
1239
+ return {success:false,message:null}
1240
+ }
1241
+ }
1242
+ /**Update the message of this context menu. */
1243
+ async update(msg:ODMessageBuildResult): Promise<ODMessageBuildSentResult<boolean>> {
1244
+ try{
1245
+ const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : []
1246
+ if (this.interaction.replied || this.interaction.deferred){
1247
+ const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags}))
1248
+ this.didReply = true
1249
+ return {success:true,message:await sent.fetch()}
1250
+ }else throw new ODSystemError("Unable to update context menu interaction!")
1251
+ }catch{
1252
+ return {success:false,message:null}
1253
+ }
1254
+ }
1255
+ /**Defer this context menu. */
1256
+ async defer(type:"reply", ephemeral:boolean){
1257
+ if (this.interaction.deferred || this.interaction.replied) return false
1258
+ if (type == "reply"){
1259
+ const msgFlags: number[] = ephemeral ? [discord.MessageFlags.Ephemeral] : []
1260
+ await this.interaction.deferReply({flags:msgFlags})
1261
+ }
1262
+ this.didReply = true
1263
+ return true
1264
+ }
1265
+ /**Show a modal as reply to this context menu. */
1266
+ async modal(modal:ODModalBuildResult){
1267
+ if (this.interaction.deferred || this.interaction.replied) return false
1268
+ await this.interaction.showModal(modal.modal)
1269
+ this.didReply = true
1270
+ return true
1271
+ }
1272
+ }
1273
+
1274
+ /**## ODContextMenuResponder `class`
1275
+ * This is an Open Discord context menu responder.
1276
+ *
1277
+ * This class manages all workers which are executed when the related context menu is triggered.
1278
+ */
1279
+ export class ODContextMenuResponder<Source extends string,Params> extends ODResponderImplementation<ODContextMenuResponderInstance,Source,Params> {
1280
+ /**Respond to this button */
1281
+ async respond(instance:ODContextMenuResponderInstance, source:Source, params:Params){
1282
+ //wait for workers to finish
1283
+ await this.workers.executeWorkers(instance,source,params)
1284
+ }
1285
+ }
1286
+
1287
+ /**## ODAutocompleteResponderManager `class`
1288
+ * This is an Open Discord autocomplete responder manager.
1289
+ *
1290
+ * It contains all Open Discord autocomplete responders. These can respond to autocomplete interactions.
1291
+ *
1292
+ * Using the Open Discord responder system has a few advantages compared to vanilla discord.js:
1293
+ * - plugins can extend/edit replies
1294
+ * - automatically reply on error
1295
+ * - independent workers (with priority)
1296
+ * - fail-safe design using try-catch
1297
+ * - know where the request came from!
1298
+ * - And so much more!
1299
+ */
1300
+ export class ODAutocompleteResponderManager extends ODManager<ODAutocompleteResponder<"autocomplete",any>> {
1301
+ /**An alias to the Open Discord client manager. */
1302
+ #client: ODClientManager
1303
+ /**The callback executed when the default workers take too much time to reply. */
1304
+ #timeoutErrorCallback: ODResponderTimeoutErrorCallback<ODAutocompleteResponderInstance,"autocomplete">|null = null
1305
+ /**The amount of milliseconds before the timeout error callback is executed. */
1306
+ #timeoutMs: number|null = null
1307
+
1308
+ constructor(debug:ODDebugger, debugname:string, client:ODClientManager){
1309
+ super(debug,debugname)
1310
+ this.#client = client
1311
+ }
1312
+
1313
+ /**Set the message to send when the response times out! */
1314
+ setTimeoutErrorCallback(callback:ODResponderTimeoutErrorCallback<ODAutocompleteResponderInstance,"autocomplete">|null, ms:number|null){
1315
+ this.#timeoutErrorCallback = callback
1316
+ this.#timeoutMs = ms
1317
+ }
1318
+
1319
+ add(data:ODAutocompleteResponder<"autocomplete",any>, overwrite?:boolean){
1320
+ const res = super.add(data,overwrite)
1321
+
1322
+ this.#client.autocompletes.onInteraction(data.cmdMatch,data.match,(interaction) => {
1323
+ const newData = this.get(data.id)
1324
+ if (!newData) return
1325
+ newData.respond(new ODAutocompleteResponderInstance(interaction,this.#timeoutErrorCallback,this.#timeoutMs),"autocomplete",{})
1326
+ })
1327
+
1328
+ return res
1329
+ }
1330
+ }
1331
+
1332
+ /**## ODAutocompleteResponderInstance `class`
1333
+ * This is an Open Discord autocomplete responder instance.
1334
+ *
1335
+ * An instance is an active autocomplete interaction. You can reply to the autocomplete using `reply()`.
1336
+ */
1337
+ export class ODAutocompleteResponderInstance {
1338
+ /**The interaction which is the source of this instance. */
1339
+ interaction: discord.AutocompleteInteraction
1340
+ /**Did a worker already respond to this instance/interaction? */
1341
+ didRespond: boolean = false
1342
+ /**The user who triggered this autocomplete. */
1343
+ user: discord.User
1344
+ /**The guild member who triggered this autocomplete. */
1345
+ member: discord.GuildMember|null
1346
+ /**The guild where this autocomplete was triggered. */
1347
+ guild: discord.Guild|null
1348
+ /**The channel where this autocomplete was triggered. */
1349
+ channel: discord.TextBasedChannel
1350
+ /**The target slash command option of this autocomplete. */
1351
+ target: discord.AutocompleteFocusedOption
1352
+
1353
+ constructor(interaction:discord.AutocompleteInteraction, errorCallback:ODResponderTimeoutErrorCallback<ODAutocompleteResponderInstance,"autocomplete">|null, timeoutMs:number|null){
1354
+ if (!interaction.channel) throw new ODSystemError("ODAutocompleteResponderInstance: Unable to find interaction channel!")
1355
+ this.interaction = interaction
1356
+ this.user = interaction.user
1357
+ this.member = (interaction.member instanceof discord.GuildMember) ? interaction.member : null
1358
+ this.guild = interaction.guild
1359
+ this.channel = interaction.channel
1360
+ this.target = interaction.options.getFocused(true)
1361
+
1362
+ setTimeout(async () => {
1363
+ if (!this.didRespond){
1364
+ process.emit("uncaughtException",new ODSystemError("Autocomplete responder instance failed to respond widthin 2.5sec!"))
1365
+ }
1366
+ },timeoutMs ?? 2500)
1367
+ }
1368
+
1369
+ /**Reply to this autocomplete. */
1370
+ async autocomplete(choices:(string|discord.ApplicationCommandOptionChoiceData)[]): Promise<{success:boolean}> {
1371
+ const newChoices: (discord.ApplicationCommandOptionChoiceData)[] = choices.map((raw) => {
1372
+ if (typeof raw == "string") return {name:raw,value:raw}
1373
+ else return raw
1374
+ })
1375
+
1376
+ try{
1377
+ if (this.interaction.responded){
1378
+ return {success:false}
1379
+ }else{
1380
+ await this.interaction.respond(newChoices)
1381
+ this.didRespond = true
1382
+ return {success:true}
1383
+ }
1384
+ }catch(err){
1385
+ process.emit("uncaughtException",err)
1386
+ return {success:false}
1387
+ }
1388
+ }
1389
+ /**Reply to this autocomplete, but filter choices based on the input of the user. */
1390
+ async filteredAutocomplete(choices:(string|discord.ApplicationCommandOptionChoiceData)[]): Promise<{success:boolean}> {
1391
+ const newChoices: (discord.ApplicationCommandOptionChoiceData)[] = choices.map((raw) => {
1392
+ if (typeof raw == "string") return {name:raw,value:raw}
1393
+ else return raw
1394
+ })
1395
+
1396
+ const filteredChoices = newChoices.filter((choice) => choice.name.startsWith(this.target.value) || choice.value.toString().startsWith(this.target.value)).slice(0,25)
1397
+ return await this.autocomplete(filteredChoices)
1398
+ }
1399
+ }
1400
+
1401
+ /**## ODAutocompleteResponder `class`
1402
+ * This is an Open Discord autocomplete responder.
1403
+ *
1404
+ * This class manages all workers which are executed when the related autocomplete is triggered.
1405
+ */
1406
+ export class ODAutocompleteResponder<Source extends string,Params> extends ODResponderImplementation<ODAutocompleteResponderInstance,Source,Params> {
1407
+ /**The slash command of the autocomplete should match the following regex. */
1408
+ cmdMatch: string|RegExp
1409
+
1410
+ constructor(id:ODValidId,cmdMatch:string|RegExp,match:string|RegExp,callback?:ODWorkerCallback<ODAutocompleteResponderInstance,Source,Params>,priority?:number,callbackId?:ODValidId){
1411
+ super(id,match,callback,priority,callbackId)
1412
+ this.cmdMatch = cmdMatch
1413
+ }
1414
+
1415
+ /**Respond to this autocomplete interaction. */
1416
+ async respond(instance:ODAutocompleteResponderInstance, source:Source, params:Params){
1417
+ //wait for workers to finish
1418
+ await this.workers.executeWorkers(instance,source,params)
1419
+ }
1420
+ }