@mostfeatured/dbi 0.2.16 → 0.2.18

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 (81) hide show
  1. package/dist/src/types/Components/HTMLComponentsV2/index.d.ts +4 -0
  2. package/dist/src/types/Components/HTMLComponentsV2/index.d.ts.map +1 -1
  3. package/dist/src/types/Components/HTMLComponentsV2/index.js +40 -5
  4. package/dist/src/types/Components/HTMLComponentsV2/index.js.map +1 -1
  5. package/dist/src/types/Event.d.ts +21 -13
  6. package/dist/src/types/Event.d.ts.map +1 -1
  7. package/dist/src/types/Event.js.map +1 -1
  8. package/dist/test/index.js +1 -1
  9. package/dist/test/index.js.map +1 -1
  10. package/generated/namespaceData.d.ts +3 -1
  11. package/package.json +6 -2
  12. package/.gitattributes +0 -2
  13. package/.hintrc +0 -8
  14. package/.vscode/settings.json +0 -3
  15. package/docs/ADVANCED_FEATURES.md +0 -840
  16. package/docs/API_REFERENCE.md +0 -929
  17. package/docs/CHAT_INPUT.md +0 -811
  18. package/docs/COMPONENTS.md +0 -1039
  19. package/docs/EVENTS.md +0 -568
  20. package/docs/GETTING_STARTED.md +0 -398
  21. package/docs/LOCALIZATION.md +0 -777
  22. package/docs/README.md +0 -345
  23. package/docs/SVELTE_COMPONENTS.md +0 -1111
  24. package/docs/llm/ADVANCED_FEATURES.txt +0 -521
  25. package/docs/llm/API_REFERENCE.txt +0 -659
  26. package/docs/llm/CHAT_INPUT.txt +0 -514
  27. package/docs/llm/COMPONENTS.txt +0 -595
  28. package/docs/llm/EVENTS.txt +0 -449
  29. package/docs/llm/GETTING_STARTED.txt +0 -296
  30. package/docs/llm/LOCALIZATION.txt +0 -501
  31. package/docs/llm/README.txt +0 -193
  32. package/docs/llm/SVELTE_COMPONENTS.txt +0 -566
  33. package/src/DBI.ts +0 -1007
  34. package/src/Events.ts +0 -189
  35. package/src/data/eventMap.json +0 -248
  36. package/src/index.ts +0 -23
  37. package/src/methods/handleMessageCommands.ts +0 -482
  38. package/src/methods/hookEventListeners.ts +0 -119
  39. package/src/methods/hookInteractionListeners.ts +0 -314
  40. package/src/methods/publishInteractions.ts +0 -256
  41. package/src/types/ApplicationRoleConnectionMetadata.ts +0 -19
  42. package/src/types/Builders/ButtonBuilder.ts +0 -53
  43. package/src/types/Builders/ChannelSelectMenuBuilder.ts +0 -53
  44. package/src/types/Builders/MentionableSelectMenuBuilder.ts +0 -53
  45. package/src/types/Builders/ModalBuilder.ts +0 -53
  46. package/src/types/Builders/RoleSelectMenuBuilder.ts +0 -53
  47. package/src/types/Builders/StringSelectMenuBuilder.ts +0 -53
  48. package/src/types/Builders/UserSelectMenuBuilder.ts +0 -53
  49. package/src/types/ChatInput/ChatInput.ts +0 -28
  50. package/src/types/ChatInput/ChatInputOptions.ts +0 -388
  51. package/src/types/Components/Button.ts +0 -39
  52. package/src/types/Components/ChannelSelectMenu.ts +0 -43
  53. package/src/types/Components/HTMLComponentsV2/HTMLComponentsV2Handlers.ts +0 -78
  54. package/src/types/Components/HTMLComponentsV2/index.ts +0 -761
  55. package/src/types/Components/HTMLComponentsV2/parser.ts +0 -649
  56. package/src/types/Components/HTMLComponentsV2/svelteParser.ts +0 -1503
  57. package/src/types/Components/HTMLComponentsV2/svelteRenderer.ts +0 -416
  58. package/src/types/Components/MentionableSelectMenu.ts +0 -43
  59. package/src/types/Components/Modal.ts +0 -46
  60. package/src/types/Components/RoleSelectMenu.ts +0 -43
  61. package/src/types/Components/StringSelectMenu.ts +0 -43
  62. package/src/types/Components/UserSelectMenu.ts +0 -43
  63. package/src/types/Event.ts +0 -145
  64. package/src/types/Interaction.ts +0 -100
  65. package/src/types/other/CustomEvent.ts +0 -19
  66. package/src/types/other/FakeMessageInteraction.ts +0 -408
  67. package/src/types/other/InteractionLocale.ts +0 -34
  68. package/src/types/other/Locale.ts +0 -70
  69. package/src/types/other/MessageContextMenu.ts +0 -27
  70. package/src/types/other/UserContextMenu.ts +0 -25
  71. package/src/utils/MemoryStore.ts +0 -28
  72. package/src/utils/UtilTypes.ts +0 -11
  73. package/src/utils/customId.ts +0 -49
  74. package/src/utils/permissions.ts +0 -5
  75. package/src/utils/recursiveImport.ts +0 -35
  76. package/src/utils/recursiveUnload.ts +0 -25
  77. package/src/utils/unloadModule.ts +0 -7
  78. package/test/index.ts +0 -176
  79. package/test/product-showcase.svelte +0 -558
  80. package/test/test.ts +0 -3
  81. package/tsconfig.json +0 -51
@@ -1,649 +0,0 @@
1
- import { JSDOM } from "jsdom";
2
- import { Eta } from "eta";
3
- import { DBI } from "../../../DBI";
4
- import { NamespaceEnums } from "../../../../generated/namespaceData";
5
- import { ButtonStyle, ComponentType, TextInputStyle } from "discord.js";
6
- import { buildCustomId } from "../../../utils/customId";
7
- import * as stuffs from "stuffs";
8
-
9
- const eta = new Eta({
10
- useWith: true,
11
- });
12
-
13
- function getAttributeBoolean(element: Element, attribute: string): boolean {
14
- return element.hasAttribute(attribute) ? ['true', ''].includes(element.getAttribute(attribute)) : false
15
- }
16
-
17
- function parseElementDataAttributes(dbi: DBI<NamespaceEnums>, attributes: NamedNodeMap): any[] {
18
- let list = Array.from(attributes)
19
- .filter(attr => attr.nodeName.startsWith("data-"))
20
- .map(attr => {
21
- let splited = attr.nodeName.slice(5).split(":");
22
- let index = parseInt(splited[0]);
23
- let value;
24
- switch (splited[1]) {
25
- case "int":
26
- case "integer":
27
- case "float":
28
- case "number": value = Number(attr.nodeValue!); break;
29
- case "bool":
30
- case "boolean": value = attr.nodeValue === "true" || attr.nodeValue === "1"; break;
31
- case "string":
32
- case "str": value = attr.nodeValue; break;
33
- case "refrence":
34
- case "ref": value = dbi.data.refs.get(attr.nodeValue!)?.value; break;
35
- case "json": value = JSON.parse(attr.nodeValue!); break;
36
- default: value = attr.nodeValue; break;
37
- }
38
- return {
39
- index,
40
- value
41
- };
42
- })
43
- .sort((a, b) => a.index - b.index)
44
- .map(i => i.value);
45
- let data = attributes.getNamedItem("data")?.nodeValue;
46
- return list.length ? list : data ? [data] : [];
47
- }
48
-
49
- function getCleanTextContent(element: Element): string {
50
- return (element.textContent?.trim() || "").split("\n").map(line => line.trim()).join("\n");
51
- }
52
-
53
- function parseCustomIdAttributes(dbi: DBI<NamespaceEnums>, dbiName: string, element: Element): string {
54
- let customId = element.getAttribute("custom-id");
55
- if (!customId) {
56
- let name = element.getAttribute("name");
57
- if (!name) throw new Error("Element must have a name or custom-id attribute.");
58
- customId = buildCustomId(
59
- dbi,
60
- dbiName,
61
- [
62
- name,
63
- ...parseElementDataAttributes(dbi, element.attributes),
64
- ],
65
- element.hasAttribute("ttl") ? parseInt(element.getAttribute("ttl")!) : undefined,
66
- true
67
- );
68
- }
69
- return customId;
70
- }
71
-
72
- function parseActionRow(dbi: DBI<NamespaceEnums>, dbiName: string, actionRow: Element) {
73
- return {
74
- type: ComponentType.ActionRow,
75
- components: Array.from(actionRow.children).map((element) => {
76
- return parseElement(dbi, dbiName, element);
77
- })
78
- }
79
- }
80
-
81
- function parseButton(dbi: DBI<NamespaceEnums>, dbiName: string, button: Element) {
82
- const style = button.getAttribute("style") || button.getAttribute("button-style") || "Primary";
83
- const isDisabled = getAttributeBoolean(button, "disabled");
84
-
85
- const needsCustomId = style !== "Link" && style !== "Premium";
86
-
87
- return {
88
- type: ComponentType.Button,
89
- style: ButtonStyle[style],
90
- label: getCleanTextContent(button),
91
- emoji: button.getAttribute("emoji"),
92
- custom_id: needsCustomId ? parseCustomIdAttributes(dbi, dbiName, button) : undefined,
93
- disabled: isDisabled,
94
- url: button.getAttribute("url"),
95
- sku_id: button.getAttribute("sku-id"),
96
- }
97
- }
98
-
99
- function parseStringSelect(dbi: DBI<NamespaceEnums>, dbiName: string, stringSelect: Element) {
100
- let minValues = parseInt(stringSelect.getAttribute("min-values"));
101
- let maxValues = parseInt(stringSelect.getAttribute("max-values"));
102
-
103
- // Support both <option> and <select-option> elements (Svelte may output either)
104
- let options = Array.from(stringSelect.querySelectorAll("option, select-option")).map(option => {
105
- return {
106
- label: option.getAttribute("label") || getCleanTextContent(option),
107
- value: option.getAttribute("value"),
108
- description: option.getAttribute("description"),
109
- emoji: option.getAttribute("emoji"),
110
- default: getAttributeBoolean(option, "default"),
111
- }
112
- });
113
-
114
- return {
115
- type: ComponentType.StringSelect,
116
- custom_id: parseCustomIdAttributes(dbi, dbiName, stringSelect),
117
- placeholder: stringSelect.getAttribute("placeholder"),
118
- min_values: !isNaN(minValues) ? minValues : undefined,
119
- max_values: !isNaN(maxValues) ? maxValues : undefined,
120
- disabled: getAttributeBoolean(stringSelect, "disabled"),
121
- options
122
- }
123
- }
124
-
125
- function parseNonStringSelect(dbi: DBI<NamespaceEnums>, dbiName: string, userSelect: Element, type: ComponentType) {
126
- let minValues = parseInt(userSelect.getAttribute("min-values"));
127
- let maxValues = parseInt(userSelect.getAttribute("max-values"));
128
-
129
- // Support both <option> and <select-option> elements (Svelte may output either)
130
- let options = Array.from(userSelect.querySelectorAll("option, select-option")).map(option => {
131
- return {
132
- id: getCleanTextContent(option) || option.getAttribute("id"),
133
- type: option.getAttribute("type")
134
- }
135
- });
136
-
137
- return {
138
- type,
139
- custom_id: parseCustomIdAttributes(dbi, dbiName, userSelect),
140
- placeholder: userSelect.getAttribute("placeholder"),
141
- min_values: !isNaN(minValues) ? minValues : undefined,
142
- max_values: !isNaN(maxValues) ? maxValues : undefined,
143
- disabled: getAttributeBoolean(userSelect, "disabled"),
144
- options
145
- }
146
- }
147
-
148
- function parseSection(dbi: DBI<NamespaceEnums>, dbiName: string, sectionElement: Element) {
149
- const childs = [...sectionElement.children];
150
- const components = childs.find(el => el.tagName === "COMPONENTS");
151
- const children = Array.from(components?.children || []);
152
-
153
- // Look for accessory in <accessory> wrapper or directly as <thumbnail>/<button>
154
- let accessory = childs.find(el => el.tagName === "ACCESSORY")?.children?.[0];
155
-
156
- // If no <accessory> wrapper, look for direct thumbnail or button
157
- if (!accessory) {
158
- accessory = childs.find(el => el.tagName === "THUMBNAIL" || el.tagName === "BUTTON");
159
- }
160
-
161
- return {
162
- type: ComponentType.Section,
163
- components: children.map((element) => {
164
- return parseElement(dbi, dbiName, element);
165
- }),
166
- ...(accessory ? { accessory: parseElement(dbi, dbiName, accessory) } : {})
167
- }
168
- }
169
-
170
- function parseTextDisplay(dbi: DBI<NamespaceEnums>, dbiName: string, textDisplayElement: Element) {
171
- return {
172
- type: ComponentType.TextDisplay,
173
- content: getCleanTextContent(textDisplayElement) || "",
174
- }
175
- }
176
-
177
- function parseThumbnail(dbi: DBI<NamespaceEnums>, dbiName: string, thumbnailElement: Element) {
178
- return {
179
- type: ComponentType.Thumbnail,
180
- media: {
181
- url: thumbnailElement.getAttribute("url") || thumbnailElement.getAttribute("media")
182
- }
183
- }
184
- }
185
-
186
- function parseMediaGallery(dbi: DBI<NamespaceEnums>, dbiName: string, mediaGalleryElement: Element) {
187
- return {
188
- type: ComponentType.MediaGallery,
189
- items: Array.from(mediaGalleryElement.querySelectorAll("item")).map(item => {
190
- return {
191
- media: {
192
- url: item.getAttribute("url")
193
- },
194
- description: getCleanTextContent(mediaGalleryElement) || item.getAttribute("description") || "",
195
- spoiler: getAttributeBoolean(item, "spoiler"),
196
- };
197
- })
198
- }
199
- }
200
-
201
- function parseFile(dbi: DBI<NamespaceEnums>, dbiName: string, fileElement: Element) {
202
- return {
203
- type: ComponentType.File,
204
- file: {
205
- url: fileElement.getAttribute("url"),
206
- },
207
- spoiler: getAttributeBoolean(fileElement, "spoiler"),
208
- }
209
- }
210
-
211
- function parseSeparator(dbi: DBI<NamespaceEnums>, dbiName: string, separatorElement: Element) {
212
- return {
213
- type: ComponentType.Separator,
214
- divider: separatorElement.hasAttribute("divider"),
215
- spacing: parseInt(separatorElement.getAttribute("spacing") || '1'),
216
- }
217
- }
218
-
219
- function parseContainer(dbi: DBI<NamespaceEnums>, dbiName: string, containerElement: Element) {
220
- const components = [...containerElement.children].find(el => el.tagName === "COMPONENTS");
221
- const children = Array.from(components?.children || []);
222
-
223
- return {
224
- type: ComponentType.Container,
225
- components: children.map((element) => {
226
- return parseElement(dbi, dbiName, element);
227
- }),
228
- accent_color: parseColor(containerElement.getAttribute("accent-color") || ""),
229
- spoiler: containerElement.hasAttribute("spoiler"),
230
- }
231
- }
232
-
233
- function parseTextInput(dbi: DBI<NamespaceEnums>, dbiName: string, textInputSelect: Element) {
234
- let minLength = parseInt(textInputSelect.getAttribute("min-length"));
235
- let maxLength = parseInt(textInputSelect.getAttribute("max-length"));
236
-
237
- return {
238
- type: ComponentType.TextInput,
239
- custom_id: textInputSelect.getAttribute("custom-id") || textInputSelect.getAttribute("id"),
240
- style: TextInputStyle[textInputSelect.getAttribute("input-style") || textInputSelect.getAttribute("style") || "Short"],
241
- label: textInputSelect.getAttribute("label"),
242
- placeholder: textInputSelect.getAttribute("placeholder"),
243
- min_length: !isNaN(minLength) ? minLength : undefined,
244
- max_length: !isNaN(maxLength) ? maxLength : undefined,
245
- required: textInputSelect.hasAttribute("required"),
246
- value: getCleanTextContent(textInputSelect) || textInputSelect.getAttribute("value"),
247
- }
248
- }
249
-
250
- /**
251
- * Parse a modal element into Discord Modal format
252
- * Now supports the new Label component structure with various child components:
253
- * - text-input (type 4)
254
- * - string-select (type 3)
255
- * - user-select (type 5)
256
- * - role-select (type 6)
257
- * - mentionable-select (type 7)
258
- * - channel-select (type 8)
259
- * - file-upload (type 19)
260
- * - text-display (type 10)
261
- *
262
- * Example with Label wrapper (recommended):
263
- * <components type="modal" id="my-modal" title="My Modal">
264
- * <label label="Your Name" description="Enter your full name">
265
- * <text-input id="name" style="Short" />
266
- * </label>
267
- * <label label="Select Bug Type">
268
- * <string-select id="bug-type">
269
- * <option value="ant">🐜 Ant</option>
270
- * </string-select>
271
- * </label>
272
- * </components>
273
- *
274
- * Legacy format (still supported but deprecated):
275
- * <components type="modal" id="my-modal" title="My Modal">
276
- * <text-input id="name" label="Name" />
277
- * </components>
278
- */
279
- export function parseModal(dbi: DBI<NamespaceEnums>, dbiName: string, modalElement: Element, { data = {}, ttl = 0 }: any = {}) {
280
- const title = modalElement.getAttribute("title") || "Modal";
281
- const modalId = modalElement.getAttribute("id") || modalElement.getAttribute("name");
282
-
283
- if (!modalId) {
284
- throw new Error("Modal must have an id or name attribute");
285
- }
286
-
287
- // Parse modal children - supports Label, action-row (legacy), and direct text-input (legacy)
288
- const children = Array.from(modalElement.children);
289
- const components: any[] = [];
290
-
291
- for (const element of children) {
292
- const tagName = element.tagName.toUpperCase();
293
-
294
- if (tagName === "FIELD") {
295
- // New Label component (type 18) - wraps other modal components
296
- components.push(parseModalField(dbi, dbiName, element));
297
- } else if (tagName === "ACTION-ROW") {
298
- // Legacy: Action row with text inputs (deprecated but still supported)
299
- components.push({
300
- type: ComponentType.ActionRow,
301
- components: Array.from(element.children).map((child) => {
302
- return parseModalComponent(dbi, dbiName, child);
303
- })
304
- });
305
- } else if (tagName === "TEXT-INPUT") {
306
- // Legacy: Direct text-input auto-wrapped in action row
307
- components.push({
308
- type: ComponentType.ActionRow,
309
- components: [parseTextInput(dbi, dbiName, element)]
310
- });
311
- } else if (tagName === "TEXT-DISPLAY") {
312
- // Text display directly in modal (type 10)
313
- components.push({
314
- type: 10, // ComponentType.TextDisplay
315
- content: getCleanTextContent(element)
316
- });
317
- } else {
318
- // Try to parse as a modal-supported component and auto-wrap in Label
319
- const supportedTags = ['STRING-SELECT', 'USER-SELECT', 'ROLE-SELECT', 'MENTIONABLE-SELECT', 'CHANNEL-SELECT', 'FILE-UPLOAD'];
320
- if (supportedTags.includes(tagName)) {
321
- // Auto-wrap in a label for convenience
322
- components.push({
323
- type: 18, // Label
324
- label: element.getAttribute("label") || element.getAttribute("placeholder") || tagName.toLowerCase(),
325
- description: element.getAttribute("description"),
326
- component: parseModalComponent(dbi, dbiName, element)
327
- });
328
- } else {
329
- console.warn(`[DBI-Modal] Unsupported element in modal: ${tagName}. Supported: field, text-input, text-display, string-select, user-select, role-select, mentionable-select, channel-select, file-upload`);
330
- }
331
- }
332
- }
333
-
334
- // Build custom_id for the modal - include ref for state persistence
335
- const customIdParts: any[] = [modalId];
336
-
337
- // Add ref if data has one (for state persistence across modal submit)
338
- // Use ¤ prefix so it gets resolved to actual state object when interaction is received
339
- if (data?.$ref) {
340
- customIdParts.push(`¤${data.$ref}`);
341
- }
342
-
343
- const customId = buildCustomId(dbi, dbiName, customIdParts, ttl, true);
344
-
345
- return {
346
- title,
347
- customId,
348
- components,
349
- modalId, // Store original ID for handler lookup
350
- };
351
- }
352
-
353
- /**
354
- * Parse a Field/Label component for modals (type 18)
355
- * <field label="Field Name" description="Optional description">
356
- * <text-input id="field" />
357
- * </field>
358
- */
359
- function parseModalField(dbi: DBI<NamespaceEnums>, dbiName: string, fieldElement: Element) {
360
- const label = fieldElement.getAttribute("label") || "Label";
361
- const description = fieldElement.getAttribute("description");
362
-
363
- // Get the child component
364
- const child = fieldElement.children[0];
365
- if (!child) {
366
- throw new Error("Field component must have a child component (text-input, string-select, etc.);");
367
- }
368
-
369
- return {
370
- type: 18, // Label component type
371
- label,
372
- description,
373
- component: parseModalComponent(dbi, dbiName, child)
374
- };
375
- }
376
-
377
- /**
378
- * Parse a component that can be inside a modal Label
379
- */
380
- function parseModalComponent(dbi: DBI<NamespaceEnums>, dbiName: string, element: Element): any {
381
- const tagName = element.tagName.toUpperCase();
382
-
383
- switch (tagName) {
384
- case "TEXT-INPUT":
385
- return parseTextInput(dbi, dbiName, element);
386
-
387
- case "STRING-SELECT":
388
- case "STRING-SELECT-MENU":
389
- return parseStringSelectForModal(dbi, dbiName, element);
390
-
391
- case "USER-SELECT":
392
- case "USER-SELECT-MENU":
393
- return parseAutoSelectForModal(dbi, dbiName, element, 5); // type 5
394
-
395
- case "ROLE-SELECT":
396
- case "ROLE-SELECT-MENU":
397
- return parseAutoSelectForModal(dbi, dbiName, element, 6); // type 6
398
-
399
- case "MENTIONABLE-SELECT":
400
- case "MENTIONABLE-SELECT-MENU":
401
- return parseAutoSelectForModal(dbi, dbiName, element, 7); // type 7
402
-
403
- case "CHANNEL-SELECT":
404
- case "CHANNEL-SELECT-MENU":
405
- return parseAutoSelectForModal(dbi, dbiName, element, 8); // type 8
406
-
407
- case "FILE-UPLOAD":
408
- return parseFileUpload(dbi, dbiName, element);
409
-
410
- default:
411
- throw new Error(`Unsupported modal component: ${tagName}. Supported: text-input, string-select, user-select, role-select, mentionable-select, channel-select, file-upload`);
412
- }
413
- }
414
-
415
- /**
416
- * Parse a string select for modal (similar to message but with required field)
417
- */
418
- function parseStringSelectForModal(dbi: DBI<NamespaceEnums>, dbiName: string, element: Element) {
419
- const customId = element.getAttribute("id") || element.getAttribute("custom-id") || element.getAttribute("name");
420
- if (!customId) {
421
- throw new Error("String select in modal must have an id, custom-id, or name attribute");
422
- }
423
-
424
- const minValues = parseInt(element.getAttribute("min-values") || "1");
425
- const maxValues = parseInt(element.getAttribute("max-values") || "1");
426
- const required = element.getAttribute("required") !== "false";
427
-
428
- const options = Array.from(element.querySelectorAll("option")).map((option) => ({
429
- label: getCleanTextContent(option) || option.getAttribute("label") || "Option",
430
- value: option.getAttribute("value") || getCleanTextContent(option),
431
- description: option.getAttribute("description"),
432
- emoji: option.getAttribute("emoji") ? { name: option.getAttribute("emoji") } : undefined,
433
- default: getAttributeBoolean(option, "default")
434
- }));
435
-
436
- return {
437
- type: 3, // String Select
438
- custom_id: customId,
439
- placeholder: element.getAttribute("placeholder"),
440
- min_values: isNaN(minValues) ? 1 : minValues,
441
- max_values: isNaN(maxValues) ? 1 : maxValues,
442
- required,
443
- options
444
- };
445
- }
446
-
447
- /**
448
- * Parse auto-populated select menus for modal (user, role, mentionable, channel)
449
- */
450
- function parseAutoSelectForModal(dbi: DBI<NamespaceEnums>, dbiName: string, element: Element, componentType: number) {
451
- const customId = element.getAttribute("id") || element.getAttribute("custom-id") || element.getAttribute("name");
452
- if (!customId) {
453
- throw new Error(`Select menu in modal must have an id, custom-id, or name attribute`);
454
- }
455
-
456
- const minValues = parseInt(element.getAttribute("min-values") || "1");
457
- const maxValues = parseInt(element.getAttribute("max-values") || "1");
458
- const required = element.getAttribute("required") !== "false";
459
-
460
- const result: any = {
461
- type: componentType,
462
- custom_id: customId,
463
- placeholder: element.getAttribute("placeholder"),
464
- min_values: isNaN(minValues) ? 1 : minValues,
465
- max_values: isNaN(maxValues) ? 1 : maxValues,
466
- required
467
- };
468
-
469
- // Channel select can have channel_types filter
470
- if (componentType === 8) {
471
- const channelTypes = element.getAttribute("channel-types");
472
- if (channelTypes) {
473
- result.channel_types = channelTypes.split(",").map(t => parseInt(t.trim()));
474
- }
475
- }
476
-
477
- return result;
478
- }
479
-
480
- /**
481
- * Parse file upload component for modals (type 19)
482
- * <file-upload id="attachment" min-values="1" max-values="5" />
483
- */
484
- function parseFileUpload(dbi: DBI<NamespaceEnums>, dbiName: string, element: Element) {
485
- const customId = element.getAttribute("id") || element.getAttribute("custom-id") || element.getAttribute("name");
486
- if (!customId) {
487
- throw new Error("File upload must have an id, custom-id, or name attribute");
488
- }
489
-
490
- const minValues = parseInt(element.getAttribute("min-values") || "1");
491
- const maxValues = parseInt(element.getAttribute("max-values") || "1");
492
- const required = element.getAttribute("required") !== "false";
493
-
494
- return {
495
- type: 19, // File Upload
496
- custom_id: customId,
497
- min_values: isNaN(minValues) ? 1 : minValues,
498
- max_values: isNaN(maxValues) ? 1 : maxValues,
499
- required
500
- };
501
- }
502
-
503
- function parseColor(color: string) {
504
- if (!color) return;
505
- if (/\d{3,6}/.test(color)) return parseInt(color, 10);
506
- if (color.startsWith("#")) return parseInt(color.slice(1), 16);
507
- if (color.startsWith("0x")) return parseInt(color.slice(2), 16);
508
- if (color.startsWith("rgb(")) {
509
- const rgb = color.slice(4, -1).split(",").map(Number);
510
- return (rgb[0] << 16) + (rgb[1] << 8) + rgb[2];
511
- }
512
- return parseInt(color, 16);
513
- }
514
-
515
- function parseElement(dbi: DBI<NamespaceEnums>, dbiName: string, element: Element) {
516
- switch (element.tagName) {
517
- case "ACTION-ROW":
518
- return parseActionRow(dbi, dbiName, element);
519
- case "BUTTON":
520
- return parseButton(dbi, dbiName, element);
521
- case "STRING-SELECT":
522
- case "STRING-SELECT-MENU":
523
- return parseStringSelect(dbi, dbiName, element);
524
- case "USER-SELECT":
525
- case "USER-SELECT-MENU":
526
- return parseNonStringSelect(dbi, dbiName, element, ComponentType.UserSelect);
527
- case "ROLE-SELECT":
528
- case "ROLE-SELECT-MENU":
529
- return parseNonStringSelect(dbi, dbiName, element, ComponentType.RoleSelect);
530
- case "MENTIONABLE-SELECT":
531
- case "MENTIONABLE-SELECT-MENU":
532
- return parseNonStringSelect(dbi, dbiName, element, ComponentType.MentionableSelect);
533
- case "CHANNEL-SELECT":
534
- case "CHANNEL-SELECT-MENU":
535
- return parseNonStringSelect(dbi, dbiName, element, ComponentType.ChannelSelect);
536
- case "SECTION":
537
- return parseSection(dbi, dbiName, element);
538
- case "TEXT-DISPLAY":
539
- return parseTextDisplay(dbi, dbiName, element);
540
- case "THUMBNAIL":
541
- return parseThumbnail(dbi, dbiName, element);
542
- case "MEDIA-GALLERY":
543
- return parseMediaGallery(dbi, dbiName, element);
544
- case "FILE":
545
- return parseFile(dbi, dbiName, element);
546
- case "SEPARATOR":
547
- return parseSeparator(dbi, dbiName, element);
548
- case "CONTAINER":
549
- return parseContainer(dbi, dbiName, element);
550
- case "TEXT-INPUT":
551
- return parseTextInput(dbi, dbiName, element);
552
- default:
553
- throw new Error(`Unknown HTML component: ${element.tagName}`);
554
- }
555
- }
556
-
557
- export function parseHTMLComponentsV2(dbi: DBI<NamespaceEnums>, template: string, dbiName: string, { data = {}, ttl = 0 }: any = { data: {}, ttl: 0 }) {
558
- const { window: { document } } = new JSDOM(
559
- eta.renderString(
560
- template,
561
- {
562
- it: data,
563
- $refId(obj: any) {
564
- if (obj?.$ref) return `¤${obj.$ref}`;
565
- let id = stuffs.randomString(8);
566
- Object.assign(obj, {
567
- $ref: id,
568
- $unRef() { return dbi.data.refs.delete(id); },
569
- });
570
- dbi.data.refs.set(id, { at: Date.now(), value: obj, ttl });
571
- return `¤${id}`;
572
- }
573
- }
574
- )
575
- );
576
-
577
- const components = [...document.body.children].find(el => el.tagName === "COMPONENTS");
578
- const children = Array.from(components?.children || []);
579
-
580
- if (!children.length) throw new Error("No components found in the provided HTML template.");
581
-
582
- return children.map((element) => {
583
- return parseElement(dbi, dbiName, element);
584
- });
585
- }
586
-
587
- export interface ParsedComponentsResult {
588
- /** Main components to display (id="main" or no id/type) */
589
- components: any[];
590
- /** Modal definitions keyed by their id */
591
- modals: Map<string, { title: string; customId: string; components: any[]; modalId: string }>;
592
- }
593
-
594
- /**
595
- * Parse HTML with support for multiple <components> elements
596
- * - <components> or <components id="main"> - Main display components
597
- * - <components type="modal" id="xxx"> - Modal definitions
598
- */
599
- export function parseHTMLComponentsV2Multi(dbi: DBI<NamespaceEnums>, template: string, dbiName: string, { data = {}, ttl = 0 }: any = { data: {}, ttl: 0 }): ParsedComponentsResult {
600
- const { window: { document } } = new JSDOM(
601
- eta.renderString(
602
- template,
603
- {
604
- it: data,
605
- $refId(obj: any) {
606
- if (obj?.$ref) return `¤${obj.$ref}`;
607
- let id = stuffs.randomString(8);
608
- Object.assign(obj, {
609
- $ref: id,
610
- $unRef() { return dbi.data.refs.delete(id); },
611
- });
612
- dbi.data.refs.set(id, { at: Date.now(), value: obj, ttl });
613
- return `¤${id}`;
614
- }
615
- }
616
- )
617
- );
618
-
619
- // Only select top-level <components> elements (direct children of body)
620
- // This avoids selecting nested <components> inside <container>, <section>, etc.
621
- const allComponents = [...document.body.children].filter(el => el.tagName === "COMPONENTS");
622
- const modals = new Map<string, { title: string; customId: string; components: any[]; modalId: string }>();
623
- let mainComponents: any[] = [];
624
-
625
- for (const componentsEl of allComponents) {
626
- const type = componentsEl.getAttribute("type");
627
- const id = componentsEl.getAttribute("id");
628
-
629
- if (type === "modal") {
630
- // This is a modal definition
631
- const modalData = parseModal(dbi, dbiName, componentsEl, { data, ttl });
632
- modals.set(modalData.modalId, modalData);
633
- } else if (!id || id === "main") {
634
- // This is the main components (no id, or id="main")
635
- const children = Array.from(componentsEl.children);
636
- mainComponents = children.map((element) => parseElement(dbi, dbiName, element));
637
- }
638
- // Components with other ids are ignored (could be used for other purposes)
639
- }
640
-
641
- if (!mainComponents.length && modals.size === 0) {
642
- throw new Error("No components found in the provided HTML template.");
643
- }
644
-
645
- return {
646
- components: mainComponents,
647
- modals
648
- };
649
- }