@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.
Files changed (84) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +92 -0
  3. package/package.json +70 -0
  4. package/src/Assertions.ts +15 -0
  5. package/src/components/ActionRow.ts +346 -0
  6. package/src/components/Assertions.ts +190 -0
  7. package/src/components/Component.ts +47 -0
  8. package/src/components/Components.ts +275 -0
  9. package/src/components/button/Button.ts +34 -0
  10. package/src/components/button/CustomIdButton.ts +74 -0
  11. package/src/components/button/LinkButton.ts +39 -0
  12. package/src/components/button/PremiumButton.ts +26 -0
  13. package/src/components/button/mixins/EmojiOrLabelButtonMixin.ts +52 -0
  14. package/src/components/fileUpload/Assertions.ts +12 -0
  15. package/src/components/fileUpload/FileUpload.ts +109 -0
  16. package/src/components/label/Assertions.ts +28 -0
  17. package/src/components/label/Label.ts +215 -0
  18. package/src/components/selectMenu/BaseSelectMenu.ts +89 -0
  19. package/src/components/selectMenu/ChannelSelectMenu.ts +115 -0
  20. package/src/components/selectMenu/MentionableSelectMenu.ts +126 -0
  21. package/src/components/selectMenu/RoleSelectMenu.ts +89 -0
  22. package/src/components/selectMenu/StringSelectMenu.ts +165 -0
  23. package/src/components/selectMenu/StringSelectMenuOption.ts +113 -0
  24. package/src/components/selectMenu/UserSelectMenu.ts +89 -0
  25. package/src/components/textInput/Assertions.ts +15 -0
  26. package/src/components/textInput/TextInput.ts +154 -0
  27. package/src/components/v2/Assertions.ts +82 -0
  28. package/src/components/v2/Container.ts +254 -0
  29. package/src/components/v2/File.ts +81 -0
  30. package/src/components/v2/MediaGallery.ts +128 -0
  31. package/src/components/v2/MediaGalleryItem.ts +85 -0
  32. package/src/components/v2/Section.ts +266 -0
  33. package/src/components/v2/Separator.ts +82 -0
  34. package/src/components/v2/TextDisplay.ts +63 -0
  35. package/src/components/v2/Thumbnail.ts +100 -0
  36. package/src/index.ts +109 -0
  37. package/src/interactions/commands/Command.ts +87 -0
  38. package/src/interactions/commands/SharedName.ts +68 -0
  39. package/src/interactions/commands/SharedNameAndDescription.ts +69 -0
  40. package/src/interactions/commands/chatInput/Assertions.ts +180 -0
  41. package/src/interactions/commands/chatInput/ChatInputCommand.ts +40 -0
  42. package/src/interactions/commands/chatInput/ChatInputCommandSubcommands.ts +117 -0
  43. package/src/interactions/commands/chatInput/mixins/ApplicationCommandNumericOptionMinMaxValueMixin.ts +52 -0
  44. package/src/interactions/commands/chatInput/mixins/ApplicationCommandOptionChannelTypesMixin.ts +57 -0
  45. package/src/interactions/commands/chatInput/mixins/ApplicationCommandOptionWithAutocompleteMixin.ts +32 -0
  46. package/src/interactions/commands/chatInput/mixins/ApplicationCommandOptionWithChoicesMixin.ts +43 -0
  47. package/src/interactions/commands/chatInput/mixins/SharedChatInputCommandOptions.ts +204 -0
  48. package/src/interactions/commands/chatInput/mixins/SharedSubcommands.ts +61 -0
  49. package/src/interactions/commands/chatInput/options/ApplicationCommandOptionBase.ts +66 -0
  50. package/src/interactions/commands/chatInput/options/attachment.ts +20 -0
  51. package/src/interactions/commands/chatInput/options/boolean.ts +20 -0
  52. package/src/interactions/commands/chatInput/options/channel.ts +28 -0
  53. package/src/interactions/commands/chatInput/options/integer.ts +34 -0
  54. package/src/interactions/commands/chatInput/options/mentionable.ts +20 -0
  55. package/src/interactions/commands/chatInput/options/number.ts +34 -0
  56. package/src/interactions/commands/chatInput/options/role.ts +20 -0
  57. package/src/interactions/commands/chatInput/options/string.ts +71 -0
  58. package/src/interactions/commands/chatInput/options/user.ts +20 -0
  59. package/src/interactions/commands/contextMenu/Assertions.ts +31 -0
  60. package/src/interactions/commands/contextMenu/ContextMenuCommand.ts +42 -0
  61. package/src/interactions/commands/contextMenu/MessageCommand.ts +19 -0
  62. package/src/interactions/commands/contextMenu/UserCommand.ts +19 -0
  63. package/src/interactions/modals/Assertions.ts +27 -0
  64. package/src/interactions/modals/Modal.ts +158 -0
  65. package/src/messages/AllowedMentions.ts +193 -0
  66. package/src/messages/Assertions.ts +148 -0
  67. package/src/messages/Attachment.ts +209 -0
  68. package/src/messages/Message.ts +692 -0
  69. package/src/messages/MessageReference.ts +111 -0
  70. package/src/messages/embed/Assertions.ts +53 -0
  71. package/src/messages/embed/Embed.ts +352 -0
  72. package/src/messages/embed/EmbedAuthor.ts +83 -0
  73. package/src/messages/embed/EmbedField.ts +67 -0
  74. package/src/messages/embed/EmbedFooter.ts +65 -0
  75. package/src/messages/poll/Assertions.ts +20 -0
  76. package/src/messages/poll/Poll.ts +243 -0
  77. package/src/messages/poll/PollAnswer.ts +77 -0
  78. package/src/messages/poll/PollAnswerMedia.ts +38 -0
  79. package/src/messages/poll/PollMedia.ts +41 -0
  80. package/src/messages/poll/PollQuestion.ts +20 -0
  81. package/src/util/ValidationError.ts +21 -0
  82. package/src/util/normalizeArray.ts +19 -0
  83. package/src/util/resolveBuilder.ts +40 -0
  84. 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
+ });