@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,65 @@
|
|
|
1
|
+
import type { JSONEncodable } from '@ovencord/util';
|
|
2
|
+
import type { APIEmbedFooter } from 'discord-api-types/v10';
|
|
3
|
+
import { validate } from '../../util/validation.js';
|
|
4
|
+
import { embedFooterPredicate } from './Assertions.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A builder that creates API-compatible JSON data for the embed footer.
|
|
8
|
+
*/
|
|
9
|
+
export class EmbedFooterBuilder implements JSONEncodable<APIEmbedFooter> {
|
|
10
|
+
/**
|
|
11
|
+
* The API data associated with this embed footer.
|
|
12
|
+
*/
|
|
13
|
+
private readonly data: Partial<APIEmbedFooter>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new embed footer.
|
|
17
|
+
*
|
|
18
|
+
* @param data - The API data to create this embed footer with
|
|
19
|
+
*/
|
|
20
|
+
public constructor(data: Partial<APIEmbedFooter> = {}) {
|
|
21
|
+
this.data = structuredClone(data);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Sets the text for this embed footer.
|
|
26
|
+
*
|
|
27
|
+
* @param text - The text to use
|
|
28
|
+
*/
|
|
29
|
+
public setText(text: string): this {
|
|
30
|
+
this.data.text = text;
|
|
31
|
+
return this;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Sets the url for this embed footer.
|
|
36
|
+
*
|
|
37
|
+
* @param url - The url to use
|
|
38
|
+
*/
|
|
39
|
+
public setIconURL(url: string): this {
|
|
40
|
+
this.data.icon_url = url;
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Clears the icon URL for this embed footer.
|
|
46
|
+
*/
|
|
47
|
+
public clearIconURL(): this {
|
|
48
|
+
this.data.icon_url = undefined;
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Serializes this builder to API-compatible JSON data.
|
|
54
|
+
*
|
|
55
|
+
* Note that by disabling validation, there is no guarantee that the resulting object will be valid.
|
|
56
|
+
*
|
|
57
|
+
* @param validationOverride - Force validation to run/not run regardless of your global preference
|
|
58
|
+
*/
|
|
59
|
+
public toJSON(validationOverride?: boolean): APIEmbedFooter {
|
|
60
|
+
const clone = structuredClone(this.data);
|
|
61
|
+
validate(embedFooterPredicate, clone, validationOverride);
|
|
62
|
+
|
|
63
|
+
return clone as APIEmbedFooter;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { PollLayoutType } from 'discord-api-types/v10';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { emojiPredicate } from '../../components/Assertions';
|
|
4
|
+
|
|
5
|
+
export const pollQuestionPredicate = z.object({ text: z.string().min(1).max(300) });
|
|
6
|
+
|
|
7
|
+
export const pollAnswerMediaPredicate = z.object({
|
|
8
|
+
text: z.string().min(1).max(55),
|
|
9
|
+
emoji: emojiPredicate.optional(),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export const pollAnswerPredicate = z.object({ poll_media: pollAnswerMediaPredicate });
|
|
13
|
+
|
|
14
|
+
export const pollPredicate = z.object({
|
|
15
|
+
question: pollQuestionPredicate,
|
|
16
|
+
answers: z.array(pollAnswerPredicate).min(1).max(10),
|
|
17
|
+
duration: z.number().min(1).max(768).optional(),
|
|
18
|
+
allow_multiselect: z.boolean().optional(),
|
|
19
|
+
layout_type: z.nativeEnum(PollLayoutType).optional(),
|
|
20
|
+
});
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import type { JSONEncodable } from '@ovencord/util';
|
|
2
|
+
import type { RESTAPIPoll, APIPollMedia, PollLayoutType, APIPollAnswer } from 'discord-api-types/v10';
|
|
3
|
+
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
|
|
4
|
+
import { resolveBuilder } from '../../util/resolveBuilder.js';
|
|
5
|
+
import { validate } from '../../util/validation.js';
|
|
6
|
+
import { pollPredicate } from './Assertions';
|
|
7
|
+
import { PollAnswerBuilder } from './PollAnswer.js';
|
|
8
|
+
import { PollQuestionBuilder } from './PollQuestion.js';
|
|
9
|
+
|
|
10
|
+
export interface PollData extends Omit<RESTAPIPoll, 'answers' | 'question'> {
|
|
11
|
+
answers: PollAnswerBuilder[];
|
|
12
|
+
question: PollQuestionBuilder;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A builder that creates API-compatible JSON data for polls.
|
|
17
|
+
*/
|
|
18
|
+
export class PollBuilder implements JSONEncodable<RESTAPIPoll> {
|
|
19
|
+
/**
|
|
20
|
+
* The API data associated with this poll.
|
|
21
|
+
*/
|
|
22
|
+
private readonly data: PollData;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Gets the answers of this poll.
|
|
26
|
+
*/
|
|
27
|
+
public get answers(): readonly PollAnswerBuilder[] {
|
|
28
|
+
return this.data.answers;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Creates a new poll.
|
|
33
|
+
*
|
|
34
|
+
* @param data - The API data to create this poll with
|
|
35
|
+
*/
|
|
36
|
+
public constructor(data: Partial<RESTAPIPoll> = {}) {
|
|
37
|
+
const { question, answers = [], ...rest } = data;
|
|
38
|
+
|
|
39
|
+
this.data = {
|
|
40
|
+
...structuredClone(rest),
|
|
41
|
+
question: new PollQuestionBuilder(question),
|
|
42
|
+
answers: answers.map((answer) => new PollAnswerBuilder(answer)),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Appends answers to the poll.
|
|
48
|
+
*
|
|
49
|
+
* @remarks
|
|
50
|
+
* This method accepts either an array of answers or a variable number of answer parameters.
|
|
51
|
+
* The maximum amount of answers that can be added is 10.
|
|
52
|
+
* @example
|
|
53
|
+
* Using an array:
|
|
54
|
+
* ```ts
|
|
55
|
+
* const answers: APIPollMedia[] = ...;
|
|
56
|
+
* const poll = new PollBuilder()
|
|
57
|
+
* .addAnswers(answers);
|
|
58
|
+
* ```
|
|
59
|
+
* @example
|
|
60
|
+
* Using rest parameters (variadic):
|
|
61
|
+
* ```ts
|
|
62
|
+
* const poll = new PollBuilder()
|
|
63
|
+
* .addAnswers(
|
|
64
|
+
* { text: 'Answer 1' },
|
|
65
|
+
* { text: 'Answer 2' },
|
|
66
|
+
* );
|
|
67
|
+
* ```
|
|
68
|
+
* @param answers - The answers to add
|
|
69
|
+
*/
|
|
70
|
+
public addAnswers(
|
|
71
|
+
...answers: RestOrArray<
|
|
72
|
+
Omit<APIPollAnswer, 'answer_id'> | PollAnswerBuilder | ((builder: PollAnswerBuilder) => PollAnswerBuilder)
|
|
73
|
+
>
|
|
74
|
+
): this {
|
|
75
|
+
const normalizedAnswers = normalizeArray(answers);
|
|
76
|
+
const resolved = normalizedAnswers.map((answer) => resolveBuilder(answer, PollAnswerBuilder));
|
|
77
|
+
|
|
78
|
+
this.data.answers.push(...resolved);
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Removes, replaces, or inserts answers for this poll.
|
|
84
|
+
*
|
|
85
|
+
* @remarks
|
|
86
|
+
* This method behaves similarly
|
|
87
|
+
* to {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/splice | Array.prototype.splice()}.
|
|
88
|
+
* The maximum amount of answers that can be added is 10.
|
|
89
|
+
*
|
|
90
|
+
* It's useful for modifying and adjusting order of the already-existing answers of a poll.
|
|
91
|
+
* @example
|
|
92
|
+
* Remove the first answer:
|
|
93
|
+
* ```ts
|
|
94
|
+
* poll.spliceAnswers(0, 1);
|
|
95
|
+
* ```
|
|
96
|
+
* @example
|
|
97
|
+
* Remove the first n answers:
|
|
98
|
+
* ```ts
|
|
99
|
+
* const n = 4;
|
|
100
|
+
* poll.spliceAnswers(0, n);
|
|
101
|
+
* ```
|
|
102
|
+
* @example
|
|
103
|
+
* Remove the last answer:
|
|
104
|
+
* ```ts
|
|
105
|
+
* poll.spliceAnswers(-1, 1);
|
|
106
|
+
* ```
|
|
107
|
+
* @param index - The index to start at
|
|
108
|
+
* @param deleteCount - The number of answers to remove
|
|
109
|
+
* @param answers - The replacing answer objects
|
|
110
|
+
*/
|
|
111
|
+
public spliceAnswers(
|
|
112
|
+
index: number,
|
|
113
|
+
deleteCount: number,
|
|
114
|
+
...answers: (
|
|
115
|
+
| Omit<APIPollAnswer, 'answer_id'>
|
|
116
|
+
| PollAnswerBuilder
|
|
117
|
+
| ((builder: PollAnswerBuilder) => PollAnswerBuilder)
|
|
118
|
+
)[]
|
|
119
|
+
): this {
|
|
120
|
+
const normalizedAnswers = normalizeArray(answers);
|
|
121
|
+
const resolved = normalizedAnswers.map((answer) => resolveBuilder(answer, PollAnswerBuilder));
|
|
122
|
+
|
|
123
|
+
this.data.answers.splice(index, deleteCount, ...resolved);
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Sets the answers for this poll.
|
|
129
|
+
*
|
|
130
|
+
* @remarks
|
|
131
|
+
* This method is an alias for {@link PollBuilder.spliceAnswers}. More specifically,
|
|
132
|
+
* it splices the entire array of answers, replacing them with the provided answers.
|
|
133
|
+
*
|
|
134
|
+
* You can set a maximum of 10 answers.
|
|
135
|
+
* @param answers - The answers to set
|
|
136
|
+
*/
|
|
137
|
+
public setAnswers(
|
|
138
|
+
...answers: RestOrArray<
|
|
139
|
+
Omit<APIPollAnswer, 'answer_id'> | PollAnswerBuilder | ((builder: PollAnswerBuilder) => PollAnswerBuilder)
|
|
140
|
+
>
|
|
141
|
+
): this {
|
|
142
|
+
return this.spliceAnswers(0, this.data.answers.length, ...normalizeArray(answers));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Sets the question for this poll.
|
|
147
|
+
*
|
|
148
|
+
* @param options - The data to use for this poll's question
|
|
149
|
+
*/
|
|
150
|
+
public setQuestion(
|
|
151
|
+
options:
|
|
152
|
+
| Omit<APIPollMedia, 'emoji'>
|
|
153
|
+
| PollQuestionBuilder
|
|
154
|
+
| ((builder: PollQuestionBuilder) => PollQuestionBuilder),
|
|
155
|
+
): this {
|
|
156
|
+
this.data.question = resolveBuilder(options, PollQuestionBuilder);
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Updates the question of this poll.
|
|
162
|
+
*
|
|
163
|
+
* @param updater - The function to update the question with
|
|
164
|
+
*/
|
|
165
|
+
public updateQuestion(updater: (builder: PollQuestionBuilder) => void): this {
|
|
166
|
+
updater(this.data.question);
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Sets the layout type for this poll.
|
|
172
|
+
*
|
|
173
|
+
* @remarks
|
|
174
|
+
* This method is redundant while only one type of poll layout exists (`PollLayoutType.Default`)
|
|
175
|
+
* with Discord using that as the layout type if none is specified.
|
|
176
|
+
* @param type - The type of poll layout to use
|
|
177
|
+
*/
|
|
178
|
+
public setLayoutType(type: PollLayoutType): this {
|
|
179
|
+
this.data.layout_type = type;
|
|
180
|
+
return this;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Clears the layout type for this poll.
|
|
185
|
+
*/
|
|
186
|
+
public clearLayoutType(): this {
|
|
187
|
+
this.data.layout_type = undefined;
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Sets whether multi-select is enabled for this poll.
|
|
193
|
+
*
|
|
194
|
+
* @param multiSelect - Whether to allow multi-select
|
|
195
|
+
*/
|
|
196
|
+
public setMultiSelect(multiSelect = true): this {
|
|
197
|
+
this.data.allow_multiselect = multiSelect;
|
|
198
|
+
return this;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Sets how long this poll will be open for in hours.
|
|
203
|
+
*
|
|
204
|
+
* @remarks
|
|
205
|
+
* Minimum duration is `1`, with maximum duration being `768` (32 days).
|
|
206
|
+
* Default if none specified is `24` (one day).
|
|
207
|
+
* @param duration - The amount of hours this poll will be open for
|
|
208
|
+
*/
|
|
209
|
+
public setDuration(duration: number): this {
|
|
210
|
+
this.data.duration = duration;
|
|
211
|
+
return this;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Clears the duration for this poll.
|
|
216
|
+
*/
|
|
217
|
+
public clearDuration(): this {
|
|
218
|
+
this.data.duration = undefined;
|
|
219
|
+
return this;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Serializes this builder to API-compatible JSON data.
|
|
224
|
+
*
|
|
225
|
+
* Note that by disabling validation, there is no guarantee that the resulting object will be valid.
|
|
226
|
+
*
|
|
227
|
+
* @param validationOverride - Force validation to run/not run regardless of your global preference
|
|
228
|
+
*/
|
|
229
|
+
public toJSON(validationOverride?: boolean): RESTAPIPoll {
|
|
230
|
+
const { answers, question, ...rest } = this.data;
|
|
231
|
+
|
|
232
|
+
const data = {
|
|
233
|
+
...structuredClone(rest),
|
|
234
|
+
// Disable validation because the pollPredicate below will validate those as well
|
|
235
|
+
answers: answers.map((answer) => answer.toJSON(false)),
|
|
236
|
+
question: question.toJSON(false),
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
validate(pollPredicate, data, validationOverride);
|
|
240
|
+
|
|
241
|
+
return data;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { JSONEncodable } from '@ovencord/util';
|
|
2
|
+
import type { APIPollAnswer, APIPollMedia } from 'discord-api-types/v10';
|
|
3
|
+
import { resolveBuilder } from '../../util/resolveBuilder';
|
|
4
|
+
import { validate } from '../../util/validation';
|
|
5
|
+
import { pollAnswerPredicate } from './Assertions';
|
|
6
|
+
import { PollAnswerMediaBuilder } from './PollAnswerMedia';
|
|
7
|
+
|
|
8
|
+
export interface PollAnswerData extends Omit<APIPollAnswer, 'answer_id' | 'poll_media'> {
|
|
9
|
+
poll_media: PollAnswerMediaBuilder;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A builder that creates API-compatible JSON data for poll answers.
|
|
14
|
+
*/
|
|
15
|
+
export class PollAnswerBuilder implements JSONEncodable<Omit<APIPollAnswer, 'answer_id'>> {
|
|
16
|
+
/**
|
|
17
|
+
* The API data associated with this poll answer.
|
|
18
|
+
*/
|
|
19
|
+
private readonly data: PollAnswerData;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a new poll answer.
|
|
23
|
+
*
|
|
24
|
+
* @param data - The API data to create this poll answer with
|
|
25
|
+
*/
|
|
26
|
+
public constructor(data: Partial<Omit<APIPollAnswer, 'answer_id'>> = {}) {
|
|
27
|
+
const { poll_media, ...rest } = data;
|
|
28
|
+
|
|
29
|
+
this.data = {
|
|
30
|
+
...structuredClone(rest),
|
|
31
|
+
poll_media: new PollAnswerMediaBuilder(poll_media),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Sets the media for this poll answer.
|
|
37
|
+
*
|
|
38
|
+
* @param options - The data to use for this poll answer's media
|
|
39
|
+
*/
|
|
40
|
+
public setMedia(
|
|
41
|
+
options: APIPollMedia | PollAnswerMediaBuilder | ((builder: PollAnswerMediaBuilder) => PollAnswerMediaBuilder),
|
|
42
|
+
): this {
|
|
43
|
+
this.data.poll_media = resolveBuilder(options, PollAnswerMediaBuilder);
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Updates the media of this poll answer.
|
|
49
|
+
*
|
|
50
|
+
* @param updater - The function to update the media with
|
|
51
|
+
*/
|
|
52
|
+
public updateMedia(updater: (builder: PollAnswerMediaBuilder) => void): this {
|
|
53
|
+
updater(this.data.poll_media);
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Serializes this builder to API-compatible JSON data.
|
|
59
|
+
*
|
|
60
|
+
* Note that by disabling validation, there is no guarantee that the resulting object will be valid.
|
|
61
|
+
*
|
|
62
|
+
* @param validationOverride - Force validation to run/not run regardless of your global preference
|
|
63
|
+
*/
|
|
64
|
+
public toJSON(validationOverride?: boolean): Omit<APIPollAnswer, 'answer_id'> {
|
|
65
|
+
const { poll_media, ...rest } = this.data;
|
|
66
|
+
|
|
67
|
+
const data = {
|
|
68
|
+
...structuredClone(rest),
|
|
69
|
+
// Disable validation because the pollAnswerPredicate below will validate this as well
|
|
70
|
+
poll_media: poll_media.toJSON(false),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
validate(pollAnswerPredicate, data, validationOverride);
|
|
74
|
+
|
|
75
|
+
return data;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { APIPartialEmoji, APIPollMedia } from 'discord-api-types/v10';
|
|
2
|
+
import { validate } from '../../util/validation.js';
|
|
3
|
+
import { pollAnswerMediaPredicate } from './Assertions.js';
|
|
4
|
+
import { PollMediaBuilder } from './PollMedia.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A builder that creates API-compatible JSON data for the media of a poll answer.
|
|
8
|
+
*/
|
|
9
|
+
export class PollAnswerMediaBuilder extends PollMediaBuilder {
|
|
10
|
+
/**
|
|
11
|
+
* Sets the emoji for this poll answer media.
|
|
12
|
+
*
|
|
13
|
+
* @param emoji - The emoji to use
|
|
14
|
+
*/
|
|
15
|
+
public setEmoji(emoji: APIPartialEmoji): this {
|
|
16
|
+
this.data.emoji = emoji;
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Clears the emoji for this poll answer media.
|
|
22
|
+
*/
|
|
23
|
+
public clearEmoji(): this {
|
|
24
|
+
this.data.emoji = undefined;
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* {@inheritDoc PollMediaBuilder.toJSON}
|
|
30
|
+
*/
|
|
31
|
+
public override toJSON(validationOverride?: boolean): APIPollMedia {
|
|
32
|
+
const clone = structuredClone(this.data);
|
|
33
|
+
|
|
34
|
+
validate(pollAnswerMediaPredicate, clone, validationOverride);
|
|
35
|
+
|
|
36
|
+
return clone;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { APIPollMedia } from 'discord-api-types/v10';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The base poll media builder that contains common symbols for poll media builders.
|
|
5
|
+
*/
|
|
6
|
+
export abstract class PollMediaBuilder {
|
|
7
|
+
/**
|
|
8
|
+
* The API data associated with this poll media.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
protected readonly data: Partial<APIPollMedia>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Creates new poll media.
|
|
16
|
+
*
|
|
17
|
+
* @param data - The API data to create this poll media with
|
|
18
|
+
*/
|
|
19
|
+
public constructor(data: Partial<APIPollMedia> = {}) {
|
|
20
|
+
this.data = structuredClone(data);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Sets the text for this poll media.
|
|
25
|
+
*
|
|
26
|
+
* @param text - The text to use
|
|
27
|
+
*/
|
|
28
|
+
public setText(text: string): this {
|
|
29
|
+
this.data.text = text;
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Serializes this builder to API-compatible JSON data.
|
|
35
|
+
*
|
|
36
|
+
* Note that by disabling validation, there is no guarantee that the resulting object will be valid.
|
|
37
|
+
*
|
|
38
|
+
* @param validationOverride - Force validation to run/not run regardless of your global preference
|
|
39
|
+
*/
|
|
40
|
+
public abstract toJSON(validationOverride?: boolean): APIPollMedia;
|
|
41
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { APIPollMedia } from 'discord-api-types/v10';
|
|
2
|
+
import { validate } from '../../util/validation.js';
|
|
3
|
+
import { pollQuestionPredicate } from './Assertions.js';
|
|
4
|
+
import { PollMediaBuilder } from './PollMedia.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A builder that creates API-compatible JSON data for a poll question.
|
|
8
|
+
*/
|
|
9
|
+
export class PollQuestionBuilder extends PollMediaBuilder {
|
|
10
|
+
/**
|
|
11
|
+
* {@inheritDoc PollMediaBuilder.toJSON}
|
|
12
|
+
*/
|
|
13
|
+
public override toJSON(validationOverride?: boolean): Omit<APIPollMedia, 'emoji'> {
|
|
14
|
+
const clone = structuredClone(this.data);
|
|
15
|
+
|
|
16
|
+
validate(pollQuestionPredicate, clone, validationOverride);
|
|
17
|
+
|
|
18
|
+
return clone;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* An error that is thrown when validation fails.
|
|
5
|
+
*/
|
|
6
|
+
export class ValidationError extends Error {
|
|
7
|
+
/**
|
|
8
|
+
* The underlying cause of the validation error.
|
|
9
|
+
*/
|
|
10
|
+
public override readonly cause: z.ZodError;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
public constructor(error: z.ZodError) {
|
|
16
|
+
super(error.message);
|
|
17
|
+
|
|
18
|
+
this.name = 'ValidationError';
|
|
19
|
+
this.cause = error;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes data that is a rest parameter or an array into an array with a depth of 1.
|
|
3
|
+
*
|
|
4
|
+
* @typeParam ItemType - The data that must satisfy {@link RestOrArray}.
|
|
5
|
+
* @param arr - The (possibly variadic) data to normalize
|
|
6
|
+
*/
|
|
7
|
+
export function normalizeArray<ItemType>(arr: RestOrArray<ItemType>): ItemType[] {
|
|
8
|
+
if (Array.isArray(arr[0])) return [...arr[0]];
|
|
9
|
+
return arr as ItemType[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Represents data that may be an array or came from a rest parameter.
|
|
14
|
+
*
|
|
15
|
+
* @remarks
|
|
16
|
+
* This type is used throughout builders to ensure both an array and variadic arguments
|
|
17
|
+
* may be used. It is normalized with {@link normalizeArray}.
|
|
18
|
+
*/
|
|
19
|
+
export type RestOrArray<Type> = Type[] | [Type[]];
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { JSONEncodable } from '@ovencord/util';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @privateRemarks
|
|
5
|
+
* This is a type-guard util, because if you were to in-line `builder instanceof Constructor` in the `resolveBuilder`
|
|
6
|
+
* function, TS doesn't narrow out the type `Builder`, causing a type error on the last return statement.
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
function isBuilder<Builder extends JSONEncodable<any>>(
|
|
10
|
+
builder: unknown,
|
|
11
|
+
Constructor: new () => Builder,
|
|
12
|
+
): builder is Builder {
|
|
13
|
+
return builder instanceof Constructor;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* "Resolves" a builder from the 3 ways it can be input:
|
|
18
|
+
* 1. A clean instance
|
|
19
|
+
* 2. A data object that can be used to construct the builder
|
|
20
|
+
* 3. A function that takes a builder and returns a builder e.g. `builder => builder.setFoo('bar')`
|
|
21
|
+
*
|
|
22
|
+
* @typeParam Builder - The builder type
|
|
23
|
+
* @typeParam BuilderData - The data object that can be used to construct the builder
|
|
24
|
+
* @param builder - The user input, as described in the function description
|
|
25
|
+
* @param Constructor - The constructor of the builder
|
|
26
|
+
*/
|
|
27
|
+
export function resolveBuilder<Builder extends JSONEncodable<any>, BuilderData extends Record<PropertyKey, any>>(
|
|
28
|
+
builder: Builder | BuilderData | ((builder: Builder) => Builder),
|
|
29
|
+
Constructor: new (data?: BuilderData) => Builder,
|
|
30
|
+
): Builder {
|
|
31
|
+
if (isBuilder(builder, Constructor)) {
|
|
32
|
+
return builder;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (typeof builder === 'function') {
|
|
36
|
+
return builder(new Constructor());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return new Constructor(builder);
|
|
40
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import { ValidationError } from './ValidationError.js';
|
|
3
|
+
|
|
4
|
+
let validationEnabled = true;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Enables validators.
|
|
8
|
+
*
|
|
9
|
+
* @returns Whether validation is occurring.
|
|
10
|
+
*/
|
|
11
|
+
export function enableValidators() {
|
|
12
|
+
return (validationEnabled = true);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Disables validators.
|
|
17
|
+
*
|
|
18
|
+
* @returns Whether validation is occurring.
|
|
19
|
+
*/
|
|
20
|
+
export function disableValidators() {
|
|
21
|
+
return (validationEnabled = false);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Checks whether validation is occurring.
|
|
26
|
+
*/
|
|
27
|
+
export function isValidationEnabled() {
|
|
28
|
+
return validationEnabled;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Parses a value with a given validator, accounting for whether validation is enabled.
|
|
33
|
+
*
|
|
34
|
+
* @param validator - The zod validator to use
|
|
35
|
+
* @param value - The value to parse
|
|
36
|
+
* @param validationOverride - Force validation to run/not run regardless of your global preference
|
|
37
|
+
* @returns The result from parsing
|
|
38
|
+
* @throws {@link ValidationError}
|
|
39
|
+
* Throws if the value does not pass validation, if enabled.
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export function validate<Validator extends z.ZodType>(
|
|
43
|
+
validator: Validator,
|
|
44
|
+
value: unknown,
|
|
45
|
+
validationOverride?: boolean,
|
|
46
|
+
): z.output<Validator> {
|
|
47
|
+
if (validationOverride === false || (validationOverride === undefined && !isValidationEnabled())) {
|
|
48
|
+
return value as z.output<Validator>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const result = validator.safeParse(value);
|
|
52
|
+
|
|
53
|
+
if (!result.success) {
|
|
54
|
+
throw new ValidationError(result.error);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return result.data;
|
|
58
|
+
}
|