@open-discord-bots/framework 0.2.17 → 0.3.0

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 (49) hide show
  1. package/dist/api/index.d.ts +16 -15
  2. package/dist/api/index.js +16 -15
  3. package/dist/api/main.d.ts +31 -23
  4. package/dist/api/main.js +3 -1
  5. package/dist/api/modules/action.d.ts +2 -2
  6. package/dist/api/modules/action.js +1 -5
  7. package/dist/api/modules/base.d.ts +2 -2
  8. package/dist/api/modules/builder.d.ts +2 -2
  9. package/dist/api/modules/builder.js +0 -4
  10. package/dist/api/modules/checker.d.ts +2 -2
  11. package/dist/api/modules/checker.js +2 -6
  12. package/dist/api/modules/component.d.ts +922 -0
  13. package/dist/api/modules/component.js +1344 -0
  14. package/dist/api/modules/config.d.ts +30 -1
  15. package/dist/api/modules/config.js +81 -0
  16. package/dist/api/modules/cooldown.d.ts +5 -5
  17. package/dist/api/modules/cooldown.js +1 -17
  18. package/dist/api/modules/database.d.ts +21 -13
  19. package/dist/api/modules/database.js +0 -23
  20. package/dist/api/modules/helpmenu.d.ts +9 -7
  21. package/dist/api/modules/helpmenu.js +22 -17
  22. package/dist/api/modules/language.d.ts +2 -2
  23. package/dist/api/modules/language.js +3 -7
  24. package/dist/api/modules/progressbar.d.ts +2 -1
  25. package/dist/api/modules/progressbar.js +1 -1
  26. package/dist/api/modules/responder.d.ts +2 -2
  27. package/dist/api/modules/responder.js +0 -4
  28. package/dist/api/modules/session.d.ts +1 -1
  29. package/dist/api/modules/session.js +1 -1
  30. package/dist/api/modules/startscreen.d.ts +2 -2
  31. package/dist/api/modules/startscreen.js +5 -3
  32. package/package.json +3 -2
  33. package/src/api/index.ts +16 -15
  34. package/src/api/main.ts +33 -24
  35. package/src/api/modules/action.ts +2 -4
  36. package/src/api/modules/base.ts +2 -2
  37. package/src/api/modules/builder.ts +2 -4
  38. package/src/api/modules/checker.ts +5 -6
  39. package/src/api/modules/component.ts +1822 -0
  40. package/src/api/modules/config.ts +78 -1
  41. package/src/api/modules/cooldown.ts +8 -13
  42. package/src/api/modules/database.ts +24 -32
  43. package/src/api/modules/helpmenu.ts +29 -22
  44. package/src/api/modules/language.ts +5 -7
  45. package/src/api/modules/progressbar.ts +2 -3
  46. package/src/api/modules/responder.ts +2 -4
  47. package/src/api/modules/session.ts +1 -1
  48. package/src/api/modules/startscreen.ts +6 -4
  49. package/src/api/modules/component.txt +0 -350
@@ -0,0 +1,1822 @@
1
+ ///////////////////////////////////////
2
+ //COMPONENTS MODULE
3
+ ///////////////////////////////////////
4
+ import { ODId, ODValidId, ODSystemError, ODManagerData, ODNoGeneric, ODManager, ODValidButtonColor, ODInterfaceWithPartialProperty } from "./base.js"
5
+ import * as discord from "discord.js"
6
+ import { ODWorkerManager, ODWorkerCallback, ODWorker } from "./worker.js"
7
+ import { ODDebugger } from "./console.js"
8
+
9
+ /**## ODComponentFactoryInstance `class`
10
+ * An Open Discord component factory instance.
11
+ *
12
+ * It will contain the final root component which is returned by an `ODComponentFactory`.
13
+ * This component can be used in another `ODComponentFactory` or rendered to a message/modal.
14
+ */
15
+ export class ODComponentFactoryInstance<Component extends ODComponent<object,any>> {
16
+ /**The root component of this factory. */
17
+ #rootComponent: Component|null = null
18
+
19
+ /**The root component of this factory. */
20
+ getComponent(){
21
+ return this.#rootComponent
22
+ }
23
+ /**Set the root component of this factory. */
24
+ setComponent(c:Component|null){
25
+ this.#rootComponent = c
26
+ }
27
+ }
28
+
29
+ /**## ODComponentFactory `class`
30
+ * An Open Discord component factory.
31
+ *
32
+ * It is a collection of functions/workers/hooks which will build a Discord message/modal component from scratch.
33
+ * Plugins can intercept and modify these workers to replace default behaviour or layout.
34
+ */
35
+ export class ODComponentFactory<Component extends ODComponent<object,any>,Origin extends string,Params,WorkerIds extends string = string> extends ODManagerData {
36
+ /**A collection of all workers for this component factory. */
37
+ workers: ODWorkerManager<ODComponentFactoryInstance<Component>,Origin,Params,WorkerIds>
38
+
39
+ constructor(id:ODValidId, callback?:ODWorkerCallback<ODComponentFactoryInstance<Component>,Origin,Params>, priority?:number, callbackId?:ODValidId){
40
+ super(id)
41
+ this.workers = new ODWorkerManager("ascending")
42
+ if (callback) this.workers.add(new ODWorker(callbackId ? callbackId : id,priority ?? 0,callback))
43
+ }
44
+ /**Run all workers and return the resulting component. */
45
+ async build(origin:Origin, params:Params): Promise<ODComponentInferBuildResult<Component>> {
46
+ const instance = new ODComponentFactoryInstance<Component>()
47
+ await this.workers.executeWorkers(instance,origin,params)
48
+ const rootComponent = instance.getComponent()
49
+ if (!rootComponent) throw new ODSystemError("ODComponentFactory.build() --> Failed to build component! (id: "+this.id.value+")")
50
+ return rootComponent.build()
51
+ }
52
+ }
53
+
54
+ /**## ODComponentManagerIdConstraint `type`
55
+ * The constraint/layout for id mappings/interfaces of the `ODComponentManager` class.
56
+ */
57
+ export type ODComponentManagerIdConstraint = Record<string,{origin:string,params:object,workers:string}>
58
+
59
+ /**## ODBaseComponentManager `class`
60
+ * A generic Open Discord component manager.
61
+ *
62
+ * It contains a collection of all Open Discord component factories. You can:
63
+ * - Add your own message/modal component factories
64
+ * - Modify existing message/modal component factories
65
+ *
66
+ * Messages created using this system are not compatible with `ODBuilder` messages!
67
+ */
68
+ export class ODBaseComponentManager<IdList extends ODComponentManagerIdConstraint = ODComponentManagerIdConstraint,Component extends ODComponent<object,any> = ODComponent<object,any>> extends ODManager<ODComponentFactory<Component,string,{},string>> {
69
+ get<FactoryId extends keyof ODNoGeneric<IdList>>(id:FactoryId): ODComponentFactory<Component,IdList[FactoryId]["origin"],IdList[FactoryId]["params"],IdList[FactoryId]["workers"]>
70
+ get(id:ODValidId): ODComponentFactory<Component,string,{},string>|null
71
+
72
+ get(id:ODValidId): ODComponentFactory<Component,string,{},string>|null {
73
+ return super.get(id)
74
+ }
75
+
76
+ remove<FactoryId extends keyof ODNoGeneric<IdList>>(id:FactoryId): ODComponentFactory<Component,IdList[FactoryId]["origin"],IdList[FactoryId]["params"],IdList[FactoryId]["workers"]>
77
+ remove(id:ODValidId): ODComponentFactory<Component,string,{},string>|null
78
+
79
+ remove(id:ODValidId): ODComponentFactory<Component,string,{},string>|null {
80
+ return super.remove(id)
81
+ }
82
+
83
+ exists(id:keyof ODNoGeneric<IdList>): boolean
84
+ exists(id:ODValidId): boolean
85
+
86
+ exists(id:ODValidId): boolean {
87
+ return super.exists(id)
88
+ }
89
+ }
90
+
91
+ /**## ODSharedComponentManager `class
92
+ * A special class with types for shared message/modal `ODComponent`'s.
93
+ * Create button, dropdown or any other layout component template to use them in messages & modals.
94
+ */
95
+ export class ODSharedComponentManager<IdList extends ODComponentManagerIdConstraint = ODComponentManagerIdConstraint> extends ODBaseComponentManager<IdList,ODComponent<object,any>> {
96
+ constructor(debug:ODDebugger){
97
+ super(debug,"shared component")
98
+ }
99
+ }
100
+
101
+ /**## ODMessageComponentManager `class
102
+ * A special class with types for `ODMessageComponent`'s.
103
+ * Create message templates to use as replies and make use of shared components.
104
+ */
105
+ export class ODMessageComponentManager<IdList extends ODComponentManagerIdConstraint = ODComponentManagerIdConstraint> extends ODBaseComponentManager<IdList,ODMessageComponent|ODSimpleMessageComponent> {
106
+ constructor(debug:ODDebugger){
107
+ super(debug,"message component")
108
+ }
109
+ }
110
+
111
+ /**## ODModalComponentManager `class
112
+ * A special class with types for `ODModalComponent`'s.
113
+ * Create modal templates to use as forms and make use of shared components.
114
+ */
115
+ export class ODModalComponentManager<IdList extends ODComponentManagerIdConstraint = ODComponentManagerIdConstraint> extends ODBaseComponentManager<IdList,ODModalComponent> {
116
+ constructor(debug:ODDebugger){
117
+ super(debug,"modal component")
118
+ }
119
+ }
120
+
121
+ /**## ODComponentManager `class`
122
+ * An Open Discord component manager.
123
+ *
124
+ * Create message & modal templates, re-use components and design all visual elements of the bot.
125
+ *
126
+ * Using the Open Discord component system has many advantages compared to vanilla `discord.js`:
127
+ * - Plugins can extend, edit & replace messages & components
128
+ * - Includes automatic error handling
129
+ * - Independent workers/hooks (with priority)
130
+ * - Fail-safe design using try-catch
131
+ * - Automatic switch between components v2 and legacy components depending on message requirements
132
+ * - Get to know the origin of the request (e.g. button, dropdown, modal, ...)
133
+ * - And so much more!
134
+ */
135
+ export class ODComponentManager<
136
+ SharedIdList extends ODComponentManagerIdConstraint = ODComponentManagerIdConstraint,
137
+ MessageIdList extends ODComponentManagerIdConstraint = ODComponentManagerIdConstraint,
138
+ ModalIdList extends ODComponentManagerIdConstraint = ODComponentManagerIdConstraint
139
+ > {
140
+ /**The manager for all shared components. */
141
+ shared: ODSharedComponentManager<SharedIdList>
142
+ /**The manager for all messages components. */
143
+ messages: ODMessageComponentManager<MessageIdList>
144
+ /**The manager for all modals components. */
145
+ modals: ODModalComponentManager<ModalIdList>
146
+
147
+ constructor(debug:ODDebugger){
148
+ this.shared = new ODSharedComponentManager(debug)
149
+ this.messages = new ODMessageComponentManager(debug)
150
+ this.modals = new ODModalComponentManager(debug)
151
+ }
152
+ }
153
+
154
+ ///////////////////////////////////////
155
+ // GENERIC COMPONENT DEFINITIONS //
156
+ ///////////////////////////////////////
157
+
158
+ /**## ODComponentInferBuildResult `type`
159
+ * Infer the build result of a certain component.
160
+ */
161
+ export type ODComponentInferBuildResult<Component> = Component extends ODComponent<object,infer BuildResult> ? BuildResult : never
162
+
163
+ /**## ODComponent `class`
164
+ * An Open Discord message/modal component.
165
+ *
166
+ * This class itself doesn't do anything, but is a blueprint for other
167
+ * `ODComponent` classes which represent the new Discord message/modal components.
168
+ */
169
+ export abstract class ODComponent<Data extends object,BuildResult> {
170
+ /**The id of this message/modal component. */
171
+ id: ODId
172
+ /**The data or configuration of this message/modal component. */
173
+ readonly data: Data
174
+
175
+ constructor(id:ODValidId,data:Data){
176
+ this.id = new ODId(id)
177
+ this.data = data
178
+ }
179
+
180
+ /**Build this component. Returns `null` when invalid. */
181
+ abstract build(): Promise<BuildResult|null>|BuildResult|null
182
+ }
183
+
184
+ /**## ODGroupComponent `class`
185
+ * An Open Discord message/modal component with children.
186
+ *
187
+ * This class itself doesn't do anything, but is a blueprint for other
188
+ * `ODGroupComponent` classes which represent the new Discord message/modal components.
189
+ */
190
+ export abstract class ODGroupComponent<Data extends object,ChildComponent extends ODComponent<object,any>,BuildResult> extends ODComponent<Data,BuildResult> {
191
+ /**The collection of child components. */
192
+ readonly children: ChildComponent[] = []
193
+
194
+ /**Add a new component to this group. There are multiple modes available:
195
+ * - `start`: insert at the start of the list.
196
+ * - `end`: insert at the end of the list.
197
+ * - `before`: insert before an existing component `referenceId` (`start` if `referenceId` is invalid)
198
+ * - `after`: insert after an existing component `referenceId` (`end` if `referenceId` is invalid)
199
+ * - `index`: insert at a certain index `referenceIndex` (`end` if `referenceIndex` is invalid)
200
+ */
201
+ addComponent(c:ChildComponent,mode:"start"|"end"): void
202
+ addComponent(c:ChildComponent,mode:"before"|"after",referenceId:ODValidId): void
203
+ addComponent(c:ChildComponent,mode:"index",referenceIndex:number): void
204
+ addComponent(c:ChildComponent,mode:"start"|"end"|"before"|"after"|"index" = "start",reference?:ODValidId|number){
205
+ if (mode == "start") this.children.unshift(c)
206
+ else if (mode == "end") this.children.push(c)
207
+ else if (mode == "before"){
208
+ if (!reference || !this.children.find((c) => c.id.value === new ODId(reference).value)) this.children.unshift(c)
209
+ else{
210
+ const referenceIndex = this.children.findIndex((c) => c.id.value === new ODId(reference).value)
211
+ this.children.splice(referenceIndex,0,c) //insert at position 'referenceIndex' (before)
212
+ }
213
+ }else if (mode == "after"){
214
+ if (!reference || !this.children.find((c) => c.id.value === new ODId(reference).value)) this.children.push(c)
215
+ else{
216
+ const referenceIndex = this.children.findIndex((c) => c.id.value === new ODId(reference).value)
217
+ this.children.splice(referenceIndex+1,0,c) //insert at position 'referenceIndex+1' (after)
218
+ }
219
+ }else if (mode == "index"){
220
+ if (!reference || typeof reference !== "number") this.children.push(c)
221
+ else{
222
+ this.children.splice(reference,0,c) //insert at position 'reference'
223
+ }
224
+ }
225
+ }
226
+ /**Get a component with a certain ID in this group. Returns `null` if non-existent. */
227
+ getComponent(id:ODValidId){
228
+ const component = this.children.find((c) => c.id.value === new ODId(id).value)
229
+ return component ?? null
230
+ }
231
+ /**Get the position of a component with a certain ID in this group. Returns `-1` if non-existent. */
232
+ getComponentPosition(id:ODValidId){
233
+ return this.children.findIndex((c) => c.id.value === new ODId(id).value)
234
+ }
235
+ /**Returns if a component with a certain ID exists in this group. */
236
+ existsComponent(id:ODValidId){
237
+ const component = this.children.find((c) => c.id.value === new ODId(id).value)
238
+ return component ? true : false
239
+ }
240
+ /**Remove a component with a certain ID from this group. Returns the removed component or `null if non-existent. */
241
+ removeComponent(id:ODValidId){
242
+ const index = this.children.findIndex((c) => c.id.value === new ODId(id).value)
243
+ if (index < 0) return null
244
+ else return this.children.splice(index,1)[0]
245
+ }
246
+ /**Moves an existing component to a new location in this group. There are multiple modes available:
247
+ * - `start`: move to the start of the list.
248
+ * - `end`: move to the end of the list.
249
+ * - `before`: move before an existing component `referenceId` (`start` if `referenceId` is invalid)
250
+ * - `after`: move after an existing component `referenceId` (`end` if `referenceId` is invalid)
251
+ * - `index`: move to a certain index `referenceIndex` (`end` if `referenceIndex` is invalid)
252
+ */
253
+ moveComponent(id:ODValidId,mode:"start"|"end"): void
254
+ moveComponent(id:ODValidId,mode:"before"|"after",referenceId:ODValidId): void
255
+ moveComponent(id:ODValidId,mode:"index",referenceIndex:number): void
256
+ moveComponent(id:ODValidId,mode:"start"|"end"|"before"|"after"|"index" = "start",reference?:ODValidId|number){
257
+ const component = this.removeComponent(id)
258
+ if (component){
259
+ if (mode == "start" || mode == "end") this.addComponent(component,mode)
260
+ if ((mode == "before" || mode == "after") && reference) this.addComponent(component,mode,reference)
261
+ if (mode == "index" && typeof reference == "number") this.addComponent(component,mode,reference)
262
+ return
263
+ }
264
+ }
265
+ }
266
+
267
+ /**## ODParentComponent `class`
268
+ * An Open Discord message/modal component with a single child.
269
+ *
270
+ * This class itself doesn't do anything, but is a blueprint for other
271
+ * `ODParentComponent` classes which represent the new Discord message/modal components.
272
+ */
273
+ export abstract class ODParentComponent<Data extends object,ChildComponent extends ODComponent<object,any>,BuildResult> extends ODComponent<Data,BuildResult> {
274
+ /**The child component of this parent. */
275
+ #child: ChildComponent|null = null
276
+
277
+ /**The child component of this parent. */
278
+ get child(){
279
+ return this.#child
280
+ }
281
+ /**Set the child component of this parent. */
282
+ setComponent(c:ChildComponent|null){
283
+ this.#child = c
284
+ }
285
+ }
286
+
287
+ //////////////////////////////////////
288
+ // GLOBAL COMPONENT DEFINITIONS //
289
+ //////////////////////////////////////
290
+
291
+ /**## ODMessageComponentData `type`
292
+ * The configurable settings/options for the `ODMessageComponent`.
293
+ */
294
+ export interface ODMessageComponentData {
295
+ /**Should the message be sent as ephemeral? */
296
+ ephemeral?:boolean,
297
+ /**Suppress/hide embeds. */
298
+ supressEmbeds?:boolean,
299
+ /**Do not send notifications to mentioned users or roles. */
300
+ supressNotifications?:boolean
301
+ /**Additional options that aren't covered by the Open Discord api!*/
302
+ additionalOptions?:Omit<discord.MessageCreateOptions,"poll"|"content"|"embeds"|"components"|"files"|"flags"|"stickers">,
303
+ /**Add additional files which can be used in components as `attachment://...` */
304
+ additionalAttachments?:discord.AttachmentBuilder[]
305
+ }
306
+
307
+ /**## ODValidMessageComponents `type`
308
+ * A collection of all valid top-level components that can be sent in a message.
309
+ */
310
+ export type ODValidMessageComponents = ODTextComponent|ODFileComponent|ODGalleryComponent
311
+
312
+ /**## ODMessageComponentBuildResult `type`
313
+ * The constructed message from an `ODMessageComponent`.
314
+ */
315
+ export interface ODMessageComponentBuildResult {
316
+ /**The message to send. */
317
+ msg:discord.MessageCreateOptions,
318
+ /**Is this message using Components V2? */
319
+ componentsV2:boolean,
320
+ /**Should the message be sent as ephemeral? */
321
+ ephemeral:boolean,
322
+ /**Suppress/hide embeds. */
323
+ supressEmbeds:boolean,
324
+ /**Do not send notifications to mentioned users or roles. */
325
+ supressNotifications:boolean
326
+ }
327
+
328
+ /**## ODMessageComponent `class`
329
+ * A message builder with **components v2** support.
330
+ * Add items to this message using `addComponent()`.
331
+ *
332
+ * Use `ODSimpleMessageComponent` for components v1, polls, embeds, etc
333
+ */
334
+ export class ODMessageComponent extends ODGroupComponent<ODMessageComponentData,ODValidMessageComponents,ODMessageComponentBuildResult> {
335
+ constructor(id:ODValidId,data?:Partial<ODMessageComponentData>){
336
+ const initData: ODMessageComponentData = {...data}
337
+ super(id,initData)
338
+ }
339
+
340
+ async build(){
341
+ if (this.children.length < 1) throw new ODSystemError("ODMessageComponent:build('"+this.id.value+"') => Requires at least one child component.")
342
+
343
+ const attachments: discord.AttachmentBuilder[] = [...(this.data.additionalAttachments ?? [])]
344
+ const components: discord.JSONEncodable<discord.APIMessageTopLevelComponent>[] = []
345
+
346
+ for (const component of this.children){
347
+ if (component instanceof ODFileComponent){
348
+ //ODFileComponent (special)
349
+ const res = await component.build()
350
+ if (res) components.push(res.file)
351
+ if (res?.attachment) attachments.push(res.attachment)
352
+
353
+ }else if (component instanceof ODGalleryComponent){
354
+ //ODGalleryComponent (special)
355
+ const res = await component.build()
356
+ if (res) components.push(res.gallery)
357
+ if (res?.attachments) attachments.push(...res.attachments)
358
+ }else{
359
+ //general ODComponent's
360
+ const res = await component.build()
361
+ if (res) components.push(res)
362
+ }
363
+ }
364
+
365
+ return {
366
+ msg:{
367
+ components,
368
+ files:attachments,
369
+ ...this.data.additionalOptions
370
+ },
371
+ componentsV2:true,
372
+ ephemeral:this.data.ephemeral ?? false,
373
+ supressEmbeds:this.data.supressEmbeds ?? false,
374
+ supressNotifications:this.data.supressNotifications ?? false
375
+ }
376
+
377
+ }
378
+
379
+ /**Enable/disable ephemeral mode. */
380
+ setEphemeral(value:boolean){
381
+ this.data.ephemeral = value
382
+ }
383
+ /**Enable supress (hide) embeds mode. */
384
+ setSupressEmbeds(value:boolean){
385
+ this.data.supressEmbeds = value
386
+ }
387
+ /**Enable supress (hide) notifications mode. */
388
+ setSupressNotifications(value:boolean){
389
+ this.data.supressNotifications = value
390
+ }
391
+ /**Add an additional attachment which can be used in components as `attachment://...` */
392
+ addAdditionalAttachments(...attachments:discord.AttachmentBuilder[]){
393
+ if (!this.data.additionalAttachments) this.data.additionalAttachments = []
394
+ this.data.additionalAttachments.push(...attachments)
395
+ }
396
+ }
397
+
398
+
399
+ /**## ODSimpleMessageComponentData `type`
400
+ * The configurable settings/options for the `ODSimpleMessageComponent`.
401
+ */
402
+ export interface ODSimpleMessageComponentData {
403
+ /**Should the message be sent as ephemeral? */
404
+ ephemeral?:boolean,
405
+ /**Suppress/hide embeds. */
406
+ supressEmbeds?:boolean,
407
+ /**Do not send notifications to mentioned users or roles. */
408
+ supressNotifications?:boolean
409
+ /**Additional options that aren't covered by the Open Discord api!*/
410
+ additionalOptions?:Omit<discord.MessageCreateOptions,"poll"|"content"|"embeds"|"components"|"files"|"flags">,
411
+ /**Add additional files which can be used in components as `attachment://...` */
412
+ additionalAttachments?:discord.AttachmentBuilder[]
413
+ }
414
+
415
+ /**## ODValidSimpleMessageComponents `type`
416
+ * A collection of all valid top-level components that can be sent in a simple message (components v1).
417
+ */
418
+ export type ODValidSimpleMessageComponents = ODContentComponent|ODEmbedComponent|ODFileComponent|ODPollComponent
419
+
420
+ /**## ODSimpleMessageComponent `class`
421
+ * A message builder with **components v1** support.
422
+ * Add items to this message using `addComponent()`.
423
+ *
424
+ * Use `ODMessageComponent` for components v2, components, containers, etc
425
+ */
426
+ export class ODSimpleMessageComponent extends ODGroupComponent<ODSimpleMessageComponentData,ODValidSimpleMessageComponents,ODMessageComponentBuildResult> {
427
+ constructor(id:ODValidId,data?:Partial<ODMessageComponentData>){
428
+ const initData: ODMessageComponentData = {...data}
429
+ super(id,initData)
430
+ }
431
+
432
+ async build(){
433
+ if (this.children.length < 1) throw new ODSystemError("ODMessageComponent:build('"+this.id.value+"') => Requires at least one child component.")
434
+
435
+ const attachments: discord.AttachmentBuilder[] = [...(this.data.additionalAttachments ?? [])]
436
+ const embeds: discord.EmbedBuilder[] = []
437
+ let content: string|undefined = undefined
438
+ let poll: discord.PollData|undefined = undefined
439
+
440
+ for (const component of this.children){
441
+ if (component instanceof ODFileComponent){
442
+ //ODFileComponent (special)
443
+ const res = await component.build()
444
+ if (res?.attachment) attachments.push(res.attachment)
445
+
446
+ }else if (component instanceof ODContentComponent){
447
+ //ODContentComponent (special)
448
+ const res = await component.build()
449
+ if (res) content = res.content
450
+
451
+ }else if (component instanceof ODEmbedComponent){
452
+ //ODEmbedComponent (special)
453
+ const res = await component.build()
454
+ if (res) embeds.push(res)
455
+
456
+ }else{
457
+ //ODPollComponent (special)
458
+ const res = await component.build()
459
+ if (res) poll = res
460
+ }
461
+ }
462
+
463
+ return {
464
+ msg:{
465
+ content,
466
+ embeds,
467
+ poll,
468
+ files:attachments,
469
+ ...this.data.additionalOptions
470
+ },
471
+ componentsV2:false,
472
+ ephemeral:this.data.ephemeral ?? false,
473
+ supressEmbeds:this.data.supressEmbeds ?? false,
474
+ supressNotifications:this.data.supressNotifications ?? false
475
+ }
476
+ }
477
+
478
+ /**Enable/disable ephemeral mode. */
479
+ setEphemeral(value:boolean){
480
+ this.data.ephemeral = value
481
+ }
482
+ /**Enable supress (hide) embeds mode. */
483
+ setSupressEmbeds(value:boolean){
484
+ this.data.supressEmbeds = value
485
+ }
486
+ /**Enable supress (hide) notifications mode. */
487
+ setSupressNotifications(value:boolean){
488
+ this.data.supressNotifications = value
489
+ }
490
+ /**Add an additional attachment which can be used in components as `attachment://...` */
491
+ addAdditionalAttachments(...attachments:discord.AttachmentBuilder[]){
492
+ if (!this.data.additionalAttachments) this.data.additionalAttachments = []
493
+ this.data.additionalAttachments.push(...attachments)
494
+ }
495
+ }
496
+
497
+
498
+ /**## ODModalComponentData `type`
499
+ * The configurable settings/options for the `ODModalComponent`.
500
+ */
501
+ export interface ODModalComponentData {
502
+ /**The title of the modal (max 45 characters). */
503
+ title:string,
504
+ /**The custom id of this modal. */
505
+ customId?:string
506
+ }
507
+
508
+ /**## ODValidModalComponents `type`
509
+ * A collection of all valid top-level components that can be sent in a message.
510
+ */
511
+ export type ODValidModalComponents = ODLabelComponent|ODTextComponent
512
+
513
+ /**## ODModalComponent `class`
514
+ * A modal builder with **components v2** support.
515
+ * Add questions, select menu's & labels to this modal using `addComponent()`.
516
+ */
517
+ export class ODModalComponent extends ODGroupComponent<ODModalComponentData,ODValidModalComponents,discord.ModalBuilder> {
518
+ constructor(id:ODValidId,data?:Partial<ODModalComponentData>){
519
+ const initData: ODModalComponentData = {title:"<empty>",...data}
520
+ super(id,initData)
521
+ }
522
+
523
+ async build(){
524
+ if (this.children.length < 1) throw new ODSystemError("ODModalComponent:build('"+this.id.value+"') => Requires at least one child component.")
525
+ if (this.children.length > 5) throw new ODSystemError("ODModalComponent:build('"+this.id.value+"') => A modal doesn't support more than 5 components.")
526
+
527
+ const components: (discord.APITextDisplayComponent|discord.APILabelComponent)[] = []
528
+
529
+ for (const component of this.children){
530
+ if (component instanceof ODLabelComponent){
531
+ //ODLabelComponent (special)
532
+ const res = await component.build()
533
+ if (res) components.push(res.toJSON())
534
+
535
+ }else{
536
+ //ODTextComponent (special)
537
+ const res = await component.build()
538
+ if (res) components.push(res.toJSON())
539
+ }
540
+ }
541
+
542
+ return new discord.ModalBuilder({
543
+ components,
544
+ title:this.data.title,
545
+ customId:this.data.customId
546
+ })
547
+ }
548
+
549
+ /**Set the title of the modal. */
550
+ setTitle(title:string){
551
+ this.data.title = title
552
+ }
553
+ /**Set the custom id of this modal. */
554
+ setCustomId(id:string|null){
555
+ this.data.customId = id ?? undefined
556
+ return this
557
+ }
558
+ }
559
+
560
+
561
+ //////////////////////////////////////
562
+ // LAYOUT COMPONENT DEFINITIONS //
563
+ //////////////////////////////////////
564
+
565
+ /**## ODActionRowComponentData `type`
566
+ * The configurable settings/options for the `ODActionRowComponent`.
567
+ */
568
+ export interface ODActionRowComponentData {
569
+ //no additional top-level data
570
+ }
571
+
572
+ /**## ODValidActionRowComponents `type`
573
+ * A collection of all valid action row components.
574
+ */
575
+ export type ODValidActionRowComponents = ODButtonComponent|ODDropdownComponent
576
+
577
+ /**## ODActionRowComponent `class`
578
+ * An actionrow component which is a container for buttons, a select menu or inputs in a message or modal.
579
+ */
580
+ export class ODActionRowComponent extends ODGroupComponent<ODActionRowComponentData,ODValidActionRowComponents,discord.ActionRowBuilder<discord.MessageActionRowComponentBuilder>> {
581
+ constructor(id:ODValidId,data?:Partial<ODActionRowComponentData>){
582
+ const initData: ODActionRowComponentData = {...data}
583
+ super(id,initData)
584
+ }
585
+
586
+ async build(){
587
+ if (this.children.length < 1) throw new ODSystemError("ODActionRowComponent:build('"+this.id.value+"') => Requires at least one child component.")
588
+ if (this.children.length > 5) throw new ODSystemError("ODActionRowComponent:build('"+this.id.value+"') => An action row doesn't support more than 5 components.")
589
+
590
+ const components: discord.JSONEncodable<discord.APIComponentInMessageActionRow>[] = []
591
+
592
+ for (const component of this.children){
593
+ //actionrow ODComponent's
594
+ const res = await component.build()
595
+ if (res) components.push(res)
596
+ }
597
+
598
+ return new discord.ActionRowBuilder<discord.MessageActionRowComponentBuilder>({components})
599
+ }
600
+ }
601
+
602
+ /**## ODContainerComponentData `type`
603
+ * The configurable settings/options for the `ODContainerComponent`.
604
+ */
605
+ export interface ODContainerComponentData {
606
+ /**The color of this container. */
607
+ color?:discord.ColorResolvable,
608
+ /**Mark the contents of this container as spoiler. */
609
+ spoiler:boolean
610
+ }
611
+
612
+ /**## ODValidContainerComponents `type`
613
+ * A collection of all valid container components.
614
+ */
615
+ export type ODValidContainerComponents = ODActionRowComponent|ODTextComponent|ODSectionComponent|ODGalleryComponent|ODSeparatorComponent|ODFileComponent
616
+
617
+ /**## ODContainerComponent `class`
618
+ * An embed-like container for text, titles, buttons, sections, separators and other components.
619
+ */
620
+ export class ODContainerComponent extends ODGroupComponent<ODContainerComponentData,ODValidContainerComponents,{container:discord.ContainerBuilder,attachments:discord.AttachmentBuilder[]}> {
621
+ constructor(id:ODValidId,data?:Partial<ODContainerComponentData>){
622
+ const initData: ODContainerComponentData = {spoiler:false,...data}
623
+ super(id,initData)
624
+ }
625
+
626
+ async build(){
627
+ if (this.children.length < 1) throw new ODSystemError("ODContainerComponent:build('"+this.id.value+"') => Requires at least one child component.")
628
+
629
+ const components: discord.APIComponentInContainer[] = []
630
+ const attachments: discord.AttachmentBuilder[] = []
631
+
632
+ for (const component of this.children){
633
+ if (component instanceof ODFileComponent){
634
+ //ODFileComponent (special)
635
+ const res = await component.build()
636
+ if (res) components.push(res.file.toJSON())
637
+ if (res?.attachment) attachments.push(res.attachment)
638
+
639
+ }else if (component instanceof ODGalleryComponent){
640
+ //ODGalleryComponent (special)
641
+ const res = await component.build()
642
+ if (res) components.push(res.gallery.toJSON())
643
+ if (res?.attachments) attachments.push(...res.attachments)
644
+ }else{
645
+ //general ODComponent's
646
+ const res = await component.build()
647
+ if (res) components.push(res.toJSON())
648
+ }
649
+ }
650
+
651
+ return {
652
+ container:new discord.ContainerBuilder({
653
+ components,
654
+ accent_color:this.data.color ? discord.resolveColor(this.data.color) : undefined,
655
+ spoiler:this.data.spoiler
656
+ }),
657
+ attachments
658
+ }
659
+ }
660
+
661
+ /**Set the accent color of this embed-like container. */
662
+ setColor(color:discord.ColorResolvable|null){
663
+ this.data.color = color ?? undefined
664
+ }
665
+ /**Mark the contents of this container as spoiler. */
666
+ setSpoiler(spoiler:boolean){
667
+ this.data.spoiler = spoiler
668
+ }
669
+ }
670
+
671
+
672
+ /**## ODSectionComponentData `type`
673
+ * The configurable settings/options for the `ODSectionComponent`.
674
+ */
675
+ export interface ODSectionComponentData {
676
+ /**The accessory component shown on the right side of the section. */
677
+ accessory?:ODButtonComponent|ODThumbnailComponent
678
+ }
679
+
680
+ /**## ODSectionComponent `class`
681
+ * A layout component that allows you to contextually associate content with an accessory component.
682
+ * - Components: Left
683
+ * - Accessory: Right
684
+ */
685
+ export class ODSectionComponent extends ODGroupComponent<ODSectionComponentData,ODTextComponent,discord.SectionBuilder> {
686
+ constructor(id:ODValidId,data?:Partial<ODSectionComponentData>){
687
+ const initData: ODSectionComponentData = {...data}
688
+ super(id,initData)
689
+ }
690
+
691
+ async build(){
692
+ if (this.children.length < 1) throw new ODSystemError("ODSectionComponent:build('"+this.id.value+"') => Requires at least one child component.")
693
+ if (this.children.length > 3) throw new ODSystemError("ODSectionComponent:build('"+this.id.value+"') => A maximum of 3 child components are allowed in a section.")
694
+
695
+ const components: discord.APITextDisplayComponent[] = []
696
+
697
+ for (const component of this.children){
698
+ //section ODComponent's
699
+ const res = await component.build()
700
+ if (res) components.push(res.toJSON())
701
+
702
+ }
703
+
704
+ let accessory: discord.APISectionAccessoryComponent|undefined = undefined
705
+ if (this.data.accessory){
706
+ const accessoryRes = await this.data.accessory.build()
707
+ if (accessoryRes) accessory = accessoryRes.toJSON()
708
+ }
709
+
710
+ return new discord.SectionBuilder({components,accessory})
711
+ }
712
+
713
+ /**Set the accessory component shown on the right side of the section. */
714
+ setAccessory(accessory:ODButtonComponent|ODThumbnailComponent|null){
715
+ this.data.accessory = accessory ?? undefined
716
+ }
717
+ }
718
+
719
+ /**## ODLabelComponentData `type`
720
+ * The configurable settings/options for the `ODLabelComponent`.
721
+ */
722
+ export interface ODLabelComponentData {
723
+ /**The title for the child component in the modal. */
724
+ title:string,
725
+ /**An option description for the child component in the modal. */
726
+ description?:string
727
+ }
728
+
729
+ /**## ODValidLabelComponents `type`
730
+ * A collection of all valid label components.
731
+ */
732
+ export type ODValidLabelComponents = ODShortInputComponent|ODParagraphInputComponent|ODDropdownComponent|ODRadioGroupComponent|ODCheckboxGroupComponent|ODCheckboxComponent|ODFileUploadComponent
733
+
734
+ /**## ODLabelComponent `class`
735
+ * A visual separator between components. The visibility and padding of this separator can be changed.
736
+ */
737
+ export class ODLabelComponent extends ODParentComponent<ODLabelComponentData,ODValidLabelComponents,discord.LabelBuilder> {
738
+ constructor(id:ODValidId,data:Partial<ODLabelComponentData>){
739
+ const initData: ODLabelComponentData = {title:"<empty>",...data}
740
+ super(id,initData)
741
+ }
742
+
743
+ async build(){
744
+ let component: discord.APIComponentInLabel|undefined = undefined
745
+ if (this.child){
746
+ const accessoryRes = await this.child.build()
747
+ if (accessoryRes) component = accessoryRes.toJSON()
748
+ }
749
+
750
+ return new discord.LabelBuilder({
751
+ label:this.data.title,
752
+ description:this.data.description,
753
+ component
754
+ })
755
+ }
756
+
757
+ /**Set the title of the child component in the modal. */
758
+ setTitle(title:string){
759
+ this.data.title = title
760
+ }
761
+ /**Set the description of the child component in the modal. */
762
+ setDescription(description:string|null){
763
+ this.data.description = description ?? undefined
764
+ }
765
+ }
766
+
767
+
768
+ /**## ODSeparatorComponentData `type`
769
+ * The configurable settings/options for the `ODSeparatorComponent`.
770
+ */
771
+ export interface ODSeparatorComponentData {
772
+ /**Whether a visual divider should be displayed in the component. (Default: `true`) */
773
+ divider:boolean,
774
+ /**Size of separator padding (Default: `small`) */
775
+ spacing:"small"|"large"
776
+ }
777
+
778
+ /**## ODSeparatorComponent `class`
779
+ * A visual separator between components. The visibility and padding of this separator can be changed.
780
+ */
781
+ export class ODSeparatorComponent extends ODComponent<ODSeparatorComponentData,discord.SeparatorBuilder> {
782
+ constructor(id:ODValidId,data:Partial<ODSeparatorComponentData>){
783
+ const initData: ODSeparatorComponentData = {divider:true,spacing:"small",...data}
784
+ super(id,initData)
785
+ }
786
+
787
+ async build(){
788
+ return new discord.SeparatorBuilder({
789
+ divider:this.data.divider,
790
+ spacing:(this.data.spacing == "small") ? discord.SeparatorSpacingSize.Small : discord.SeparatorSpacingSize.Large
791
+ })
792
+ }
793
+
794
+ /**Set whether a visual divider should be displayed in the component. (Default: `true`). */
795
+ setDivider(divider:boolean){
796
+ this.data.divider = divider
797
+ }
798
+ /**Set the size of separator padding (Default `small`). */
799
+ setSpacing(spacing:"small"|"large"){
800
+ this.data.spacing = spacing
801
+ }
802
+ }
803
+
804
+ ///////////////////////////////////////
805
+ // CONTENT COMPONENT DEFINITIONS //
806
+ ///////////////////////////////////////
807
+
808
+ /**## ODTextComponentData `type`
809
+ * The configurable settings/options for the `ODTextComponent`.
810
+ */
811
+ export interface ODTextComponentData {
812
+ /**The text to display. */
813
+ content:string
814
+ }
815
+
816
+ /**## ODTextComponent `class`
817
+ * A text component which renders markdown text in a message.
818
+ */
819
+ export class ODTextComponent extends ODComponent<ODTextComponentData,discord.TextDisplayBuilder> {
820
+ constructor(id:ODValidId,data:Partial<ODTextComponentData>){
821
+ const initData: ODTextComponentData = {content:"",...data}
822
+ super(id,initData)
823
+ }
824
+
825
+ async build(){
826
+ if (this.data.content.length < 1) throw new ODSystemError("ODTextComponent:build('"+this.id.value+"') => Unable to display text component without contents.")
827
+ return new discord.TextDisplayBuilder({
828
+ content:this.data.content
829
+ })
830
+ }
831
+
832
+ /**Set the text to display. */
833
+ setContent(value:string){
834
+ this.data.content = value
835
+ }
836
+ }
837
+
838
+ /**## ODFileComponentData `type`
839
+ * The configurable settings/options for the `ODFileComponent`.
840
+ */
841
+ export interface ODFileComponentData {
842
+ /**The name of this file. */
843
+ name:string,
844
+ /**The description of this file. */
845
+ description?:string,
846
+ /**Should this file be marked as a spoiler? */
847
+ spoiler?:boolean,
848
+ /**The binary/text contents of the file. Ignored when `externalUrl` is used. */
849
+ content?:discord.BufferResolvable,
850
+ /**A URL to an external file or image. When specified, the `content` (setContent) will be ignored. */
851
+ externalUrl?:string
852
+ }
853
+
854
+ /**## ODFileComponent `class`
855
+ * A file component which adds a file in a message.
856
+ */
857
+ export class ODFileComponent extends ODComponent<ODFileComponentData,{file:discord.FileBuilder,attachment:discord.AttachmentBuilder|null}> {
858
+ constructor(id:ODValidId,data:Partial<ODFileComponentData>){
859
+ const initData: ODFileComponentData = {name:"file.txt",...data}
860
+ super(id,initData)
861
+ }
862
+
863
+ async build(){
864
+ if (!this.data.content && !this.data.externalUrl) throw new ODSystemError("ODFileComponent:build('"+this.id.value+"') => Unable to display file component without binary or url.")
865
+
866
+ const attachment = (this.data.content) ? new discord.AttachmentBuilder(this.data.content,{
867
+ name:this.data.name,
868
+ description:this.data.description
869
+ }) : null
870
+
871
+ return {
872
+ attachment,
873
+ file:new discord.FileBuilder({
874
+ file:{
875
+ url:(this.data.externalUrl ?? "attachment://"+this.data.name)
876
+ },
877
+ spoiler:this.data.spoiler
878
+ })
879
+ }
880
+ }
881
+
882
+ /**Set the filename + extension. */
883
+ setName(value:string){
884
+ this.data.name = value
885
+ }
886
+ /**Set the file description. */
887
+ setDescription(value:string|null){
888
+ this.data.description = value ?? undefined
889
+ }
890
+ /**Set the text/binary contents of the file. */
891
+ setContent(value:discord.BufferResolvable|null){
892
+ this.data.content = value ?? undefined
893
+ }
894
+ /**Set a URL to an external file or image. When used, `setContent()` will be ignored! */
895
+ setExternalUrl(value:string|null){
896
+ this.data.externalUrl = value ?? undefined
897
+ }
898
+ /**Mark the file as spoiler. */
899
+ setSpoiler(value:boolean){
900
+ this.data.spoiler = value
901
+ }
902
+ }
903
+
904
+ /**## ODGalleryComponentData `type`
905
+ * The configurable settings/options for the `ODGalleryComponent`.
906
+ */
907
+ export interface ODGalleryComponentData {
908
+ //no additional top-level data
909
+ }
910
+
911
+ /**## ODGalleryComponent `class`
912
+ * A gallery component which renders a grid of media items (images/videos) in a message.
913
+ * Add items to this gallery using `addComponent()`.
914
+ */
915
+ export class ODGalleryComponent extends ODGroupComponent<ODGalleryComponentData,ODFileComponent,{gallery:discord.MediaGalleryBuilder,attachments:discord.AttachmentBuilder[]}> {
916
+ constructor(id:ODValidId,data?:Partial<ODGalleryComponentData>){
917
+ const initData: ODGalleryComponentData = {...data}
918
+ super(id,initData)
919
+ }
920
+
921
+ async build(){
922
+ if (this.children.length < 1) throw new ODSystemError("ODGalleryComponent:build('"+this.id.value+"') => Requires at least one child component.")
923
+
924
+ const gallery = new discord.MediaGalleryBuilder()
925
+ const attachments: discord.AttachmentBuilder[] = []
926
+
927
+ for (const file of this.children){
928
+ if (!file.data.content && !file.data.externalUrl) continue
929
+ if (file.data.content) attachments.push(new discord.AttachmentBuilder(file.data.content,{
930
+ name:file.data.name,
931
+ description:file.data.description
932
+ }))
933
+ gallery.addItems(new discord.MediaGalleryItemBuilder({
934
+ description:file.data.description,
935
+ spoiler:file.data.spoiler,
936
+ media:{
937
+ url:(file.data.externalUrl ?? "attachment://"+file.data.name)
938
+ }
939
+ }))
940
+ }
941
+
942
+ return {gallery,attachments}
943
+ }
944
+ }
945
+
946
+ /**## ODThumbnailComponentData `type`
947
+ * The configurable settings/options for the `ODThumbnailComponent`.
948
+ */
949
+ export interface ODThumbnailComponentData {
950
+ /**The URL of the thumbnail image. Can be an external URL or `attachment://filename.ext`. */
951
+ url:string,
952
+ /**The alt text/description of the thumbnail image. */
953
+ description?:string,
954
+ /**Should this thumbnail be marked as a spoiler? */
955
+ spoiler?:boolean
956
+ }
957
+
958
+ /**## ODThumbnailComponent `class`
959
+ * A thumbnail component which renders a small image as an accessory inside an `ODSectionComponent`.
960
+ * This component must be used via `ODSectionComponent.setComponent()`
961
+ */
962
+ export class ODThumbnailComponent extends ODComponent<ODThumbnailComponentData,discord.ThumbnailBuilder> {
963
+ constructor(id:ODValidId,data:Partial<ODThumbnailComponentData>){
964
+ const initData: ODThumbnailComponentData = {url:"",...data}
965
+ super(id,initData)
966
+ }
967
+
968
+ async build(){
969
+ if (this.data.url.length < 1) throw new ODSystemError("ODThumbnailComponent:build('"+this.id.value+"') => Thumbnail component requires an image URL.")
970
+
971
+ return new discord.ThumbnailBuilder({
972
+ media:{ url:this.data.url },
973
+ description:this.data.description,
974
+ spoiler:this.data.spoiler
975
+ })
976
+ }
977
+
978
+ /**Set the URL of the thumbnail image. */
979
+ setUrl(value:string){
980
+ this.data.url = value
981
+ }
982
+ /**Set the alt text/description of the thumbnail image. */
983
+ setDescription(value:string|null){
984
+ this.data.description = value ?? undefined
985
+ }
986
+ /**Mark the thumbnail as a spoiler. */
987
+ setSpoiler(value:boolean){
988
+ this.data.spoiler = value
989
+ }
990
+ }
991
+
992
+ /**## ODContentComponentData `type`
993
+ * The configurable settings/options for the `ODContentComponent`.
994
+ */
995
+ export interface ODContentComponentData {
996
+ /**The text to display. */
997
+ content:string
998
+ }
999
+
1000
+ /**## ODContentComponent `class`
1001
+ * A text component which renders markdown text in a message without `Components V2` enabled. (Old behaviour)
1002
+ */
1003
+ export class ODContentComponent extends ODComponent<ODContentComponentData,{content:string}> {
1004
+ constructor(id:ODValidId,data:Partial<ODContentComponentData>){
1005
+ const initData: ODContentComponentData = {content:"",...data}
1006
+ super(id,initData)
1007
+ }
1008
+
1009
+ async build(){
1010
+ if (this.data.content.length < 1) throw new ODSystemError("ODContentComponent:build('"+this.id.value+"') => Unable to display content component without contents.")
1011
+ return {
1012
+ content:this.data.content
1013
+ }
1014
+ }
1015
+
1016
+ /**Set the text to display. */
1017
+ setContent(value:string){
1018
+ this.data.content = value
1019
+ }
1020
+ }
1021
+
1022
+
1023
+ /**## ODEmbedComponentData `type`
1024
+ * The configurable settings/options for the `ODEmbedComponent`.
1025
+ */
1026
+ export interface ODEmbedComponentData {
1027
+ /**The title of the embed. */
1028
+ title?:string,
1029
+ /**The color of the embed. */
1030
+ color?:discord.ColorResolvable,
1031
+ /**The url of the embed. */
1032
+ url?:string,
1033
+ /**The description of the embed. */
1034
+ description?:string,
1035
+ /**The author text of the embed. */
1036
+ authorText?:string,
1037
+ /**The author image of the embed. */
1038
+ authorImage?:string,
1039
+ /**The author url of the embed. */
1040
+ authorUrl?:string,
1041
+ /**The footer text of the embed. */
1042
+ footerText?:string,
1043
+ /**The footer image of the embed. */
1044
+ footerImage?:string,
1045
+ /**The image of the embed. */
1046
+ image?:string,
1047
+ /**The thumbnail of the embed. */
1048
+ thumbnail?:string,
1049
+ /**The fields of the embed. */
1050
+ fields?:ODInterfaceWithPartialProperty<discord.EmbedField,"inline">[],
1051
+ /**The timestamp of the embed. */
1052
+ timestamp?:number|Date
1053
+ }
1054
+
1055
+ /**## ODEmbedComponent `class`
1056
+ * An embed component which renders an embed in a message without `Components V2` enabled. (Old behaviour)
1057
+ */
1058
+ export class ODEmbedComponent extends ODComponent<ODEmbedComponentData,discord.EmbedBuilder> {
1059
+ constructor(id:ODValidId,data:Partial<ODEmbedComponentData>){
1060
+ const initData: ODEmbedComponentData = {...data}
1061
+ super(id,initData)
1062
+ }
1063
+
1064
+ async build(){
1065
+ if (this.data.title && this.data.title.length > 256) throw new ODSystemError("ODEmbedComponent:build('"+this.id.value+"') => An embed title can't exceed 256 characters.")
1066
+ if (this.data.description && this.data.description.length > 4096) throw new ODSystemError("ODEmbedComponent:build('"+this.id.value+"') => An embed description can't exceed 4096 characters.")
1067
+ if (this.data.fields && this.data.fields.length > 25) throw new ODSystemError("ODEmbedComponent:build('"+this.id.value+"') => An embed can't contain more than 25 fields.")
1068
+ if (this.data.footerText && this.data.footerText.length > 2048) throw new ODSystemError("ODEmbedComponent:build('"+this.id.value+"') => An embed footer can't exceed 2048 characters.")
1069
+ if (this.data.authorText && this.data.authorText.length > 256) throw new ODSystemError("ODEmbedComponent:build('"+this.id.value+"') => An embed author can't exceed 256 characters.")
1070
+
1071
+ return new discord.EmbedBuilder({
1072
+ title:this.data.title,
1073
+ color:(this.data.color) ? discord.resolveColor(this.data.color) : undefined,
1074
+ url:this.data.url,
1075
+ description:this.data.description,
1076
+ author:(this.data.authorText) ? {
1077
+ name:this.data.authorText,
1078
+ icon_url:this.data.authorImage,
1079
+ url:this.data.authorUrl
1080
+ } : undefined,
1081
+ footer:(this.data.footerText) ? {
1082
+ text:this.data.footerText,
1083
+ icon_url:this.data.footerImage
1084
+ } : undefined,
1085
+ image:(this.data.image) ? {
1086
+ url:this.data.image
1087
+ } : undefined,
1088
+ thumbnail:(this.data.thumbnail) ? {
1089
+ url:this.data.thumbnail
1090
+ } : undefined,
1091
+ fields:this.data.fields,
1092
+ timestamp:(this.data.timestamp) ? new Date(this.data.timestamp).toISOString() : undefined
1093
+ })
1094
+ }
1095
+
1096
+ /**Set the title of this embed */
1097
+ setTitle(title:string|null){
1098
+ this.data.title = title ?? undefined
1099
+ return this
1100
+ }
1101
+ /**Set the color of this embed */
1102
+ setColor(color:discord.ColorResolvable|null){
1103
+ this.data.color = color ?? undefined
1104
+ return this
1105
+ }
1106
+ /**Set the url of this embed */
1107
+ setUrl(url:string|null){
1108
+ this.data.url = url ?? undefined
1109
+ return this
1110
+ }
1111
+ /**Set the description of this embed */
1112
+ setDescription(description:string|null){
1113
+ this.data.description = description ?? undefined
1114
+ return this
1115
+ }
1116
+ /**Set the author of this embed */
1117
+ setAuthor(text:string|null, image?:string|null, url?:string|null){
1118
+ this.data.authorText = text ?? undefined
1119
+ this.data.authorImage = image ?? undefined
1120
+ this.data.authorUrl = url ?? undefined
1121
+ return this
1122
+ }
1123
+ /**Set the footer of this embed */
1124
+ setFooter(text:string|null, image?:string|null){
1125
+ this.data.footerText = text ?? undefined
1126
+ this.data.footerImage = image ?? undefined
1127
+ return this
1128
+ }
1129
+ /**Set the image of this embed */
1130
+ setImage(image:string|null){
1131
+ this.data.image = image ?? undefined
1132
+ return this
1133
+ }
1134
+ /**Set the thumbnail of this embed */
1135
+ setThumbnail(thumbnail:string|null){
1136
+ this.data.thumbnail = thumbnail ?? undefined
1137
+ return this
1138
+ }
1139
+ /**Set the fields of this embed */
1140
+ setFields(fields:ODInterfaceWithPartialProperty<discord.EmbedField,"inline">[]){
1141
+ //Check field properties
1142
+ for (const [index,field] of fields.entries()){
1143
+ if (field.value.length >= 1024) throw new ODSystemError("ODEmbedComponent:build('"+this.id.value+"') => field "+index+" reached 1024 character limit!")
1144
+ if (field.name.length >= 256) throw new ODSystemError("ODEmbedComponent:build('"+this.id.value+"') => field "+index+" reached 256 name character limit!")
1145
+ }
1146
+ this.data.fields = fields
1147
+ return this
1148
+ }
1149
+ /**Add fields to this embed */
1150
+ addFields(...fields:ODInterfaceWithPartialProperty<discord.EmbedField,"inline">[]){
1151
+ //Check field properties
1152
+ for (const [index,field] of fields.entries()){
1153
+ if (field.value.length >= 1024) throw new ODSystemError("ODEmbedComponent:build('"+this.id.value+"') => field "+index+" reached 1024 character limit!")
1154
+ if (field.name.length >= 256) throw new ODSystemError("ODEmbedComponent:build('"+this.id.value+"') => field "+index+" reached 256 name character limit!")
1155
+ }
1156
+
1157
+ if (!this.data.fields) this.data.fields = []
1158
+ this.data.fields.push(...fields)
1159
+ return this
1160
+ }
1161
+ /**Clear all fields from this embed */
1162
+ clearFields(){
1163
+ this.data.fields = []
1164
+ return this
1165
+ }
1166
+ /**Set the timestamp of this embed. When set to `true`, the current timestamp is used. */
1167
+ setTimestamp(timestamp:number|Date|true|null){
1168
+ if (timestamp === true) this.data.timestamp = new Date()
1169
+ else this.data.timestamp = timestamp ?? undefined
1170
+ return this
1171
+ }
1172
+ }
1173
+
1174
+ /**## ODPollComponentData `type`
1175
+ * The configurable settings/options for the `ODPollComponent`.
1176
+ */
1177
+ export interface ODPollComponentData {
1178
+ /**The poll question. */
1179
+ question:string,
1180
+ /**The duration of the poll in hours. */
1181
+ durationHours:number,
1182
+ /**Available poll answers. */
1183
+ answers:discord.PollAnswerData[],
1184
+ /**Allow selecting multiple answers. */
1185
+ allowMultiSelect:boolean
1186
+ }
1187
+
1188
+ /**## ODPollComponent `class`
1189
+ * A poll component which adds a poll to a message without `Components V2` enabled. (Old behaviour)
1190
+ */
1191
+ export class ODPollComponent extends ODComponent<ODPollComponentData,discord.PollData> {
1192
+ constructor(id:ODValidId,data:Partial<ODPollComponentData>){
1193
+ const initData: ODPollComponentData = {question:"<empty>",durationHours:1,allowMultiSelect:false,answers:[],...data}
1194
+ super(id,initData)
1195
+ }
1196
+
1197
+ async build(){
1198
+ if (this.data.question.length < 1) throw new ODSystemError("ODPollComponent:build('"+this.id.value+"') => Please provide a valid poll question.")
1199
+ if (this.data.answers.length < 1) throw new ODSystemError("ODPollComponent:build('"+this.id.value+"') => Please provide at least one answer to the poll.")
1200
+ return {
1201
+ layoutType:discord.PollLayoutType.Default,
1202
+ allowMultiselect:this.data.allowMultiSelect,
1203
+ duration:this.data.durationHours,
1204
+ question:{text:this.data.question},
1205
+ answers:this.data.answers
1206
+ }
1207
+ }
1208
+
1209
+ /**Set the poll question. */
1210
+ setQuestion(question:string){
1211
+ this.data.question = question
1212
+ }
1213
+ /**Set the poll duration in hours. */
1214
+ setDurationHours(duration:number){
1215
+ this.data.durationHours = duration
1216
+ }
1217
+ /**Allow selecting multiple answers. */
1218
+ setMultiSelect(multi:boolean){
1219
+ this.data.allowMultiSelect = multi
1220
+ }
1221
+ /**Set the poll answers. */
1222
+ setAnswers(answers:discord.PollAnswerData[]){
1223
+ this.data.answers = answers
1224
+ }
1225
+ /**Add additional poll answers. */
1226
+ addAnswers(...answers:discord.PollAnswerData[]){
1227
+ this.data.answers.push(...answers)
1228
+ }
1229
+ }
1230
+
1231
+ ///////////////////////////////////////////
1232
+ // INTERACTIVE COMPONENT DEFINITIONS //
1233
+ ///////////////////////////////////////////
1234
+
1235
+
1236
+ /**## ODButtonComponentData `type`
1237
+ * The configurable settings/options for the `ODButtonComponent`.
1238
+ */
1239
+ export interface ODButtonComponentData {
1240
+ /**The custom id of this button. Ignored when `url` is specified. */
1241
+ customId?:string,
1242
+ /**The url of this button. Disables interactions & customId */
1243
+ url?:string,
1244
+ /**The button color. Ignored when `url` is specified. */
1245
+ color:ODValidButtonColor,
1246
+ /**The button label */
1247
+ label?:string,
1248
+ /**The button emoji */
1249
+ emoji?:string,
1250
+ /**Is the button disabled? */
1251
+ disabled:boolean
1252
+ }
1253
+
1254
+ /**## ODButtonComponent `class`
1255
+ * A button component which renders an interactive button inside the message.
1256
+ * A reply can be sent using Open Discord responders.
1257
+ */
1258
+ export class ODButtonComponent extends ODComponent<ODButtonComponentData,discord.ButtonBuilder> {
1259
+ constructor(id:ODValidId,data:Partial<ODButtonComponentData>){
1260
+ const initData: ODButtonComponentData = {color:"gray",disabled:false,...data}
1261
+ super(id,initData)
1262
+ }
1263
+
1264
+ async build(){
1265
+ if (!this.data.emoji && !this.data.label) throw new ODSystemError("ODButtonComponent:build('"+this.id.value+"') => A button must include at least one label or emoji.")
1266
+ if (this.data.customId && this.data.customId.length > 100) throw new ODSystemError("ODButtonComponent:build('"+this.id.value+"') => A custom ID '"+this.data.customId+"' must be shorter than 100 characters.")
1267
+
1268
+ return new discord.ButtonBuilder({
1269
+ customId:(!this.data.url) ? this.data.customId : undefined,
1270
+ label:this.data.label,
1271
+ emoji:this.data.emoji ? discord.resolvePartialEmoji(this.data.emoji) : undefined,
1272
+ disabled:this.data.disabled,
1273
+ url:(this.data.url) ? this.data.url : undefined,
1274
+ style:this.getButtonStyle(),
1275
+ })
1276
+ }
1277
+
1278
+ /**Get the `discord.ButtonStyle` for the specified `ODValidButtonColor` */
1279
+ protected getButtonStyle(): discord.ButtonStyle {
1280
+ if (this.data.url) return discord.ButtonStyle.Link
1281
+ else if (this.data.color == "blue") return discord.ButtonStyle.Primary
1282
+ else if (this.data.color == "green") return discord.ButtonStyle.Success
1283
+ else if (this.data.color == "red") return discord.ButtonStyle.Danger
1284
+ else return discord.ButtonStyle.Secondary
1285
+ }
1286
+
1287
+ /**Set the custom id of this button. Ignored when `setUrl()` is used. */
1288
+ setCustomId(id:string|null){
1289
+ this.data.customId = id ?? undefined
1290
+ return this
1291
+ }
1292
+ /**Set the url of this button. */
1293
+ setUrl(url:string|null){
1294
+ this.data.url = url ?? undefined
1295
+ return this
1296
+ }
1297
+ /**Set the color of this button. Ignored when `setUrl()` is used. */
1298
+ setColor(color:ODValidButtonColor){
1299
+ this.data.color = color
1300
+ return this
1301
+ }
1302
+ /**Set the label of this button. */
1303
+ setLabel(label:string|null){
1304
+ this.data.label = label ?? undefined
1305
+ return this
1306
+ }
1307
+ /**Set the emoji of this button. */
1308
+ setEmoji(emoji:string|null){
1309
+ this.data.emoji = emoji ?? undefined
1310
+ return this
1311
+ }
1312
+ /**Disable this button. */
1313
+ setDisabled(disabled:boolean){
1314
+ this.data.disabled = disabled
1315
+ return this
1316
+ }
1317
+ }
1318
+
1319
+
1320
+ /**## ODShortInputComponentData `type`
1321
+ * The configurable settings/options for the `ODShortInputComponent`.
1322
+ */
1323
+ export interface ODShortInputComponentData {
1324
+ /**The custom id of this modal text input. */
1325
+ customId?:string
1326
+ /**The min length of this modal text input. */
1327
+ minLength?:number,
1328
+ /**The max length of this modal text input. */
1329
+ maxLength?:number,
1330
+ /**Is this modal text input required? */
1331
+ required:boolean,
1332
+ /**The placeholder of this modal text input. */
1333
+ placeholder?:string,
1334
+ /**The initial value of this modal text input. */
1335
+ initialValue?:string
1336
+ }
1337
+
1338
+ /**## ODShortInputComponent `class`
1339
+ * A short text input component for modals.
1340
+ * It must be placed inside an `ODLabelComponent`.
1341
+ */
1342
+ export class ODShortInputComponent extends ODComponent<ODShortInputComponentData,discord.TextInputBuilder> {
1343
+ constructor(id:ODValidId,data:Partial<ODShortInputComponentData>){
1344
+ const initData: ODShortInputComponentData = {required:false,...data}
1345
+ super(id,initData)
1346
+ }
1347
+
1348
+ async build(){
1349
+ return new discord.TextInputBuilder({
1350
+ style:discord.TextInputStyle.Short,
1351
+ customId:this.data.customId,
1352
+ minLength:this.data.minLength,
1353
+ maxLength:this.data.maxLength,
1354
+ required:this.data.required,
1355
+ placeholder:this.data.placeholder,
1356
+ value:this.data.initialValue
1357
+ })
1358
+ }
1359
+
1360
+ /**Set the custom id of this modal text input. */
1361
+ setCustomId(id:string|null){
1362
+ this.data.customId = id ?? undefined
1363
+ return this
1364
+ }
1365
+ /**Set the minimum amount of characters of this modal text input. */
1366
+ setMinLength(length:number|null){
1367
+ this.data.minLength = length ?? undefined
1368
+ return this
1369
+ }
1370
+ /**Set the maximum amount of characters of this modal text input. */
1371
+ setMaxLength(length:number|null){
1372
+ this.data.maxLength = length ?? undefined
1373
+ return this
1374
+ }
1375
+ /**Set the placeholder of this modal text input. */
1376
+ setPlaceholder(placeholder:string|null){
1377
+ this.data.placeholder = placeholder ?? undefined
1378
+ return this
1379
+ }
1380
+ /**Set the initial value of this modal text input. */
1381
+ setInitialValue(initialValue:string|null){
1382
+ this.data.initialValue = initialValue ?? undefined
1383
+ return this
1384
+ }
1385
+ /**Mark this modal text input as required. */
1386
+ setRequired(required:boolean){
1387
+ this.data.required = required
1388
+ return this
1389
+ }
1390
+ }
1391
+
1392
+ /**## ODParagraphInputComponentData `type`
1393
+ * The configurable settings/options for the `ODParagraphInputComponent`.
1394
+ */
1395
+ export interface ODParagraphInputComponentData {
1396
+ /**The custom id of this modal text input. */
1397
+ customId?:string
1398
+ /**The min length of this modal text input. */
1399
+ minLength?:number,
1400
+ /**The max length of this modal text input. */
1401
+ maxLength?:number,
1402
+ /**Is this modal text input required? */
1403
+ required:boolean,
1404
+ /**The placeholder of this modal text input. */
1405
+ placeholder?:string,
1406
+ /**The initial value of this modal text input. */
1407
+ initialValue?:string
1408
+ }
1409
+
1410
+ /**## ODParagraphInputComponent `class`
1411
+ * A paragraph text input component for modals.
1412
+ * It must be placed inside an `ODLabelComponent`.
1413
+ */
1414
+ export class ODParagraphInputComponent extends ODComponent<ODParagraphInputComponentData,discord.TextInputBuilder> {
1415
+ constructor(id:ODValidId,data:Partial<ODParagraphInputComponentData>){
1416
+ const initData: ODParagraphInputComponentData = {required:false,...data}
1417
+ super(id,initData)
1418
+ }
1419
+
1420
+ async build(){
1421
+ return new discord.TextInputBuilder({
1422
+ style:discord.TextInputStyle.Paragraph,
1423
+ customId:this.data.customId,
1424
+ minLength:this.data.minLength,
1425
+ maxLength:this.data.maxLength,
1426
+ required:this.data.required,
1427
+ placeholder:this.data.placeholder,
1428
+ value:this.data.initialValue
1429
+ })
1430
+ }
1431
+
1432
+ /**Set the custom id of this modal text input. */
1433
+ setCustomId(id:string|null){
1434
+ this.data.customId = id ?? undefined
1435
+ return this
1436
+ }
1437
+ /**Set the minimum amount of characters of this modal text input. */
1438
+ setMinLength(length:number|null){
1439
+ this.data.minLength = length ?? undefined
1440
+ return this
1441
+ }
1442
+ /**Set the maximum amount of characters of this modal text input. */
1443
+ setMaxLength(length:number|null){
1444
+ this.data.maxLength = length ?? undefined
1445
+ return this
1446
+ }
1447
+ /**Set the placeholder of this modal text input. */
1448
+ setPlaceholder(placeholder:string|null){
1449
+ this.data.placeholder = placeholder ?? undefined
1450
+ return this
1451
+ }
1452
+ /**Set the initial value of this modal text input. */
1453
+ setInitialValue(initialValue:string|null){
1454
+ this.data.initialValue = initialValue ?? undefined
1455
+ return this
1456
+ }
1457
+ /**Mark this modal text input as required. */
1458
+ setRequired(required:boolean){
1459
+ this.data.required = required
1460
+ return this
1461
+ }
1462
+ }
1463
+
1464
+
1465
+ /**## ODDropdownComponentData `type`
1466
+ * The configurable settings/options for the `ODDropdownComponent`.
1467
+ */
1468
+ export interface ODDropdownComponentData {
1469
+ /**The type of this dropdown. */
1470
+ type:"string"|"role"|"channel"|"user"|"mentionable",
1471
+ /**The custom id of this dropdown. */
1472
+ customId?:string,
1473
+ /**The placeholder of this dropdown. */
1474
+ placeholder?:string,
1475
+ /**The minimum amount of items to be selected in this dropdown. */
1476
+ minValues?:number,
1477
+ /**The maximum amount of items to be selected in this dropdown. */
1478
+ maxValues?:number,
1479
+ /**Is this dropdown disabled? */
1480
+ disabled?:boolean,
1481
+
1482
+ /**Allowed channel types when the type is "channel" */
1483
+ channelTypes?:discord.ChannelType[]
1484
+ /**The options when the type is "string" */
1485
+ options?:discord.SelectMenuComponentOptionData[],
1486
+ /**The options when the type is "user" */
1487
+ users?:discord.User[],
1488
+ /**The options when the type is "role" */
1489
+ roles?:discord.Role[],
1490
+ /**The options when the type is "channel" */
1491
+ channels?:discord.Channel[],
1492
+ /**The options when the type is "mentionable" */
1493
+ mentionables?:(discord.User|discord.Role)[],
1494
+ }
1495
+
1496
+ /**## ODDropdownComponent `class`
1497
+ * A dropdown component which renders an interactive dropdown inside the message/modal.
1498
+ * A reply can be sent using Open Discord responders.
1499
+ */
1500
+ export class ODDropdownComponent extends ODComponent<ODDropdownComponentData,discord.BaseSelectMenuBuilder<discord.APISelectMenuComponent>> {
1501
+ constructor(id:ODValidId,data:Partial<ODDropdownComponentData>){
1502
+ const initData: ODDropdownComponentData = {type:"string",...data}
1503
+ super(id,initData)
1504
+ }
1505
+
1506
+ async build(){
1507
+ const genericOpts = {
1508
+ customId:this.data.customId,
1509
+ disabled:this.data.disabled,
1510
+ placeholder:this.data.placeholder,
1511
+ minValues:this.data.minValues,
1512
+ maxValues:this.data.maxValues,
1513
+ }
1514
+
1515
+ if (this.data.type == "string"){
1516
+ if (!this.data.options || this.data.options.length < 1) throw new ODSystemError("ODDropdownComponent:build('"+this.id.value+"') => Please provide at least one string option using setOptions().")
1517
+ return new discord.StringSelectMenuBuilder({
1518
+ ...genericOpts,
1519
+ options:this.data.options
1520
+ })
1521
+ }else if (this.data.type == "user"){
1522
+ if (!this.data.users || this.data.users.length < 1) throw new ODSystemError("ODDropdownComponent:build('"+this.id.value+"') => Please provide at least one user option using setUsers().")
1523
+ return new discord.UserSelectMenuBuilder({
1524
+ ...genericOpts,
1525
+ defaultValues:this.data.users.map((u) => ({id:u.id,type:discord.SelectMenuDefaultValueType.User}))
1526
+ })
1527
+ }else if (this.data.type == "role"){
1528
+ if (!this.data.roles || this.data.roles.length < 1) throw new ODSystemError("ODDropdownComponent:build('"+this.id.value+"') => Please provide at least one role option using setRoles().")
1529
+ return new discord.RoleSelectMenuBuilder({
1530
+ ...genericOpts,
1531
+ defaultValues:this.data.roles.map((r) => ({id:r.id,type:discord.SelectMenuDefaultValueType.Role}))
1532
+ })
1533
+ }else if (this.data.type == "channel"){
1534
+ if (!this.data.channels || this.data.channels.length < 1) throw new ODSystemError("ODDropdownComponent:build('"+this.id.value+"') => Please provide at least one channel option using setChannels().")
1535
+ return new discord.ChannelSelectMenuBuilder({
1536
+ ...genericOpts,
1537
+ channelTypes:this.data.channelTypes,
1538
+ defaultValues:this.data.channels.map((c) => ({id:c.id,type:discord.SelectMenuDefaultValueType.Channel}))
1539
+ })
1540
+ }else if (this.data.type == "mentionable"){
1541
+ if (!this.data.mentionables || this.data.mentionables.length < 1) throw new ODSystemError("ODDropdownComponent:build('"+this.id.value+"') => Please provide at least one role/user option using setMentionables().")
1542
+ return new discord.MentionableSelectMenuBuilder({
1543
+ ...genericOpts,
1544
+ defaultValues:this.data.mentionables.map((m) => (m instanceof discord.User ? {id:m.id,type:discord.SelectMenuDefaultValueType.User} : {id:m.id,type:discord.SelectMenuDefaultValueType.Role}))
1545
+ })
1546
+ }else throw new ODSystemError("ODDropdownComponent:build('"+this.id.value+"') => Please set the dropdown type to one of the following: string, user, role, channel, mentionable.")
1547
+ }
1548
+
1549
+ /**Set the custom id of this dropdown. */
1550
+ setCustomId(id:string|null){
1551
+ this.data.customId = id ?? undefined
1552
+ return this
1553
+ }
1554
+ /**Set the type of this dropdown. */
1555
+ setType(type:"string"|"role"|"channel"|"user"|"mentionable"){
1556
+ this.data.type = type
1557
+ return this
1558
+ }
1559
+ /**Set the minimum selection amount of this dropdown. */
1560
+ setMinValues(amount:number|null){
1561
+ this.data.minValues = amount ?? undefined
1562
+ return this
1563
+ }
1564
+ /**Set the maximum selection amount of this dropdown. */
1565
+ setMaxValues(amount:number|null){
1566
+ this.data.maxValues = amount ?? undefined
1567
+ return this
1568
+ }
1569
+ /**Set the placeholder of this dropdown. */
1570
+ setPlaceholder(placeholder:string|null){
1571
+ this.data.placeholder = placeholder ?? undefined
1572
+ return this
1573
+ }
1574
+ /**Disable this dropdown. */
1575
+ setDisabled(disabled:boolean){
1576
+ this.data.disabled = disabled
1577
+ return this
1578
+ }
1579
+ /**Set the available channel types of this dropdown. */
1580
+ setChannelTypes(channelTypes:discord.ChannelType[]){
1581
+ this.data.channelTypes = channelTypes
1582
+ return this
1583
+ }
1584
+ /**Set the options of this dropdown (when `type == "string"`) */
1585
+ setOptions(options:discord.SelectMenuComponentOptionData[]){
1586
+ this.data.options = options
1587
+ return this
1588
+ }
1589
+ /**Set the users of this dropdown (when `type == "user"`) */
1590
+ setUsers(users:discord.User[]){
1591
+ this.data.users = users
1592
+ return this
1593
+ }
1594
+ /**Set the roles of this dropdown (when `type == "role"`) */
1595
+ setRoles(roles:discord.Role[]){
1596
+ this.data.roles = roles
1597
+ return this
1598
+ }
1599
+ /**Set the channels of this dropdown (when `type == "channel"`) */
1600
+ setChannels(channels:discord.Channel[]){
1601
+ this.data.channels = channels
1602
+ return this
1603
+ }
1604
+ /**Set the mentionables of this dropdown (when `type == "mentionable"`) */
1605
+ setMentionables(mentionables:(discord.User|discord.Role)[]){
1606
+ this.data.mentionables = mentionables
1607
+ return this
1608
+ }
1609
+ }
1610
+
1611
+
1612
+ /**## ODRadioGroupComponentData `type`
1613
+ * The configurable settings/options for the `ODRadioGroupComponent`.
1614
+ */
1615
+ export interface ODRadioGroupComponentData {
1616
+ /**The custom id of this radio group. */
1617
+ customId?:string,
1618
+ /**Is this radio group required? (At least one option must be selected) */
1619
+ required:boolean,
1620
+ /**The available radio options. (min 2, max 10) */
1621
+ options:discord.APIRadioGroupOption[]
1622
+ }
1623
+
1624
+ /**## ODRadioGroupComponent `class`
1625
+ * A radio group component which renders an interactive radio group input inside a modal.
1626
+ */
1627
+ export class ODRadioGroupComponent extends ODComponent<ODRadioGroupComponentData,discord.RadioGroupBuilder> {
1628
+ constructor(id:ODValidId,data:Partial<ODRadioGroupComponentData>){
1629
+ const initData: ODRadioGroupComponentData = {required:false,options:[],...data}
1630
+ super(id,initData)
1631
+ }
1632
+
1633
+ async build(){
1634
+ if (!this.data.options || this.data.options.length < 2) throw new ODSystemError("ODRadioGroupComponent:build('"+this.id.value+"') => Please provide at least 2 radio options using setOptions().")
1635
+
1636
+ return new discord.RadioGroupBuilder({
1637
+ custom_id:this.data.customId,
1638
+ required:this.data.required,
1639
+ options:this.data.options
1640
+ })
1641
+ }
1642
+
1643
+ /**Set the custom id of this radio group. */
1644
+ setCustomId(id:string|null){
1645
+ this.data.customId = id ?? undefined
1646
+ return this
1647
+ }
1648
+ /**Mark this radio group as required (At least one option must be selected). */
1649
+ setRequired(required:boolean){
1650
+ this.data.required = required
1651
+ return this
1652
+ }
1653
+ /**Set the available radio options (min 2, max 10) */
1654
+ setOptions(options:discord.APIRadioGroupOption[]){
1655
+ this.data.options = options
1656
+ return this
1657
+ }
1658
+ }
1659
+
1660
+
1661
+ /**## ODCheckboxGroupComponentData `type`
1662
+ * The configurable settings/options for the `ODCheckboxGroupComponent`.
1663
+ */
1664
+ export interface ODCheckboxGroupComponentData {
1665
+ /**The custom id of this checkbox group. */
1666
+ customId?:string,
1667
+ /**Is this checkbox group required? (At least one option must be selected) */
1668
+ required:boolean,
1669
+ /**The available checkbox options. (min 1, max 10) */
1670
+ options:discord.APICheckboxGroupOption[],
1671
+ /**The minimum amount of checkboxes to be selected. */
1672
+ minValues?:number,
1673
+ /**The maximum amount of checkboxes to be selected. */
1674
+ maxValues?:number
1675
+ }
1676
+
1677
+ /**## ODCheckboxGroupComponent `class`
1678
+ * A checkbox group component which renders an interactive checkbox group input inside a modal.
1679
+ */
1680
+ export class ODCheckboxGroupComponent extends ODComponent<ODCheckboxGroupComponentData,discord.CheckboxGroupBuilder> {
1681
+ constructor(id:ODValidId,data:Partial<ODCheckboxGroupComponentData>){
1682
+ const initData: ODCheckboxGroupComponentData = {required:false,options:[],...data}
1683
+ super(id,initData)
1684
+ }
1685
+
1686
+ async build(){
1687
+ if (!this.data.options || this.data.options.length < 2) throw new ODSystemError("ODCheckboxGroupComponent:build('"+this.id.value+"') => Please provide at least 2 radio options using setOptions().")
1688
+
1689
+ return new discord.CheckboxGroupBuilder({
1690
+ custom_id:this.data.customId,
1691
+ required:this.data.required,
1692
+ options:this.data.options,
1693
+ min_values:this.data.minValues,
1694
+ max_values:this.data.maxValues
1695
+ })
1696
+ }
1697
+
1698
+ /**Set the custom id of this checkbox group. */
1699
+ setCustomId(id:string|null){
1700
+ this.data.customId = id ?? undefined
1701
+ return this
1702
+ }
1703
+ /**Mark this checkbox group as required (At least one option must be selected). */
1704
+ setRequired(required:boolean){
1705
+ this.data.required = required
1706
+ return this
1707
+ }
1708
+ /**Set the available checkbox options (min 2, max 10) */
1709
+ setOptions(options:discord.APICheckboxGroupOption[]){
1710
+ this.data.options = options
1711
+ return this
1712
+ }
1713
+ /**Set the minimum amount of selected checkboxes. */
1714
+ setMinValues(amount:number|null){
1715
+ this.data.minValues = amount ?? undefined
1716
+ return this
1717
+ }
1718
+ /**Set the maximum amount of selected checkboxes. */
1719
+ setMaxValues(amount:number|null){
1720
+ this.data.maxValues = amount ?? undefined
1721
+ return this
1722
+ }
1723
+ }
1724
+
1725
+
1726
+ /**## ODCheckboxComponentData `type`
1727
+ * The configurable settings/options for the `ODCheckboxComponent`.
1728
+ */
1729
+ export interface ODCheckboxComponentData {
1730
+ /**The custom id of this checkbox. */
1731
+ customId?:string,
1732
+ /**Is this checkbox enabled by default? */
1733
+ default:boolean,
1734
+ }
1735
+
1736
+ /**## ODCheckboxComponent `class`
1737
+ * A checkbox component which renders an interactive checkbox input inside a modal.
1738
+ * The label & description should be set via an `ODLabelComponent`
1739
+ */
1740
+ export class ODCheckboxComponent extends ODComponent<ODCheckboxComponentData,discord.CheckboxBuilder> {
1741
+ constructor(id:ODValidId,data:Partial<ODCheckboxComponentData>){
1742
+ const initData: ODCheckboxComponentData = {default:false,...data}
1743
+ super(id,initData)
1744
+ }
1745
+
1746
+ async build(){
1747
+ return new discord.CheckboxBuilder({
1748
+ custom_id:this.data.customId,
1749
+ default:this.data.default
1750
+ })
1751
+ }
1752
+
1753
+ /**Set the custom id of this checkbox. */
1754
+ setCustomId(id:string|null){
1755
+ this.data.customId = id ?? undefined
1756
+ return this
1757
+ }
1758
+ /**Mark this checkbox as enabled by default. */
1759
+ setDefault(enabledByDefault:boolean){
1760
+ this.data.default = enabledByDefault
1761
+ return this
1762
+ }
1763
+ }
1764
+
1765
+
1766
+ /**## ODFileUploadComponentData `type`
1767
+ * The configurable settings/options for the `ODFileUploadComponent`.
1768
+ */
1769
+ export interface ODFileUploadComponentData {
1770
+ /**The custom id of this file upload. */
1771
+ customId?:string,
1772
+ /**Is this file upload required? */
1773
+ required:boolean,
1774
+ /**The minimum amount of files to upload (0-10). */
1775
+ minAmount?:number,
1776
+ /**The maximum amount of files to upload (1-10). */
1777
+ maxAmount?:number
1778
+ }
1779
+
1780
+ /**## ODFileUploadComponent `class`
1781
+ * A file upload component which allows users to upload one or multiple files inside a modal.
1782
+ * The label & description should be set via an `ODLabelComponent`
1783
+ */
1784
+ export class ODFileUploadComponent extends ODComponent<ODFileUploadComponentData,discord.FileUploadBuilder> {
1785
+ constructor(id:ODValidId,data:Partial<ODFileUploadComponentData>){
1786
+ const initData: ODFileUploadComponentData = {required:false,...data}
1787
+ super(id,initData)
1788
+ }
1789
+
1790
+ async build(){
1791
+ if (typeof this.data.minAmount == "number" && !(this.data.minAmount >= 0 && this.data.minAmount <= 10)) throw new ODSystemError("ODFileUploadComponent:build('"+this.id.value+"') => Minimum upload amount must be a value from 0 to 10.")
1792
+ if (typeof this.data.maxAmount == "number" && !(this.data.maxAmount >= 1 && this.data.maxAmount <= 10)) throw new ODSystemError("ODFileUploadComponent:build('"+this.id.value+"') => Maximum upload amount must be a value from 1 to 10.")
1793
+
1794
+ return new discord.FileUploadBuilder({
1795
+ custom_id:this.data.customId,
1796
+ required:this.data.required,
1797
+ min_values:this.data.minAmount,
1798
+ max_values:this.data.maxAmount
1799
+ })
1800
+ }
1801
+
1802
+ /**Set the custom id of this dropdown. */
1803
+ setCustomId(id:string|null){
1804
+ this.data.customId = id ?? undefined
1805
+ return this
1806
+ }
1807
+ /**Mark this file upload as required. */
1808
+ setRequired(required:boolean){
1809
+ this.data.required = required
1810
+ return this
1811
+ }
1812
+ /**Set the minimum amount of files to upload (0-10). */
1813
+ setMinAmount(amount:number|null){
1814
+ this.data.minAmount = amount ?? undefined
1815
+ return this
1816
+ }
1817
+ /**Set the maximum amount of files to upload (1-10). */
1818
+ setMaxAmount(amount:number|null){
1819
+ this.data.maxAmount = amount ?? undefined
1820
+ return this
1821
+ }
1822
+ }