Skip to content

Commit d59857e

Browse files
rie03pJiralitealmeidxvladfrangukodiakhq[bot]
authored
fix(builders): add proper snowflake validation (#11290)
* fix(builders): add proper snowflake validation close #11289 * fix(builders): use snowflake validation for attachment id * test(builders): add validation tests for snowflake attachment IDs * fix: better regex Co-authored-by: Almeida <github@almeidx.dev> * test(builders): fix snowflake validation in fileBody test * Update packages/builders/src/Assertions.ts Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> * fix: update regex --------- Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> Co-authored-by: Almeida <github@almeidx.dev> Co-authored-by: Vlad Frangu <me@vladfrangu.dev> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
1 parent 19253f6 commit d59857e

File tree

5 files changed

+18
-14
lines changed

5 files changed

+18
-14
lines changed

packages/builders/__tests__/messages/fileBody.test.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ import { AttachmentBuilder, MessageBuilder } from '../../src/index.js';
66
test('AttachmentBuilder stores and exposes file data', () => {
77
const data = Buffer.from('hello world');
88
const attachment = new AttachmentBuilder()
9-
.setId('0')
9+
.setId(1)
1010
.setFilename('greeting.txt')
1111
.setFileData(data)
1212
.setFileContentType('text/plain');
1313

1414
expect(attachment.getRawFile()).toStrictEqual({
1515
contentType: 'text/plain',
1616
data,
17-
key: 'files[0]',
17+
key: 'files[1]',
1818
name: 'greeting.txt',
1919
});
2020

@@ -27,7 +27,7 @@ test('AttachmentBuilder stores and exposes file data', () => {
2727
test('MessageBuilder.toFileBody returns JSON body and files', () => {
2828
const msg = new MessageBuilder().setContent('here is a file').addAttachments(
2929
new AttachmentBuilder()
30-
.setId('0')
30+
.setId(0)
3131
.setFilename('file.bin')
3232
.setFileData(Buffer.from([1, 2, 3]))
3333
.setFileContentType('application/octet-stream'),
@@ -47,7 +47,9 @@ test('MessageBuilder.toFileBody returns JSON body and files', () => {
4747
});
4848

4949
test('MessageBuilder.toFileBody returns empty files when attachments reference existing uploads', () => {
50-
const msg = new MessageBuilder().addAttachments(new AttachmentBuilder().setId('123').setFilename('existing.png'));
50+
const msg = new MessageBuilder().addAttachments(
51+
new AttachmentBuilder().setId('1234567890123456789').setFilename('existing.png'),
52+
);
5153

5254
const { body, files } = msg.toFileBody();
5355
expect(body).toEqual(msg.toJSON());

packages/builders/__tests__/messages/message.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ describe('Message', () => {
6464
row.addPrimaryButtonComponents((button) => button.setCustomId('abc').setLabel('def')),
6565
)
6666
.setStickerIds('123', '456')
67-
.addAttachments((attachment) => attachment.setId('hi!').setFilename('abc'))
67+
.addAttachments((attachment) => attachment.setId(0).setFilename('abc'))
6868
.setFlags(MessageFlags.Ephemeral)
6969
.setEnforceNonce(false)
7070
.updatePoll((poll) => poll.addAnswers({ poll_media: { text: 'foo' } }).setQuestion({ text: 'foo' }));
@@ -83,7 +83,7 @@ describe('Message', () => {
8383
},
8484
],
8585
sticker_ids: ['123', '456'],
86-
attachments: [{ id: 'hi!', filename: 'abc' }],
86+
attachments: [{ id: 0, filename: 'abc' }],
8787
flags: 64,
8888
enforce_nonce: false,
8989
poll: {

packages/builders/src/Assertions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { z } from 'zod';
33

44
export const idPredicate = z.int().min(0).max(2_147_483_647).optional();
55
export const customIdPredicate = z.string().min(1).max(100);
6+
export const snowflakePredicate = z.string().regex(/^(?:0|[1-9]\d*)$/);
67

78
export const memberPermissionsPredicate = z.coerce.bigint();
89

packages/builders/src/components/Assertions.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { ButtonStyle, ChannelType, ComponentType, SelectMenuDefaultValueType } from 'discord-api-types/v10';
22
import { z } from 'zod';
3-
import { idPredicate, customIdPredicate } from '../Assertions.js';
3+
import { idPredicate, customIdPredicate, snowflakePredicate } from '../Assertions.js';
44

55
const labelPredicate = z.string().min(1).max(80);
66

77
export const emojiPredicate = z
88
.strictObject({
9-
id: z.string().optional(),
9+
id: snowflakePredicate.optional(),
1010
name: z.string().min(2).max(32).optional(),
1111
animated: z.boolean().optional(),
1212
})
@@ -39,7 +39,7 @@ const buttonLinkPredicate = buttonPredicateBase.extend({
3939

4040
const buttonPremiumPredicate = buttonPredicateBase.extend({
4141
style: z.literal(ButtonStyle.Premium),
42-
sku_id: z.string(),
42+
sku_id: snowflakePredicate,
4343
});
4444

4545
export const buttonPredicate = z.discriminatedUnion('style', [
@@ -64,7 +64,7 @@ export const selectMenuChannelPredicate = selectMenuBasePredicate.extend({
6464
type: z.literal(ComponentType.ChannelSelect),
6565
channel_types: z.enum(ChannelType).array().optional(),
6666
default_values: z
67-
.object({ id: z.string(), type: z.literal(SelectMenuDefaultValueType.Channel) })
67+
.object({ id: snowflakePredicate, type: z.literal(SelectMenuDefaultValueType.Channel) })
6868
.array()
6969
.max(25)
7070
.optional(),
@@ -74,7 +74,7 @@ export const selectMenuMentionablePredicate = selectMenuBasePredicate.extend({
7474
type: z.literal(ComponentType.MentionableSelect),
7575
default_values: z
7676
.object({
77-
id: z.string(),
77+
id: snowflakePredicate,
7878
type: z.literal([SelectMenuDefaultValueType.Role, SelectMenuDefaultValueType.User]),
7979
})
8080
.array()
@@ -85,7 +85,7 @@ export const selectMenuMentionablePredicate = selectMenuBasePredicate.extend({
8585
export const selectMenuRolePredicate = selectMenuBasePredicate.extend({
8686
type: z.literal(ComponentType.RoleSelect),
8787
default_values: z
88-
.object({ id: z.string(), type: z.literal(SelectMenuDefaultValueType.Role) })
88+
.object({ id: snowflakePredicate, type: z.literal(SelectMenuDefaultValueType.Role) })
8989
.array()
9090
.max(25)
9191
.optional(),
@@ -142,7 +142,7 @@ export const selectMenuStringPredicate = selectMenuBasePredicate
142142
export const selectMenuUserPredicate = selectMenuBasePredicate.extend({
143143
type: z.literal(ComponentType.UserSelect),
144144
default_values: z
145-
.object({ id: z.string(), type: z.literal(SelectMenuDefaultValueType.User) })
145+
.object({ id: snowflakePredicate, type: z.literal(SelectMenuDefaultValueType.User) })
146146
.array()
147147
.max(25)
148148
.optional(),

packages/builders/src/messages/Assertions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Buffer } from 'node:buffer';
22
import { AllowedMentionsTypes, ComponentType, MessageFlags, MessageReferenceType } from 'discord-api-types/v10';
33
import { z } from 'zod';
4+
import { snowflakePredicate } from '../Assertions.js';
45
import { embedPredicate } from './embed/Assertions.js';
56
import { pollPredicate } from './poll/Assertions.js';
67

@@ -15,7 +16,7 @@ export const rawFilePredicate = z.object({
1516

1617
export const attachmentPredicate = z.object({
1718
// As a string it only makes sense for edits when we do have an attachment snowflake
18-
id: z.union([z.string(), z.number()]),
19+
id: z.union([snowflakePredicate, z.number()]),
1920
description: z.string().max(1_024).optional(),
2021
duration_secs: z
2122
.number()

0 commit comments

Comments
 (0)