@minesa-org/mini-interaction 0.3.14 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +143 -2
- package/dist/builders/FileUploadBuilder.d.ts +3 -23
- package/dist/builders/FileUploadBuilder.js +16 -53
- package/dist/builders/LabelBuilder.d.ts +3 -22
- package/dist/builders/LabelBuilder.js +16 -50
- package/dist/builders/ModalBuilder.d.ts +4 -28
- package/dist/builders/ModalBuilder.js +20 -81
- package/dist/builders/ModalChannelSelectMenuBuilder.d.ts +7 -43
- package/dist/builders/ModalChannelSelectMenuBuilder.js +22 -107
- package/dist/builders/ModalRoleSelectMenuBuilder.d.ts +6 -35
- package/dist/builders/ModalRoleSelectMenuBuilder.js +21 -90
- package/dist/builders/RadioBuilder.d.ts +17 -0
- package/dist/builders/RadioBuilder.js +29 -0
- package/dist/builders/TextInputBuilder.d.ts +3 -32
- package/dist/builders/TextInputBuilder.js +21 -83
- package/dist/builders/__tests__/builders.test.d.ts +1 -0
- package/dist/builders/__tests__/builders.test.js +31 -0
- package/dist/builders/index.d.ts +2 -0
- package/dist/builders/index.js +1 -0
- package/dist/core/http/DiscordRestClient.d.ts +19 -0
- package/dist/core/http/DiscordRestClient.js +55 -0
- package/dist/core/interactions/InteractionContext.d.ts +25 -0
- package/dist/core/interactions/InteractionContext.js +44 -0
- package/dist/core/interactions/InteractionVerifier.d.ts +8 -0
- package/dist/core/interactions/InteractionVerifier.js +9 -0
- package/dist/core/interactions/__tests__/interaction-context.test.d.ts +1 -0
- package/dist/core/interactions/__tests__/interaction-context.test.js +38 -0
- package/dist/index.d.ts +10 -6
- package/dist/index.js +8 -3
- package/dist/router/InteractionRouter.d.ts +12 -0
- package/dist/router/InteractionRouter.js +33 -0
- package/dist/types/InteractionFlags.d.ts +0 -4
- package/dist/types/InteractionFlags.js +0 -4
- package/dist/types/discord.d.ts +11 -0
- package/dist/types/discord.js +1 -0
- package/dist/types/radio.d.ts +23 -0
- package/dist/types/radio.js +5 -0
- package/dist/types/validation.d.ts +8 -0
- package/dist/types/validation.js +26 -0
- package/dist/utils/ModalSubmitInteraction.js +5 -2
- package/package.json +55 -53
- package/dist/clients/MiniInteraction.d.ts +0 -441
- package/dist/clients/MiniInteraction.js +0 -1731
package/README.md
CHANGED
|
@@ -1,3 +1,144 @@
|
|
|
1
|
-
# Mini Interaction
|
|
1
|
+
# 🌌 Mini Interaction
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **Sleek, Modular, and Type-Safe Discord Interactions Framework.**
|
|
4
|
+
|
|
5
|
+
Mini Interaction is a high-performance framework designed for building Discord HTTP/Webhook-based bots. It provides a modular architecture that separates concerns, making your bot easier to maintain, test, and scale.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## ✨ Features
|
|
10
|
+
|
|
11
|
+
- **🚀 Modular Router**: Easily map commands, components, and modals to handlers.
|
|
12
|
+
- **⚡ Core V10 Engine**: Native support for Discord API v10 payloads.
|
|
13
|
+
- **🛡️ Type Safety**: Full TypeScript support with rich autocompletion.
|
|
14
|
+
- **🧩 Fluent Builders**: Construct complex messages and components with a premium API.
|
|
15
|
+
- **🔐 Integrated OAuth**: Simple handlers for Discord OAuth2 flows.
|
|
16
|
+
- **🗃️ Mini Database**: Lightweight, document-based storage integration.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 📦 Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @minesa-org/mini-interaction
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 🛠️ Quick Start
|
|
29
|
+
|
|
30
|
+
Mini Interaction uses a modular approach with a dedicated Router and Context.
|
|
31
|
+
|
|
32
|
+
### 1. Define your Router
|
|
33
|
+
```ts
|
|
34
|
+
import { InteractionRouter } from '@minesa-org/mini-interaction';
|
|
35
|
+
|
|
36
|
+
const router = new InteractionRouter();
|
|
37
|
+
|
|
38
|
+
// Register a slash command
|
|
39
|
+
router.onCommand('ping', async (interaction, ctx) => {
|
|
40
|
+
return ctx.reply({ content: '🏓 Pong!' });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Register a component handler
|
|
44
|
+
router.onComponent('my_button', async (interaction, ctx) => {
|
|
45
|
+
return ctx.reply({ content: 'Button clicked!', ephemeral: true });
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 2. Handle Interactions
|
|
50
|
+
```ts
|
|
51
|
+
import {
|
|
52
|
+
verifyAndParseInteraction,
|
|
53
|
+
InteractionContext,
|
|
54
|
+
DiscordRestClient
|
|
55
|
+
} from '@minesa-org/mini-interaction';
|
|
56
|
+
|
|
57
|
+
const rest = new DiscordRestClient({
|
|
58
|
+
applicationId: process.env.DISCORD_APP_ID,
|
|
59
|
+
token: process.env.DISCORD_TOKEN
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// In your web server (e.g., Next.js, Vercel, Express)
|
|
63
|
+
export async function POST(req) {
|
|
64
|
+
const body = await req.text();
|
|
65
|
+
const signature = req.headers.get('x-signature-ed25519');
|
|
66
|
+
const timestamp = req.headers.get('x-signature-timestamp');
|
|
67
|
+
|
|
68
|
+
// Verify and parse the interaction
|
|
69
|
+
const interaction = await verifyAndParseInteraction({
|
|
70
|
+
body,
|
|
71
|
+
signature,
|
|
72
|
+
timestamp,
|
|
73
|
+
publicKey: process.env.DISCORD_PUBLIC_KEY
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (interaction.type === 1) return Response.json({ type: 1 });
|
|
77
|
+
|
|
78
|
+
const ctx = new InteractionContext({ interaction, rest });
|
|
79
|
+
const response = await router.dispatch(interaction, ctx);
|
|
80
|
+
|
|
81
|
+
return Response.json(response ?? ctx.deferReply());
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 🎨 Message Builders
|
|
88
|
+
|
|
89
|
+
Mini Interaction provides a rich set of builders to create beautiful Discord content.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { ModalBuilder, TextInputBuilder, TextInputStyle } from '@minesa-org/mini-interaction';
|
|
93
|
+
|
|
94
|
+
const modal = new ModalBuilder()
|
|
95
|
+
.setCustomId('feedback_form')
|
|
96
|
+
.setTitle('Send us Feedback')
|
|
97
|
+
.addComponents(
|
|
98
|
+
new TextInputBuilder()
|
|
99
|
+
.setCustomId('feedback_text')
|
|
100
|
+
.setLabel('Your Message')
|
|
101
|
+
.setStyle(TextInputStyle.Paragraph)
|
|
102
|
+
.setPlaceholder('Tell us what you think...')
|
|
103
|
+
);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 📡 Advanced Routing
|
|
109
|
+
|
|
110
|
+
You can organize your handlers into separate modules for better scalability.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
// components/modals.ts
|
|
114
|
+
router.onModal('feedback_submit', async (interaction, ctx) => {
|
|
115
|
+
const feedback = interaction.getTextFieldValue('feedback_text');
|
|
116
|
+
// Process feedback...
|
|
117
|
+
return ctx.reply({ content: 'Thank you for your feedback!' });
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 🛡️ Error Handling
|
|
124
|
+
|
|
125
|
+
Mini Interaction includes built-in validation to ensure your payloads follow Discord's requirements.
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { ValidationError } from '@minesa-org/mini-interaction';
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const builder = new TextInputBuilder().setCustomId(''); // Too short!
|
|
132
|
+
builder.toJSON();
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (error instanceof ValidationError) {
|
|
135
|
+
console.error(`Validation failed for ${error.component}: ${error.message}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 📜 License
|
|
143
|
+
|
|
144
|
+
MIT © [Minesa](https://github.com/minesa-org)
|
|
@@ -1,37 +1,17 @@
|
|
|
1
|
-
import { type APIFileUploadComponent } from
|
|
2
|
-
import type
|
|
3
|
-
/** Shape describing initial file upload data accepted by the builder. */
|
|
1
|
+
import { type APIFileUploadComponent } from 'discord-api-types/v10';
|
|
2
|
+
import { type JSONEncodable } from './shared.js';
|
|
4
3
|
export type FileUploadBuilderData = {
|
|
5
4
|
customId?: string;
|
|
6
5
|
minValues?: number;
|
|
7
6
|
maxValues?: number;
|
|
8
7
|
required?: boolean;
|
|
9
8
|
};
|
|
10
|
-
/** Builder for Discord file upload components used in modals. */
|
|
11
9
|
export declare class FileUploadBuilder implements JSONEncodable<APIFileUploadComponent> {
|
|
12
|
-
private data;
|
|
13
|
-
/**
|
|
14
|
-
* Creates a new file upload builder with optional seed data.
|
|
15
|
-
*/
|
|
10
|
+
private readonly data;
|
|
16
11
|
constructor(data?: FileUploadBuilderData);
|
|
17
|
-
/**
|
|
18
|
-
* Sets the custom identifier for this file upload component.
|
|
19
|
-
*/
|
|
20
12
|
setCustomId(customId: string): this;
|
|
21
|
-
/**
|
|
22
|
-
* Sets the minimum number of files that must be uploaded (min 0, max 10).
|
|
23
|
-
*/
|
|
24
13
|
setMinValues(minValues: number): this;
|
|
25
|
-
/**
|
|
26
|
-
* Sets the maximum number of files that can be uploaded (max 10).
|
|
27
|
-
*/
|
|
28
14
|
setMaxValues(maxValues: number): this;
|
|
29
|
-
/**
|
|
30
|
-
* Sets whether files are required before submitting the modal.
|
|
31
|
-
*/
|
|
32
15
|
setRequired(required: boolean): this;
|
|
33
|
-
/**
|
|
34
|
-
* Serialises the builder into an API compatible file upload component payload.
|
|
35
|
-
*/
|
|
36
16
|
toJSON(): APIFileUploadComponent;
|
|
37
17
|
}
|
|
@@ -1,59 +1,22 @@
|
|
|
1
|
-
import { ComponentType
|
|
2
|
-
|
|
1
|
+
import { ComponentType } from 'discord-api-types/v10';
|
|
2
|
+
import { assertDefined, assertRange, assertStringLength, ValidationError } from '../types/validation.js';
|
|
3
3
|
export class FileUploadBuilder {
|
|
4
4
|
data;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
customId: data.customId,
|
|
11
|
-
minValues: data.minValues,
|
|
12
|
-
maxValues: data.maxValues,
|
|
13
|
-
required: data.required,
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Sets the custom identifier for this file upload component.
|
|
18
|
-
*/
|
|
19
|
-
setCustomId(customId) {
|
|
20
|
-
this.data.customId = customId;
|
|
21
|
-
return this;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Sets the minimum number of files that must be uploaded (min 0, max 10).
|
|
25
|
-
*/
|
|
26
|
-
setMinValues(minValues) {
|
|
27
|
-
this.data.minValues = minValues;
|
|
28
|
-
return this;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Sets the maximum number of files that can be uploaded (max 10).
|
|
32
|
-
*/
|
|
33
|
-
setMaxValues(maxValues) {
|
|
34
|
-
this.data.maxValues = maxValues;
|
|
35
|
-
return this;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Sets whether files are required before submitting the modal.
|
|
39
|
-
*/
|
|
40
|
-
setRequired(required) {
|
|
41
|
-
this.data.required = required;
|
|
42
|
-
return this;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Serialises the builder into an API compatible file upload component payload.
|
|
46
|
-
*/
|
|
5
|
+
constructor(data = {}) { this.data = { ...data }; }
|
|
6
|
+
setCustomId(customId) { this.data.customId = customId; return this; }
|
|
7
|
+
setMinValues(minValues) { this.data.minValues = minValues; return this; }
|
|
8
|
+
setMaxValues(maxValues) { this.data.maxValues = maxValues; return this; }
|
|
9
|
+
setRequired(required) { this.data.required = required; return this; }
|
|
47
10
|
toJSON() {
|
|
48
|
-
|
|
49
|
-
|
|
11
|
+
const customId = assertDefined('FileUploadBuilder', 'custom_id', this.data.customId);
|
|
12
|
+
assertStringLength('FileUploadBuilder', 'custom_id', customId, 1, 100);
|
|
13
|
+
if (this.data.minValues !== undefined)
|
|
14
|
+
assertRange('FileUploadBuilder', 'min_values', this.data.minValues, 0, 10);
|
|
15
|
+
if (this.data.maxValues !== undefined)
|
|
16
|
+
assertRange('FileUploadBuilder', 'max_values', this.data.maxValues, 1, 10);
|
|
17
|
+
if (this.data.minValues !== undefined && this.data.maxValues !== undefined && this.data.minValues > this.data.maxValues) {
|
|
18
|
+
throw new ValidationError('FileUploadBuilder', 'min_values', 'cannot be greater than max_values');
|
|
50
19
|
}
|
|
51
|
-
return {
|
|
52
|
-
type: ComponentType.FileUpload,
|
|
53
|
-
custom_id: this.data.customId,
|
|
54
|
-
min_values: this.data.minValues,
|
|
55
|
-
max_values: this.data.maxValues,
|
|
56
|
-
required: this.data.required,
|
|
57
|
-
};
|
|
20
|
+
return { type: ComponentType.FileUpload, custom_id: customId, min_values: this.data.minValues, max_values: this.data.maxValues, required: this.data.required };
|
|
58
21
|
}
|
|
59
22
|
}
|
|
@@ -1,35 +1,16 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import type
|
|
3
|
-
/** Values accepted when composing label components. */
|
|
1
|
+
import { type APIComponentInLabel, type APILabelComponent } from 'discord-api-types/v10';
|
|
2
|
+
import { type JSONEncodable } from './shared.js';
|
|
4
3
|
export type LabelComponentLike = JSONEncodable<APIComponentInLabel> | APIComponentInLabel;
|
|
5
|
-
/** Shape describing initial label data accepted by the builder. */
|
|
6
4
|
export type LabelBuilderData = {
|
|
7
5
|
label?: string;
|
|
8
6
|
description?: string;
|
|
9
7
|
component?: LabelComponentLike;
|
|
10
8
|
};
|
|
11
|
-
/** Builder for Discord label components used in modals. */
|
|
12
9
|
export declare class LabelBuilder implements JSONEncodable<APILabelComponent> {
|
|
13
|
-
private data;
|
|
14
|
-
/**
|
|
15
|
-
* Creates a new label builder with optional seed data.
|
|
16
|
-
*/
|
|
10
|
+
private readonly data;
|
|
17
11
|
constructor(data?: LabelBuilderData);
|
|
18
|
-
/**
|
|
19
|
-
* Sets the label text (max 45 characters).
|
|
20
|
-
*/
|
|
21
12
|
setLabel(label: string): this;
|
|
22
|
-
/**
|
|
23
|
-
* Sets the optional description text (max 100 characters).
|
|
24
|
-
*/
|
|
25
13
|
setDescription(description: string): this;
|
|
26
|
-
/**
|
|
27
|
-
* Sets the component within the label.
|
|
28
|
-
* Can be a TextInput, SelectMenu, or FileUpload component.
|
|
29
|
-
*/
|
|
30
14
|
setComponent(component: LabelComponentLike): this;
|
|
31
|
-
/**
|
|
32
|
-
* Serialises the builder into an API compatible label component payload.
|
|
33
|
-
*/
|
|
34
15
|
toJSON(): APILabelComponent;
|
|
35
16
|
}
|
|
@@ -1,55 +1,21 @@
|
|
|
1
|
-
import { ComponentType
|
|
2
|
-
import { resolveJSONEncodable } from
|
|
3
|
-
|
|
1
|
+
import { ComponentType } from 'discord-api-types/v10';
|
|
2
|
+
import { resolveJSONEncodable } from './shared.js';
|
|
3
|
+
import { ValidationError, assertDefined, assertStringLength } from '../types/validation.js';
|
|
4
4
|
export class LabelBuilder {
|
|
5
5
|
data;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
this.data = {
|
|
11
|
-
label: data.label,
|
|
12
|
-
description: data.description,
|
|
13
|
-
component: data.component,
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Sets the label text (max 45 characters).
|
|
18
|
-
*/
|
|
19
|
-
setLabel(label) {
|
|
20
|
-
this.data.label = label;
|
|
21
|
-
return this;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Sets the optional description text (max 100 characters).
|
|
25
|
-
*/
|
|
26
|
-
setDescription(description) {
|
|
27
|
-
this.data.description = description;
|
|
28
|
-
return this;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Sets the component within the label.
|
|
32
|
-
* Can be a TextInput, SelectMenu, or FileUpload component.
|
|
33
|
-
*/
|
|
34
|
-
setComponent(component) {
|
|
35
|
-
this.data.component = component;
|
|
36
|
-
return this;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Serialises the builder into an API compatible label component payload.
|
|
40
|
-
*/
|
|
6
|
+
constructor(data = {}) { this.data = { ...data }; }
|
|
7
|
+
setLabel(label) { this.data.label = label; return this; }
|
|
8
|
+
setDescription(description) { this.data.description = description; return this; }
|
|
9
|
+
setComponent(component) { this.data.component = component; return this; }
|
|
41
10
|
toJSON() {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
description: this.data.description,
|
|
52
|
-
component: resolveJSONEncodable(this.data.component),
|
|
53
|
-
};
|
|
11
|
+
const label = assertDefined('LabelBuilder', 'label', this.data.label);
|
|
12
|
+
const component = assertDefined('LabelBuilder', 'component', this.data.component);
|
|
13
|
+
assertStringLength('LabelBuilder', 'label', label, 1, 45);
|
|
14
|
+
if (this.data.description)
|
|
15
|
+
assertStringLength('LabelBuilder', 'description', this.data.description, 1, 100);
|
|
16
|
+
const resolved = resolveJSONEncodable(component);
|
|
17
|
+
if (!('type' in resolved))
|
|
18
|
+
throw new ValidationError('LabelBuilder', 'component', 'must contain a valid component payload');
|
|
19
|
+
return { type: ComponentType.Label, label, description: this.data.description, component: resolved };
|
|
54
20
|
}
|
|
55
21
|
}
|
|
@@ -1,40 +1,16 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type
|
|
3
|
-
|
|
4
|
-
export type ModalComponentLike = JSONEncodable<APIModalInteractionResponseCallbackComponent> | APIModalInteractionResponseCallbackComponent | JSONEncodable<APITextInputComponent> | APITextInputComponent | JSONEncodable<APIComponentInLabel> | APIComponentInLabel | JSONEncodable<APISelectMenuComponent> | APISelectMenuComponent | JSONEncodable<APIFileUploadComponent> | APIFileUploadComponent;
|
|
5
|
-
/** Shape describing initial modal data accepted by the builder. */
|
|
1
|
+
import { type APIModalInteractionResponseCallbackData, type APIModalInteractionResponseCallbackComponent } from 'discord-api-types/v10';
|
|
2
|
+
import { type JSONEncodable } from './shared.js';
|
|
3
|
+
export type ModalComponentLike = JSONEncodable<APIModalInteractionResponseCallbackComponent> | APIModalInteractionResponseCallbackComponent;
|
|
6
4
|
export type ModalBuilderData = {
|
|
7
5
|
customId?: string;
|
|
8
6
|
title?: string;
|
|
9
7
|
components?: Iterable<ModalComponentLike>;
|
|
10
8
|
};
|
|
11
|
-
/** Builder for Discord modal interaction responses. */
|
|
12
9
|
export declare class ModalBuilder implements JSONEncodable<APIModalInteractionResponseCallbackData> {
|
|
13
|
-
private
|
|
14
|
-
private title?;
|
|
15
|
-
private components;
|
|
16
|
-
/**
|
|
17
|
-
* Creates a new modal builder with optional seed data.
|
|
18
|
-
*/
|
|
10
|
+
private readonly data;
|
|
19
11
|
constructor(data?: ModalBuilderData);
|
|
20
|
-
/**
|
|
21
|
-
* Sets the custom identifier returned when the modal is submitted.
|
|
22
|
-
*/
|
|
23
12
|
setCustomId(customId: string): this;
|
|
24
|
-
/**
|
|
25
|
-
* Sets the modal title displayed to users.
|
|
26
|
-
*/
|
|
27
13
|
setTitle(title: string): this;
|
|
28
|
-
/**
|
|
29
|
-
* Appends component rows to the modal body.
|
|
30
|
-
*/
|
|
31
14
|
addComponents(...components: ModalComponentLike[]): this;
|
|
32
|
-
/**
|
|
33
|
-
* Replaces all component rows for the modal.
|
|
34
|
-
*/
|
|
35
|
-
setComponents(components: Iterable<ModalComponentLike>): this;
|
|
36
|
-
/**
|
|
37
|
-
* Serialises the builder into an API compatible modal response payload.
|
|
38
|
-
*/
|
|
39
15
|
toJSON(): APIModalInteractionResponseCallbackData;
|
|
40
16
|
}
|
|
@@ -1,92 +1,31 @@
|
|
|
1
|
-
import { ComponentType } from
|
|
2
|
-
import { resolveJSONEncodable } from
|
|
3
|
-
|
|
1
|
+
import { ComponentType } from 'discord-api-types/v10';
|
|
2
|
+
import { resolveJSONEncodable } from './shared.js';
|
|
3
|
+
import { ValidationError, assertDefined, assertStringLength } from '../types/validation.js';
|
|
4
4
|
export class ModalBuilder {
|
|
5
|
-
|
|
6
|
-
title;
|
|
7
|
-
components;
|
|
8
|
-
/**
|
|
9
|
-
* Creates a new modal builder with optional seed data.
|
|
10
|
-
*/
|
|
5
|
+
data;
|
|
11
6
|
constructor(data = {}) {
|
|
12
|
-
this.
|
|
13
|
-
this.title = data.title;
|
|
14
|
-
this.components = data.components ? Array.from(data.components) : [];
|
|
7
|
+
this.data = { ...data, components: data.components ? Array.from(data.components) : [] };
|
|
15
8
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
*/
|
|
19
|
-
setCustomId(customId) {
|
|
20
|
-
this.customId = customId;
|
|
21
|
-
return this;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Sets the modal title displayed to users.
|
|
25
|
-
*/
|
|
26
|
-
setTitle(title) {
|
|
27
|
-
this.title = title;
|
|
28
|
-
return this;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Appends component rows to the modal body.
|
|
32
|
-
*/
|
|
9
|
+
setCustomId(customId) { this.data.customId = customId; return this; }
|
|
10
|
+
setTitle(title) { this.data.title = title; return this; }
|
|
33
11
|
addComponents(...components) {
|
|
34
|
-
this.components.
|
|
35
|
-
return this;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Replaces all component rows for the modal.
|
|
39
|
-
*/
|
|
40
|
-
setComponents(components) {
|
|
41
|
-
this.components = Array.from(components);
|
|
12
|
+
this.data.components = [...(this.data.components ?? []), ...components];
|
|
42
13
|
return this;
|
|
43
14
|
}
|
|
44
|
-
/**
|
|
45
|
-
* Serialises the builder into an API compatible modal response payload.
|
|
46
|
-
*/
|
|
47
15
|
toJSON() {
|
|
48
|
-
|
|
49
|
-
|
|
16
|
+
const customId = assertDefined('ModalBuilder', 'custom_id', this.data.customId);
|
|
17
|
+
const title = assertDefined('ModalBuilder', 'title', this.data.title);
|
|
18
|
+
assertStringLength('ModalBuilder', 'custom_id', customId, 1, 100);
|
|
19
|
+
assertStringLength('ModalBuilder', 'title', title, 1, 45);
|
|
20
|
+
const components = Array.from(this.data.components ?? []).map((c) => resolveJSONEncodable(c));
|
|
21
|
+
if (components.length === 0 || components.length > 5) {
|
|
22
|
+
throw new ValidationError('ModalBuilder', 'components', 'must contain between 1 and 5 components');
|
|
50
23
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (this.components.length === 0) {
|
|
55
|
-
throw new Error("[ModalBuilder] at least one component is required.");
|
|
56
|
-
}
|
|
57
|
-
if (this.components.length > 5) {
|
|
58
|
-
throw new Error("[ModalBuilder] no more than 5 components can be provided.");
|
|
59
|
-
}
|
|
60
|
-
// Auto-wrap components that need wrapping
|
|
61
|
-
const normalizedComponents = this.components.map((component) => {
|
|
62
|
-
const resolved = resolveJSONEncodable(component);
|
|
63
|
-
if (!resolved ||
|
|
64
|
-
typeof resolved !== "object" ||
|
|
65
|
-
!("type" in resolved)) {
|
|
66
|
-
return resolved;
|
|
24
|
+
for (const component of components) {
|
|
25
|
+
if (component.type !== ComponentType.ActionRow && component.type !== ComponentType.Label) {
|
|
26
|
+
throw new ValidationError('ModalBuilder', 'components', `invalid modal top-level component type ${component.type}`);
|
|
67
27
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
// (ActionRows in modals are deprecated, but still supported for backwards compatibility)
|
|
71
|
-
if (componentType === ComponentType.TextInput ||
|
|
72
|
-
componentType === ComponentType.StringSelect ||
|
|
73
|
-
componentType === ComponentType.UserSelect ||
|
|
74
|
-
componentType === ComponentType.RoleSelect ||
|
|
75
|
-
componentType === ComponentType.MentionableSelect ||
|
|
76
|
-
componentType === ComponentType.ChannelSelect ||
|
|
77
|
-
componentType === ComponentType.FileUpload ||
|
|
78
|
-
componentType === ComponentType.Label) {
|
|
79
|
-
return {
|
|
80
|
-
type: ComponentType.ActionRow,
|
|
81
|
-
components: [resolved],
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
return resolved;
|
|
85
|
-
});
|
|
86
|
-
return {
|
|
87
|
-
custom_id: this.customId,
|
|
88
|
-
title: this.title,
|
|
89
|
-
components: normalizedComponents,
|
|
90
|
-
};
|
|
28
|
+
}
|
|
29
|
+
return { custom_id: customId, title, components };
|
|
91
30
|
}
|
|
92
31
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { SelectMenuDefaultValueType, type APIChannelSelectComponent, type APISelectMenuDefaultValue
|
|
2
|
-
import type { JSONEncodable } from
|
|
3
|
-
/** Shape describing initial modal channel select menu data accepted by the builder. */
|
|
1
|
+
import { ChannelType, SelectMenuDefaultValueType, type APIChannelSelectComponent, type APISelectMenuDefaultValue } from 'discord-api-types/v10';
|
|
2
|
+
import type { JSONEncodable } from './shared.js';
|
|
4
3
|
export type ModalChannelSelectMenuBuilderData = {
|
|
5
4
|
customId?: string;
|
|
6
5
|
placeholder?: string;
|
|
@@ -11,51 +10,16 @@ export type ModalChannelSelectMenuBuilderData = {
|
|
|
11
10
|
channelTypes?: ChannelType[];
|
|
12
11
|
defaultValues?: APISelectMenuDefaultValue<SelectMenuDefaultValueType.Channel>[];
|
|
13
12
|
};
|
|
14
|
-
/** Builder for Discord channel select menu components in modals. */
|
|
15
13
|
export declare class ModalChannelSelectMenuBuilder implements JSONEncodable<APIChannelSelectComponent> {
|
|
16
|
-
private data;
|
|
17
|
-
/**
|
|
18
|
-
* Creates a new modal channel select menu builder with optional seed data.
|
|
19
|
-
*/
|
|
14
|
+
private readonly data;
|
|
20
15
|
constructor(data?: ModalChannelSelectMenuBuilderData);
|
|
21
|
-
/**
|
|
22
|
-
* Sets the unique custom identifier for the select menu interaction.
|
|
23
|
-
*/
|
|
24
16
|
setCustomId(customId: string): this;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
setPlaceholder(placeholder: string | null | undefined): this;
|
|
29
|
-
/**
|
|
30
|
-
* Sets the minimum number of channels that must be selected.
|
|
31
|
-
*/
|
|
32
|
-
setMinValues(minValues: number | null | undefined): this;
|
|
33
|
-
/**
|
|
34
|
-
* Sets the maximum number of channels that can be selected.
|
|
35
|
-
*/
|
|
36
|
-
setMaxValues(maxValues: number | null | undefined): this;
|
|
37
|
-
/**
|
|
38
|
-
* Toggles whether the select menu is disabled.
|
|
39
|
-
*/
|
|
17
|
+
setPlaceholder(placeholder?: string): this;
|
|
18
|
+
setMinValues(minValues?: number): this;
|
|
19
|
+
setMaxValues(maxValues?: number): this;
|
|
40
20
|
setDisabled(disabled: boolean): this;
|
|
41
|
-
/**
|
|
42
|
-
* Marks the select menu as required in the modal.
|
|
43
|
-
*/
|
|
44
21
|
setRequired(required: boolean): this;
|
|
45
|
-
|
|
46
|
-
* Replaces the default channel selections displayed when the menu renders.
|
|
47
|
-
*/
|
|
22
|
+
setChannelTypes(channelTypes: ChannelType[]): this;
|
|
48
23
|
setDefaultValues(defaultValues: Iterable<APISelectMenuDefaultValue<SelectMenuDefaultValueType.Channel>>): this;
|
|
49
|
-
/**
|
|
50
|
-
* Sets the channel types allowed in the select menu.
|
|
51
|
-
*/
|
|
52
|
-
setChannelTypes(...channelTypes: ChannelType[]): this;
|
|
53
|
-
/**
|
|
54
|
-
* Adds channel types allowed in the select menu.
|
|
55
|
-
*/
|
|
56
|
-
addChannelTypes(...channelTypes: ChannelType[]): this;
|
|
57
|
-
/**
|
|
58
|
-
* Serialises the builder into an API compatible channel select menu payload.
|
|
59
|
-
*/
|
|
60
24
|
toJSON(): APIChannelSelectComponent;
|
|
61
25
|
}
|