@ovencord/builders 1.11.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/LICENSE +191 -0
- package/README.md +92 -0
- package/package.json +70 -0
- package/src/Assertions.ts +15 -0
- package/src/components/ActionRow.ts +346 -0
- package/src/components/Assertions.ts +190 -0
- package/src/components/Component.ts +47 -0
- package/src/components/Components.ts +275 -0
- package/src/components/button/Button.ts +34 -0
- package/src/components/button/CustomIdButton.ts +74 -0
- package/src/components/button/LinkButton.ts +39 -0
- package/src/components/button/PremiumButton.ts +26 -0
- package/src/components/button/mixins/EmojiOrLabelButtonMixin.ts +52 -0
- package/src/components/fileUpload/Assertions.ts +12 -0
- package/src/components/fileUpload/FileUpload.ts +109 -0
- package/src/components/label/Assertions.ts +28 -0
- package/src/components/label/Label.ts +215 -0
- package/src/components/selectMenu/BaseSelectMenu.ts +89 -0
- package/src/components/selectMenu/ChannelSelectMenu.ts +115 -0
- package/src/components/selectMenu/MentionableSelectMenu.ts +126 -0
- package/src/components/selectMenu/RoleSelectMenu.ts +89 -0
- package/src/components/selectMenu/StringSelectMenu.ts +165 -0
- package/src/components/selectMenu/StringSelectMenuOption.ts +113 -0
- package/src/components/selectMenu/UserSelectMenu.ts +89 -0
- package/src/components/textInput/Assertions.ts +15 -0
- package/src/components/textInput/TextInput.ts +154 -0
- package/src/components/v2/Assertions.ts +82 -0
- package/src/components/v2/Container.ts +254 -0
- package/src/components/v2/File.ts +81 -0
- package/src/components/v2/MediaGallery.ts +128 -0
- package/src/components/v2/MediaGalleryItem.ts +85 -0
- package/src/components/v2/Section.ts +266 -0
- package/src/components/v2/Separator.ts +82 -0
- package/src/components/v2/TextDisplay.ts +63 -0
- package/src/components/v2/Thumbnail.ts +100 -0
- package/src/index.ts +109 -0
- package/src/interactions/commands/Command.ts +87 -0
- package/src/interactions/commands/SharedName.ts +68 -0
- package/src/interactions/commands/SharedNameAndDescription.ts +69 -0
- package/src/interactions/commands/chatInput/Assertions.ts +180 -0
- package/src/interactions/commands/chatInput/ChatInputCommand.ts +40 -0
- package/src/interactions/commands/chatInput/ChatInputCommandSubcommands.ts +117 -0
- package/src/interactions/commands/chatInput/mixins/ApplicationCommandNumericOptionMinMaxValueMixin.ts +52 -0
- package/src/interactions/commands/chatInput/mixins/ApplicationCommandOptionChannelTypesMixin.ts +57 -0
- package/src/interactions/commands/chatInput/mixins/ApplicationCommandOptionWithAutocompleteMixin.ts +32 -0
- package/src/interactions/commands/chatInput/mixins/ApplicationCommandOptionWithChoicesMixin.ts +43 -0
- package/src/interactions/commands/chatInput/mixins/SharedChatInputCommandOptions.ts +204 -0
- package/src/interactions/commands/chatInput/mixins/SharedSubcommands.ts +61 -0
- package/src/interactions/commands/chatInput/options/ApplicationCommandOptionBase.ts +66 -0
- package/src/interactions/commands/chatInput/options/attachment.ts +20 -0
- package/src/interactions/commands/chatInput/options/boolean.ts +20 -0
- package/src/interactions/commands/chatInput/options/channel.ts +28 -0
- package/src/interactions/commands/chatInput/options/integer.ts +34 -0
- package/src/interactions/commands/chatInput/options/mentionable.ts +20 -0
- package/src/interactions/commands/chatInput/options/number.ts +34 -0
- package/src/interactions/commands/chatInput/options/role.ts +20 -0
- package/src/interactions/commands/chatInput/options/string.ts +71 -0
- package/src/interactions/commands/chatInput/options/user.ts +20 -0
- package/src/interactions/commands/contextMenu/Assertions.ts +31 -0
- package/src/interactions/commands/contextMenu/ContextMenuCommand.ts +42 -0
- package/src/interactions/commands/contextMenu/MessageCommand.ts +19 -0
- package/src/interactions/commands/contextMenu/UserCommand.ts +19 -0
- package/src/interactions/modals/Assertions.ts +27 -0
- package/src/interactions/modals/Modal.ts +158 -0
- package/src/messages/AllowedMentions.ts +193 -0
- package/src/messages/Assertions.ts +148 -0
- package/src/messages/Attachment.ts +209 -0
- package/src/messages/Message.ts +692 -0
- package/src/messages/MessageReference.ts +111 -0
- package/src/messages/embed/Assertions.ts +53 -0
- package/src/messages/embed/Embed.ts +352 -0
- package/src/messages/embed/EmbedAuthor.ts +83 -0
- package/src/messages/embed/EmbedField.ts +67 -0
- package/src/messages/embed/EmbedFooter.ts +65 -0
- package/src/messages/poll/Assertions.ts +20 -0
- package/src/messages/poll/Poll.ts +243 -0
- package/src/messages/poll/PollAnswer.ts +77 -0
- package/src/messages/poll/PollAnswerMedia.ts +38 -0
- package/src/messages/poll/PollMedia.ts +41 -0
- package/src/messages/poll/PollQuestion.ts +20 -0
- package/src/util/ValidationError.ts +21 -0
- package/src/util/normalizeArray.ts +19 -0
- package/src/util/resolveBuilder.ts +40 -0
- package/src/util/validation.ts +58 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ApplicationCommandType, ApplicationIntegrationType, InteractionContextType } from 'discord-api-types/v10';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { localeMapPredicate, memberPermissionsPredicate } from '../../../Assertions.js';
|
|
4
|
+
|
|
5
|
+
const namePredicate = z
|
|
6
|
+
.string()
|
|
7
|
+
.min(1)
|
|
8
|
+
.max(32)
|
|
9
|
+
.refine((val) => val.trim().length > 0, {
|
|
10
|
+
message: 'Must not consist of only whitespace.',
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const contextsPredicate = z.array(z.nativeEnum(InteractionContextType));
|
|
14
|
+
const integrationTypesPredicate = z.array(z.nativeEnum(ApplicationIntegrationType));
|
|
15
|
+
|
|
16
|
+
const baseContextMenuCommandPredicate = z.object({
|
|
17
|
+
contexts: contextsPredicate.optional(),
|
|
18
|
+
default_member_permissions: memberPermissionsPredicate.optional(),
|
|
19
|
+
name: namePredicate,
|
|
20
|
+
name_localizations: localeMapPredicate.optional(),
|
|
21
|
+
integration_types: integrationTypesPredicate.optional(),
|
|
22
|
+
nsfw: z.boolean().optional(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const userCommandPredicate = baseContextMenuCommandPredicate.extend({
|
|
26
|
+
type: z.literal(ApplicationCommandType.User),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export const messageCommandPredicate = baseContextMenuCommandPredicate.extend({
|
|
30
|
+
type: z.literal(ApplicationCommandType.Message),
|
|
31
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ApplicationCommandType, RESTPostAPIContextMenuApplicationCommandsJSONBody } from 'discord-api-types/v10';
|
|
2
|
+
import { Mixin } from 'ts-mixer';
|
|
3
|
+
import { CommandBuilder } from '../Command.js';
|
|
4
|
+
import { SharedName } from '../SharedName.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The type a context menu command can be.
|
|
8
|
+
*/
|
|
9
|
+
export type ContextMenuCommandType = ApplicationCommandType.Message | ApplicationCommandType.User;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A builder that creates API-compatible JSON data for context menu commands.
|
|
13
|
+
*
|
|
14
|
+
* @mixes {@link CommandBuilder}\<{@link discord-api-types/v10#(RESTPostAPIContextMenuApplicationCommandsJSONBody:interface)}\>
|
|
15
|
+
* @mixes {@link SharedName}
|
|
16
|
+
*/
|
|
17
|
+
export abstract class ContextMenuCommandBuilder extends Mixin(
|
|
18
|
+
CommandBuilder<RESTPostAPIContextMenuApplicationCommandsJSONBody>,
|
|
19
|
+
SharedName,
|
|
20
|
+
) {
|
|
21
|
+
/**
|
|
22
|
+
* The API data associated with this context menu command.
|
|
23
|
+
*
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
protected override readonly data: Partial<RESTPostAPIContextMenuApplicationCommandsJSONBody>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Creates a new context menu command.
|
|
30
|
+
*
|
|
31
|
+
* @param data - The API data to create this context menu command with
|
|
32
|
+
*/
|
|
33
|
+
public constructor(data: Partial<RESTPostAPIContextMenuApplicationCommandsJSONBody> = {}) {
|
|
34
|
+
super();
|
|
35
|
+
this.data = structuredClone(data);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* {@inheritDoc CommandBuilder.toJSON}
|
|
40
|
+
*/
|
|
41
|
+
public abstract override toJSON(validationOverride?: boolean): RESTPostAPIContextMenuApplicationCommandsJSONBody;
|
|
42
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ApplicationCommandType, type RESTPostAPIContextMenuApplicationCommandsJSONBody } from 'discord-api-types/v10';
|
|
2
|
+
import { validate } from '../../../util/validation.js';
|
|
3
|
+
import { messageCommandPredicate } from './Assertions.js';
|
|
4
|
+
import { ContextMenuCommandBuilder } from './ContextMenuCommand.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A builder that creates API-compatible JSON data for message context menu commands.
|
|
8
|
+
*/
|
|
9
|
+
export class MessageContextCommandBuilder extends ContextMenuCommandBuilder {
|
|
10
|
+
/**
|
|
11
|
+
* {@inheritDoc CommandBuilder.toJSON}
|
|
12
|
+
*/
|
|
13
|
+
public override toJSON(validationOverride?: boolean): RESTPostAPIContextMenuApplicationCommandsJSONBody {
|
|
14
|
+
const data = { ...structuredClone(this.data), type: ApplicationCommandType.Message };
|
|
15
|
+
validate(messageCommandPredicate, data, validationOverride);
|
|
16
|
+
|
|
17
|
+
return data as RESTPostAPIContextMenuApplicationCommandsJSONBody;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ApplicationCommandType, type RESTPostAPIContextMenuApplicationCommandsJSONBody } from 'discord-api-types/v10';
|
|
2
|
+
import { validate } from '../../../util/validation.js';
|
|
3
|
+
import { userCommandPredicate } from './Assertions.js';
|
|
4
|
+
import { ContextMenuCommandBuilder } from './ContextMenuCommand.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A builder that creates API-compatible JSON data for user context menu commands.
|
|
8
|
+
*/
|
|
9
|
+
export class UserContextCommandBuilder extends ContextMenuCommandBuilder {
|
|
10
|
+
/**
|
|
11
|
+
* {@inheritDoc CommandBuilder.toJSON}
|
|
12
|
+
*/
|
|
13
|
+
public override toJSON(validationOverride?: boolean): RESTPostAPIContextMenuApplicationCommandsJSONBody {
|
|
14
|
+
const data = { ...structuredClone(this.data), type: ApplicationCommandType.User };
|
|
15
|
+
validate(userCommandPredicate, data, validationOverride);
|
|
16
|
+
|
|
17
|
+
return data as RESTPostAPIContextMenuApplicationCommandsJSONBody;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ComponentType } from 'discord-api-types/v10';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { customIdPredicate } from '../../Assertions.js';
|
|
4
|
+
import { labelPredicate } from '../../components/label/Assertions.js';
|
|
5
|
+
import { textDisplayPredicate } from '../../components/v2/Assertions.js';
|
|
6
|
+
|
|
7
|
+
const titlePredicate = z.string().min(1).max(45);
|
|
8
|
+
|
|
9
|
+
export const modalPredicate = z.object({
|
|
10
|
+
title: titlePredicate,
|
|
11
|
+
custom_id: customIdPredicate,
|
|
12
|
+
components: z
|
|
13
|
+
.union([
|
|
14
|
+
z.object({
|
|
15
|
+
type: z.literal(ComponentType.ActionRow),
|
|
16
|
+
components: z
|
|
17
|
+
.object({ type: z.literal(ComponentType.TextInput) })
|
|
18
|
+
.array()
|
|
19
|
+
.length(1),
|
|
20
|
+
}),
|
|
21
|
+
labelPredicate,
|
|
22
|
+
textDisplayPredicate,
|
|
23
|
+
])
|
|
24
|
+
.array()
|
|
25
|
+
.min(1)
|
|
26
|
+
.max(5),
|
|
27
|
+
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { JSONEncodable } from '@ovencord/util';
|
|
2
|
+
import type {
|
|
3
|
+
APILabelComponent,
|
|
4
|
+
APIModalInteractionResponseCallbackData,
|
|
5
|
+
APITextDisplayComponent,
|
|
6
|
+
} from 'discord-api-types/v10';
|
|
7
|
+
import type { ActionRowBuilder } from '../../components/ActionRow.js';
|
|
8
|
+
import type { AnyModalComponentBuilder } from '../../components/Components.js';
|
|
9
|
+
import { createComponentBuilder } from '../../components/Components.js';
|
|
10
|
+
import { LabelBuilder } from '../../components/label/Label.js';
|
|
11
|
+
import { TextDisplayBuilder } from '../../components/v2/TextDisplay.js';
|
|
12
|
+
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
|
|
13
|
+
import { resolveBuilder } from '../../util/resolveBuilder.js';
|
|
14
|
+
import { validate } from '../../util/validation.js';
|
|
15
|
+
import { modalPredicate } from './Assertions.js';
|
|
16
|
+
|
|
17
|
+
export interface ModalBuilderData extends Partial<Omit<APIModalInteractionResponseCallbackData, 'components'>> {
|
|
18
|
+
components: (ActionRowBuilder | AnyModalComponentBuilder)[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A builder that creates API-compatible JSON data for modals.
|
|
23
|
+
*/
|
|
24
|
+
export class ModalBuilder implements JSONEncodable<APIModalInteractionResponseCallbackData> {
|
|
25
|
+
/**
|
|
26
|
+
* The API data associated with this modal.
|
|
27
|
+
*/
|
|
28
|
+
private readonly data: ModalBuilderData;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The components within this modal.
|
|
32
|
+
*/
|
|
33
|
+
public get components(): readonly (ActionRowBuilder | AnyModalComponentBuilder)[] {
|
|
34
|
+
return this.data.components;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Creates a new modal.
|
|
39
|
+
*
|
|
40
|
+
* @param data - The API data to create this modal with
|
|
41
|
+
*/
|
|
42
|
+
public constructor(data: Partial<APIModalInteractionResponseCallbackData> = {}) {
|
|
43
|
+
const { components = [], ...rest } = data;
|
|
44
|
+
|
|
45
|
+
this.data = {
|
|
46
|
+
...structuredClone(rest),
|
|
47
|
+
components: components.map((component) => createComponentBuilder(component)),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Sets the title of this modal.
|
|
53
|
+
*
|
|
54
|
+
* @param title - The title to use
|
|
55
|
+
*/
|
|
56
|
+
public setTitle(title: string) {
|
|
57
|
+
this.data.title = title;
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Sets the custom id of this modal.
|
|
63
|
+
*
|
|
64
|
+
* @param customId - The custom id to use
|
|
65
|
+
*/
|
|
66
|
+
public setCustomId(customId: string) {
|
|
67
|
+
this.data.custom_id = customId;
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Adds label components to this modal.
|
|
73
|
+
*
|
|
74
|
+
* @param components - The components to add
|
|
75
|
+
*/
|
|
76
|
+
public addLabelComponents(
|
|
77
|
+
...components: RestOrArray<APILabelComponent | LabelBuilder | ((builder: LabelBuilder) => LabelBuilder)>
|
|
78
|
+
) {
|
|
79
|
+
const normalized = normalizeArray(components);
|
|
80
|
+
const resolved = normalized.map((label) => resolveBuilder(label, LabelBuilder));
|
|
81
|
+
|
|
82
|
+
this.data.components.push(...resolved);
|
|
83
|
+
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Adds text display components to this modal.
|
|
89
|
+
*
|
|
90
|
+
* @param components - The components to add
|
|
91
|
+
*/
|
|
92
|
+
public addTextDisplayComponents(
|
|
93
|
+
...components: RestOrArray<
|
|
94
|
+
APITextDisplayComponent | TextDisplayBuilder | ((builder: TextDisplayBuilder) => TextDisplayBuilder)
|
|
95
|
+
>
|
|
96
|
+
) {
|
|
97
|
+
const normalized = normalizeArray(components);
|
|
98
|
+
const resolved = normalized.map((row) => resolveBuilder(row, TextDisplayBuilder));
|
|
99
|
+
|
|
100
|
+
this.data.components.push(...resolved);
|
|
101
|
+
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Removes, replaces, or inserts components for this modal.
|
|
107
|
+
*
|
|
108
|
+
* @remarks
|
|
109
|
+
* This method behaves similarly
|
|
110
|
+
* to {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/splice | Array.prototype.splice()}.
|
|
111
|
+
* The maximum amount of components that can be added is 5.
|
|
112
|
+
*
|
|
113
|
+
* It's useful for modifying and adjusting order of the already-existing components of a modal.
|
|
114
|
+
* @example
|
|
115
|
+
* Remove the first component:
|
|
116
|
+
* ```ts
|
|
117
|
+
* modal.spliceComponents(0, 1);
|
|
118
|
+
* ```
|
|
119
|
+
* @example
|
|
120
|
+
* Remove the first n components:
|
|
121
|
+
* ```ts
|
|
122
|
+
* const n = 4;
|
|
123
|
+
* modal.spliceComponents(0, n);
|
|
124
|
+
* ```
|
|
125
|
+
* @example
|
|
126
|
+
* Remove the last component:
|
|
127
|
+
* ```ts
|
|
128
|
+
* modal.spliceComponents(-1, 1);
|
|
129
|
+
* ```
|
|
130
|
+
* @param index - The index to start at
|
|
131
|
+
* @param deleteCount - The number of components to remove
|
|
132
|
+
* @param components - The replacing components
|
|
133
|
+
*/
|
|
134
|
+
public spliceComponents(index: number, deleteCount: number, ...components: AnyModalComponentBuilder[]): this {
|
|
135
|
+
this.data.components.splice(index, deleteCount, ...components);
|
|
136
|
+
return this;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Serializes this builder to API-compatible JSON data.
|
|
141
|
+
*
|
|
142
|
+
* Note that by disabling validation, there is no guarantee that the resulting object will be valid.
|
|
143
|
+
*
|
|
144
|
+
* @param validationOverride - Force validation to run/not run regardless of your global preference
|
|
145
|
+
*/
|
|
146
|
+
public toJSON(validationOverride?: boolean): APIModalInteractionResponseCallbackData {
|
|
147
|
+
const { components, ...rest } = this.data;
|
|
148
|
+
|
|
149
|
+
const data = {
|
|
150
|
+
...structuredClone(rest),
|
|
151
|
+
components: components.map((component) => component.toJSON(validationOverride)),
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
validate(modalPredicate, data, validationOverride);
|
|
155
|
+
|
|
156
|
+
return data as APIModalInteractionResponseCallbackData;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type { JSONEncodable } from '@ovencord/util';
|
|
2
|
+
import type { AllowedMentionsTypes, APIAllowedMentions, Snowflake } from 'discord-api-types/v10';
|
|
3
|
+
import { normalizeArray, type RestOrArray } from '../util/normalizeArray.js';
|
|
4
|
+
import { validate } from '../util/validation.js';
|
|
5
|
+
import { allowedMentionPredicate } from './Assertions.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A builder that creates API-compatible JSON data for allowed mentions.
|
|
9
|
+
*/
|
|
10
|
+
export class AllowedMentionsBuilder implements JSONEncodable<APIAllowedMentions> {
|
|
11
|
+
/**
|
|
12
|
+
* The API data associated with these allowed mentions.
|
|
13
|
+
*/
|
|
14
|
+
private readonly data: Partial<APIAllowedMentions>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates a new allowed mentions builder.
|
|
18
|
+
*
|
|
19
|
+
* @param data - The API data to create this allowed mentions with
|
|
20
|
+
*/
|
|
21
|
+
public constructor(data: Partial<APIAllowedMentions> = {}) {
|
|
22
|
+
this.data = structuredClone(data);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Sets the types of mentions to parse from the content.
|
|
27
|
+
*
|
|
28
|
+
* @param parse - The types of mentions to parse from the content
|
|
29
|
+
*/
|
|
30
|
+
public setParse(...parse: RestOrArray<AllowedMentionsTypes>): this {
|
|
31
|
+
this.data.parse = normalizeArray(parse);
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Clears the parse mention types.
|
|
37
|
+
*/
|
|
38
|
+
public clearParse(): this {
|
|
39
|
+
this.data.parse = undefined;
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Sets the roles to mention.
|
|
45
|
+
*
|
|
46
|
+
* @param roles - The roles to mention
|
|
47
|
+
*/
|
|
48
|
+
public setRoles(...roles: RestOrArray<Snowflake>): this {
|
|
49
|
+
this.data.roles = normalizeArray(roles);
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Adds roles to mention.
|
|
55
|
+
*
|
|
56
|
+
* @param roles - The roles to mention
|
|
57
|
+
*/
|
|
58
|
+
public addRoles(...roles: RestOrArray<Snowflake>): this {
|
|
59
|
+
this.data.roles ??= [];
|
|
60
|
+
this.data.roles.push(...normalizeArray(roles));
|
|
61
|
+
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Removes, replaces, or inserts roles.
|
|
67
|
+
*
|
|
68
|
+
* @remarks
|
|
69
|
+
* This method behaves similarly
|
|
70
|
+
* to {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/splice | Array.prototype.splice()}.
|
|
71
|
+
*
|
|
72
|
+
* It's useful for modifying and adjusting order of the already-existing roles.
|
|
73
|
+
* @example
|
|
74
|
+
* Remove the first role:
|
|
75
|
+
* ```ts
|
|
76
|
+
* allowedMentions.spliceRoles(0, 1);
|
|
77
|
+
* ```
|
|
78
|
+
* @example
|
|
79
|
+
* Remove the first n roles:
|
|
80
|
+
* ```ts
|
|
81
|
+
* const n = 4;
|
|
82
|
+
* allowedMentions.spliceRoles(0, n);
|
|
83
|
+
* ```
|
|
84
|
+
* @example
|
|
85
|
+
* Remove the last role:
|
|
86
|
+
* ```ts
|
|
87
|
+
* allowedMentions.spliceRoles(-1, 1);
|
|
88
|
+
* ```
|
|
89
|
+
* @param index - The index to start at
|
|
90
|
+
* @param deleteCount - The number of roles to remove
|
|
91
|
+
* @param roles - The replacing role ids
|
|
92
|
+
*/
|
|
93
|
+
public spliceRoles(index: number, deleteCount: number, ...roles: RestOrArray<Snowflake>): this {
|
|
94
|
+
this.data.roles ??= [];
|
|
95
|
+
this.data.roles.splice(index, deleteCount, ...normalizeArray(roles));
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Clears the roles to mention.
|
|
101
|
+
*/
|
|
102
|
+
public clearRoles(): this {
|
|
103
|
+
this.data.roles = undefined;
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Sets the users to mention.
|
|
109
|
+
*
|
|
110
|
+
* @param users - The users to mention
|
|
111
|
+
*/
|
|
112
|
+
public setUsers(...users: RestOrArray<Snowflake>): this {
|
|
113
|
+
this.data.users = normalizeArray(users);
|
|
114
|
+
return this;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Adds users to mention.
|
|
119
|
+
*
|
|
120
|
+
* @param users - The users to mention
|
|
121
|
+
*/
|
|
122
|
+
public addUsers(...users: RestOrArray<Snowflake>): this {
|
|
123
|
+
this.data.users ??= [];
|
|
124
|
+
this.data.users.push(...normalizeArray(users));
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Removes, replaces, or inserts users.
|
|
130
|
+
*
|
|
131
|
+
* @remarks
|
|
132
|
+
* This method behaves similarly
|
|
133
|
+
* to {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/splice | Array.prototype.splice()}.
|
|
134
|
+
*
|
|
135
|
+
* It's useful for modifying and adjusting order of the already-existing users.
|
|
136
|
+
* @example
|
|
137
|
+
* Remove the first user:
|
|
138
|
+
* ```ts
|
|
139
|
+
* allowedMentions.spliceUsers(0, 1);
|
|
140
|
+
* ```
|
|
141
|
+
* @example
|
|
142
|
+
* Remove the first n users:
|
|
143
|
+
* ```ts
|
|
144
|
+
* const n = 4;
|
|
145
|
+
* allowedMentions.spliceUsers(0, n);
|
|
146
|
+
* ```
|
|
147
|
+
* @example
|
|
148
|
+
* Remove the last user:
|
|
149
|
+
* ```ts
|
|
150
|
+
* allowedMentions.spliceUsers(-1, 1);
|
|
151
|
+
* ```
|
|
152
|
+
* @param index - The index to start at
|
|
153
|
+
* @param deleteCount - The number of users to remove
|
|
154
|
+
* @param users - The replacing user ids
|
|
155
|
+
*/
|
|
156
|
+
public spliceUsers(index: number, deleteCount: number, ...users: RestOrArray<Snowflake>): this {
|
|
157
|
+
this.data.users ??= [];
|
|
158
|
+
this.data.users.splice(index, deleteCount, ...normalizeArray(users));
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Clears the users to mention.
|
|
164
|
+
*/
|
|
165
|
+
public clearUsers(): this {
|
|
166
|
+
this.data.users = undefined;
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* For replies, sets whether to mention the author of the message being replied to.
|
|
172
|
+
*
|
|
173
|
+
* @param repliedUser - Whether to mention the author of the message being replied to
|
|
174
|
+
*/
|
|
175
|
+
public setRepliedUser(repliedUser = true): this {
|
|
176
|
+
this.data.replied_user = repliedUser;
|
|
177
|
+
return this;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Serializes this builder to API-compatible JSON data.
|
|
182
|
+
*
|
|
183
|
+
* Note that by disabling validation, there is no guarantee that the resulting object will be valid.
|
|
184
|
+
*
|
|
185
|
+
* @param validationOverride - Force validation to run/not run regardless of your global preference
|
|
186
|
+
*/
|
|
187
|
+
public toJSON(validationOverride?: boolean): APIAllowedMentions {
|
|
188
|
+
const clone = structuredClone(this.data);
|
|
189
|
+
validate(allowedMentionPredicate, clone, validationOverride);
|
|
190
|
+
|
|
191
|
+
return clone as APIAllowedMentions;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { AllowedMentionsTypes, ComponentType, MessageFlags, MessageReferenceType } from 'discord-api-types/v10';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { snowflakePredicate } from '../Assertions.js';
|
|
5
|
+
import { embedPredicate } from './embed/Assertions.js';
|
|
6
|
+
import { pollPredicate } from './poll/Assertions.js';
|
|
7
|
+
|
|
8
|
+
const fileKeyRegex = /^files\[(?<placeholder>\d+?)]$/;
|
|
9
|
+
|
|
10
|
+
export const rawFilePredicate = z.object({
|
|
11
|
+
data: z.union([z.instanceof(Buffer), z.instanceof(Uint8Array), z.string()]),
|
|
12
|
+
name: z.string().min(1),
|
|
13
|
+
contentType: z.string().optional(),
|
|
14
|
+
key: z.string().regex(fileKeyRegex).optional(),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const attachmentPredicate = z.object({
|
|
18
|
+
// As a string it only makes sense for edits when we do have an attachment snowflake
|
|
19
|
+
id: z.union([snowflakePredicate, z.number()]),
|
|
20
|
+
description: z.string().max(1_024).optional(),
|
|
21
|
+
duration_secs: z
|
|
22
|
+
.number()
|
|
23
|
+
.max(2 ** 31 - 1)
|
|
24
|
+
.optional(),
|
|
25
|
+
filename: z.string().max(1_024).optional(),
|
|
26
|
+
title: z.string().max(1_024).optional(),
|
|
27
|
+
waveform: z.string().max(400).optional(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const allowedMentionPredicate = z
|
|
31
|
+
.object({
|
|
32
|
+
parse: z.nativeEnum(AllowedMentionsTypes).array().optional(),
|
|
33
|
+
roles: z.string().array().max(100).optional(),
|
|
34
|
+
users: z.string().array().max(100).optional(),
|
|
35
|
+
replied_user: z.boolean().optional(),
|
|
36
|
+
})
|
|
37
|
+
.refine(
|
|
38
|
+
(data) =>
|
|
39
|
+
!(
|
|
40
|
+
(data.parse?.includes(AllowedMentionsTypes.User) && data.users?.length) ||
|
|
41
|
+
(data.parse?.includes(AllowedMentionsTypes.Role) && data.roles?.length)
|
|
42
|
+
),
|
|
43
|
+
{
|
|
44
|
+
message:
|
|
45
|
+
'Cannot specify both parse: ["users"] and non-empty users array, or parse: ["roles"] and non-empty roles array. These are mutually exclusive',
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
export const messageReferencePredicate = z.object({
|
|
50
|
+
channel_id: z.string().optional(),
|
|
51
|
+
fail_if_not_exists: z.boolean().optional(),
|
|
52
|
+
guild_id: z.string().optional(),
|
|
53
|
+
message_id: z.string(),
|
|
54
|
+
type: z.nativeEnum(MessageReferenceType).optional(),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const baseMessagePredicate = z.object({
|
|
58
|
+
nonce: z.union([z.string().max(25), z.number()]).optional(),
|
|
59
|
+
tts: z.boolean().optional(),
|
|
60
|
+
allowed_mentions: allowedMentionPredicate.optional(),
|
|
61
|
+
message_reference: messageReferencePredicate.optional(),
|
|
62
|
+
attachments: attachmentPredicate.array().max(10).optional(),
|
|
63
|
+
enforce_nonce: z.boolean().optional(),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const basicActionRowPredicate = z.object({
|
|
67
|
+
type: z.literal(ComponentType.ActionRow),
|
|
68
|
+
components: z
|
|
69
|
+
.object({
|
|
70
|
+
type: z.union([
|
|
71
|
+
z.literal(ComponentType.Button),
|
|
72
|
+
z.literal(ComponentType.ChannelSelect),
|
|
73
|
+
z.literal(ComponentType.MentionableSelect),
|
|
74
|
+
z.literal(ComponentType.RoleSelect),
|
|
75
|
+
z.literal(ComponentType.StringSelect),
|
|
76
|
+
z.literal(ComponentType.UserSelect),
|
|
77
|
+
]),
|
|
78
|
+
})
|
|
79
|
+
.array(),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const messageNoComponentsV2Predicate = baseMessagePredicate
|
|
83
|
+
.extend({
|
|
84
|
+
content: z.string().max(2_000).optional(),
|
|
85
|
+
embeds: embedPredicate.array().max(10).optional(),
|
|
86
|
+
sticker_ids: z.array(z.string()).max(3).optional(),
|
|
87
|
+
poll: pollPredicate.optional(),
|
|
88
|
+
components: basicActionRowPredicate.array().max(5).optional(),
|
|
89
|
+
flags: z
|
|
90
|
+
.number()
|
|
91
|
+
.int()
|
|
92
|
+
.optional()
|
|
93
|
+
.refine((flags) => !flags || (flags & MessageFlags.IsComponentsV2) === 0, {
|
|
94
|
+
message: 'Cannot set content, embeds, stickers, or poll with IsComponentsV2 flag set',
|
|
95
|
+
}),
|
|
96
|
+
})
|
|
97
|
+
.refine(
|
|
98
|
+
(data) =>
|
|
99
|
+
data.content !== undefined ||
|
|
100
|
+
(data.embeds !== undefined && data.embeds.length > 0) ||
|
|
101
|
+
data.poll !== undefined ||
|
|
102
|
+
(data.attachments !== undefined && data.attachments.length > 0) ||
|
|
103
|
+
(data.components !== undefined && data.components.length > 0) ||
|
|
104
|
+
(data.sticker_ids !== undefined && data.sticker_ids.length > 0),
|
|
105
|
+
{ message: 'Messages must have content, embeds, a poll, attachments, components or stickers' },
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const allTopLevelComponentsPredicate = z
|
|
109
|
+
.union([
|
|
110
|
+
basicActionRowPredicate,
|
|
111
|
+
z.object({
|
|
112
|
+
type: z.union([
|
|
113
|
+
// Components v2
|
|
114
|
+
z.literal(ComponentType.Container),
|
|
115
|
+
z.literal(ComponentType.File),
|
|
116
|
+
z.literal(ComponentType.MediaGallery),
|
|
117
|
+
z.literal(ComponentType.Section),
|
|
118
|
+
z.literal(ComponentType.Separator),
|
|
119
|
+
z.literal(ComponentType.TextDisplay),
|
|
120
|
+
z.literal(ComponentType.Thumbnail),
|
|
121
|
+
]),
|
|
122
|
+
}),
|
|
123
|
+
])
|
|
124
|
+
.array()
|
|
125
|
+
.min(1)
|
|
126
|
+
.max(10);
|
|
127
|
+
|
|
128
|
+
const messageComponentsV2Predicate = baseMessagePredicate.extend({
|
|
129
|
+
components: allTopLevelComponentsPredicate,
|
|
130
|
+
flags: z.number().int().refine((flags) => (flags & MessageFlags.IsComponentsV2) === MessageFlags.IsComponentsV2, {
|
|
131
|
+
message: 'Must set IsComponentsV2 flag to use Components V2',
|
|
132
|
+
}),
|
|
133
|
+
// These fields cannot be set
|
|
134
|
+
content: z.string().length(0).nullish(),
|
|
135
|
+
embeds: z.array(z.never()).nullish(),
|
|
136
|
+
sticker_ids: z.array(z.never()).nullish(),
|
|
137
|
+
poll: z.null().optional(),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
export const messagePredicate = z.union([messageNoComponentsV2Predicate, messageComponentsV2Predicate]);
|
|
141
|
+
|
|
142
|
+
// This validator does not assert file.key <-> attachment.id coherence. This is fine, because the builders
|
|
143
|
+
// should effectively guarantee that.
|
|
144
|
+
export const fileBodyMessagePredicate = z.object({
|
|
145
|
+
body: messagePredicate,
|
|
146
|
+
// No min length to support message edits
|
|
147
|
+
files: rawFilePredicate.array().max(10),
|
|
148
|
+
});
|