@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.
- package/LICENSE.md +713 -0
- package/README.md +104 -0
- package/dist/api/api.d.ts +26 -0
- package/dist/api/api.js +44 -0
- package/dist/api/main.d.ts +133 -0
- package/dist/api/main.js +87 -0
- package/dist/api/modules/action.d.ts +34 -0
- package/dist/api/modules/action.js +58 -0
- package/dist/api/modules/base.d.ts +329 -0
- package/dist/api/modules/base.js +804 -0
- package/dist/api/modules/builder.d.ts +647 -0
- package/dist/api/modules/builder.js +1441 -0
- package/dist/api/modules/checker.d.ts +648 -0
- package/dist/api/modules/checker.js +1324 -0
- package/dist/api/modules/client.d.ts +768 -0
- package/dist/api/modules/client.js +1859 -0
- package/dist/api/modules/code.d.ts +33 -0
- package/dist/api/modules/code.js +57 -0
- package/dist/api/modules/config.d.ts +70 -0
- package/dist/api/modules/config.js +206 -0
- package/dist/api/modules/console.d.ts +305 -0
- package/dist/api/modules/console.js +598 -0
- package/dist/api/modules/cooldown.d.ts +138 -0
- package/dist/api/modules/cooldown.js +359 -0
- package/dist/api/modules/database.d.ts +135 -0
- package/dist/api/modules/database.js +271 -0
- package/dist/api/modules/event.d.ts +43 -0
- package/dist/api/modules/event.js +100 -0
- package/dist/api/modules/flag.d.ts +40 -0
- package/dist/api/modules/flag.js +72 -0
- package/dist/api/modules/fuse.d.ts +218 -0
- package/dist/api/modules/fuse.js +123 -0
- package/dist/api/modules/helpmenu.d.ts +106 -0
- package/dist/api/modules/helpmenu.js +167 -0
- package/dist/api/modules/language.d.ts +85 -0
- package/dist/api/modules/language.js +195 -0
- package/dist/api/modules/permission.d.ts +121 -0
- package/dist/api/modules/permission.js +314 -0
- package/dist/api/modules/plugin.d.ts +128 -0
- package/dist/api/modules/plugin.js +168 -0
- package/dist/api/modules/post.d.ts +44 -0
- package/dist/api/modules/post.js +92 -0
- package/dist/api/modules/progressbar.d.ts +108 -0
- package/dist/api/modules/progressbar.js +233 -0
- package/dist/api/modules/responder.d.ts +506 -0
- package/dist/api/modules/responder.js +1468 -0
- package/dist/api/modules/session.d.ts +58 -0
- package/dist/api/modules/session.js +171 -0
- package/dist/api/modules/startscreen.d.ts +165 -0
- package/dist/api/modules/startscreen.js +293 -0
- package/dist/api/modules/stat.d.ts +142 -0
- package/dist/api/modules/stat.js +293 -0
- package/dist/api/modules/verifybar.d.ts +54 -0
- package/dist/api/modules/verifybar.js +60 -0
- package/dist/api/modules/worker.d.ts +41 -0
- package/dist/api/modules/worker.js +93 -0
- package/dist/api/utils.d.ts +61 -0
- package/dist/api/utils.js +254 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +40 -0
- package/dist/startup/dump.d.ts +14 -0
- package/dist/startup/dump.js +79 -0
- package/dist/startup/errorHandling.d.ts +2 -0
- package/dist/startup/errorHandling.js +43 -0
- package/dist/startup/pluginLauncher.d.ts +2 -0
- package/dist/startup/pluginLauncher.js +202 -0
- package/package.json +9 -3
- package/src/api/api.ts +29 -0
- package/src/api/main.ts +189 -0
- package/src/api/modules/action.ts +58 -0
- package/src/api/modules/base.ts +811 -0
- package/src/api/modules/builder.ts +1554 -0
- package/src/api/modules/checker.ts +1549 -0
- package/src/api/modules/client.ts +2247 -0
- package/src/api/modules/code.ts +58 -0
- package/src/api/modules/config.ts +159 -0
- package/src/api/modules/console.ts +665 -0
- package/src/api/modules/cooldown.ts +348 -0
- package/src/api/modules/database.ts +278 -0
- package/src/api/modules/event.ts +99 -0
- package/src/api/modules/flag.ts +73 -0
- package/src/api/modules/fuse.ts +348 -0
- package/src/api/modules/helpmenu.ts +216 -0
- package/src/api/modules/language.ts +201 -0
- package/src/api/modules/permission.ts +340 -0
- package/src/api/modules/plugin.ts +242 -0
- package/src/api/modules/post.ts +90 -0
- package/src/api/modules/progressbar.ts +232 -0
- package/src/api/modules/responder.ts +1420 -0
- package/src/api/modules/session.ts +155 -0
- package/src/api/modules/startscreen.ts +320 -0
- package/src/api/modules/stat.ts +313 -0
- package/src/api/modules/verifybar.ts +61 -0
- package/src/api/modules/worker.ts +93 -0
- package/src/api/utils.ts +206 -0
- package/src/cli/cli.ts +151 -0
- package/src/cli/editConfig.ts +943 -0
- package/src/index.ts +6 -1
- package/src/startup/compilation.ts +186 -0
- package/src/startup/dump.ts +45 -0
- package/src/startup/errorHandling.ts +38 -0
- package/src/startup/pluginLauncher.ts +261 -0
- 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
|
+
}
|