Compare commits

...

11 Commits

Author SHA1 Message Date
Almeida
07f1d8f5b3 feat(core)!: rename getAllMembers to getMembers with query support (#11484)
BREAKING CHANGE: getAllMembers has been removed. Use getMembers instead, which accepts an optional query parameter.
2026-05-23 02:05:55 +01:00
Almeida
37892ead4c feat(ClientApplication): add fetchActivityInstance method (#11481)
* feat(ClientApplication): add fetchActivityInstance method

* refactor: requested changes

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2026-05-22 22:53:39 +00:00
Qjuh
9f111ed4df docs(Role): fix Role#tags docs (#11524)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2026-05-22 22:51:00 +00:00
Fadi Raad
c313bc58f1 docs: fix typos and duplicated words in comments and guide (#11502)
- fix "wether" -> "whether" and "dependant" -> "dependent" in RedisGateway JSDoc
- fix duplicated "the" in useful-packages guide
- fix duplicated "for" in two internal JSDoc param descriptions

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2026-05-22 22:47:15 +00:00
Almeida
76478c4ba5 feat(ShardingManager): allow setting custom api url (#11471)
* feat(ShardingManager): allow setting custom api url

* Apply suggestion from @Jiralite

Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>

---------

Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2026-05-22 23:40:09 +01:00
Kshitij Anurag
28dd65d322 feat: add shared client theme support (#11454)
* feat: add shared client theme support

* Apply suggestion from @Qjuh

Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com>

* chore: tests

* chore: format

* chore: apply suggestions from code review

---------

Co-authored-by: Almeida <github@almeidx.dev>
Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2026-05-22 22:31:00 +00:00
Denis-Adrian Cristea
e490a230a3 fix(stringOption): zod validation (#11532)
fix: zod validation

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2026-05-21 15:46:49 +00:00
Qjuh
1e88dcdc05 feat(DMChannel)!: allow partial DMChannel without client user (#11443)
BREAKING CHANGE: `DMChannel#recipientId` is now nullable.
2026-05-20 19:05:16 +01:00
Almeida
40ce0791a8 feat(ApplicationsAPI): add getActivityInstance method (#11482)
feat(core): add getActivityInstance to ApplicationsAPI

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2026-04-24 17:11:18 +00:00
Dramex
8ea7c7c7e4 fix(collection): preserve ReadonlyCollection through tap/each (#11501)
* fix(collection): preserve ReadonlyCollection through tap/each

`each` and `tap` return polymorphic `this`, which TypeScript resolves
against the `Omit<Collection, ...>` portion of `ReadonlyCollection`
rather than the full intersection. That let callers reach `set` and
`delete` on the result of a chain started from a `ReadonlyCollection`:

    const ro: ReadonlyCollection<string, number> = new Collection(...);
    ro.tap(() => {}).set('x', 0); // compiled, mutated the underlying Map

The fix omits `each` and `tap` from the base `Omit` and re-declares
them on the `ReadonlyCollection` side of the intersection so the return
type narrows back to `ReadonlyCollection`.

Closes #10514

* test(collection): gate readonly-chain checks behind if(false)

Previously the `@ts-expect-error` lines still executed the `set` and
`delete` mutations at runtime, and the final `size === 1` passed only
because they happened to cancel out. Wrapping the assertions in
`if (false)` keeps the compile-time guarantee while the backing
collection is truly untouched, and adds a `get('a') === 1` check as
a belt.

* test(collection): move readonly type checks to *.test-d.ts

Addresses review feedback. The type-level assertions around tap() and
each() preserving ReadonlyCollection belong in a *.test-d.ts file so
they run through vitest's typecheck pass instead of runtime.

Replaces the if(false)-gated @ts-expect-error block in collection.test.ts
with expectTypeOf assertions in a new collection.test-d.ts. Covers both
the no-thisArg and with-thisArg overloads of tap and each.
2026-04-20 19:15:29 +00:00
2^1
58c5ebdd08 chore(readme): update related libraries link (#11487)
* fix(readme): related libraries link broke

* docs(readme): update all related-libs links
2026-04-13 16:20:06 +00:00
56 changed files with 751 additions and 62 deletions

View File

@@ -78,7 +78,7 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/discord.js
[npm]: https://www.npmjs.com/package/discord.js
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[rpc]: https://www.npmjs.com/package/discord-rpc
[rpc-source]: https://github.com/discordjs/RPC
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -110,7 +110,7 @@ interaction.reply(oneLineCommaListsOr`
`);
```
Check the the documentation to find more useful functions.
Check the documentation to find more useful functions.
## chalk

View File

@@ -80,5 +80,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord]: https://discord.gg/djs
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/apps/proxy-container
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -45,5 +45,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord]: https://discord.gg/djs
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/apps/website
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -42,5 +42,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord]: https://discord.gg/djs
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/actions
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -42,5 +42,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord]: https://discord.gg/djs
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/api-extractor-utils
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -134,5 +134,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/brokers
[npm]: https://www.npmjs.com/package/@discordjs/brokers
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -35,8 +35,8 @@ export type RedisBrokerDiscordEvents = {
* events as the receiving service expects, and also that you handle GatewaySend events.
* - One drawback to using this directly with `@discordjs/core` is that you lose granular control over when to `ack`
* events. This implementation `ack`s as soon as the event is emitted to listeners. In practice, this means that if your
* service crashes while handling an event, it's pretty arbitrary wether that event gets re-processed on restart or not.
* (Mostly dependant on if your handler is async or not, and also if the `ack` call has time to go through).
* service crashes while handling an event, it's pretty arbitrary whether that event gets re-processed on restart or not.
* (Mostly dependent on if your handler is async or not, and also if the `ack` call has time to go through).
*
* @example
* ```ts

View File

@@ -70,5 +70,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/builders
[npm]: https://www.npmjs.com/package/@discordjs/builders
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -188,10 +188,19 @@ describe('Application Command toJSON() results', () => {
type: ApplicationCommandOptionType.String,
required: true,
autocomplete: true,
// TODO
choices: [],
});
// Starting with zod 4.4.0 (potentially lower), this usecase was broken prior to #11532
// (i.e. choices not present at all with autocomplete: true)
expect(getStringOption().setAutocomplete(true).toJSON()).toEqual<APIApplicationCommandStringOption>({
name: 'owo',
description: 'Testing 123',
type: ApplicationCommandOptionType.String,
required: true,
autocomplete: true,
});
expect(
getStringOption().addChoices({ name: 'uwu', value: '1' }).toJSON(),
).toEqual<APIApplicationCommandStringOption>({

View File

@@ -1,6 +1,6 @@
import { AllowedMentionsTypes, MessageFlags } from 'discord-api-types/v10';
import { AllowedMentionsTypes, BaseThemeType, MessageFlags } from 'discord-api-types/v10';
import { describe, test, expect } from 'vitest';
import { AllowedMentionsBuilder, EmbedBuilder, MessageBuilder } from '../../src/index.js';
import { AllowedMentionsBuilder, EmbedBuilder, MessageBuilder, SharedClientThemeBuilder } from '../../src/index.js';
const base = {
allowed_mentions: undefined,
@@ -9,6 +9,7 @@ const base = {
embeds: [],
message_reference: undefined,
poll: undefined,
shared_client_theme: undefined,
};
describe('Message', () => {
@@ -103,6 +104,92 @@ describe('Message', () => {
question: { text: 'foo' },
answers: [{ poll_media: { text: 'foo' } }],
},
shared_client_theme: undefined,
});
});
describe('SharedClientTheme', () => {
test('GIVEN a message with a shared client theme THEN return valid toJSON data', () => {
const message = new MessageBuilder().setSharedClientTheme(
new SharedClientThemeBuilder()
.setColors(['5865F2', '7258F2'])
.setGradientAngle(0)
.setBaseMix(58)
.setBaseTheme(BaseThemeType.Dark),
);
expect(message.toJSON()).toStrictEqual({
...base,
shared_client_theme: {
colors: ['5865F2', '7258F2'],
gradient_angle: 0,
base_mix: 58,
base_theme: 1,
},
});
});
test('GIVEN a message with a function to update shared client theme THEN return valid toJSON data', () => {
const message = new MessageBuilder().updateSharedClientTheme((theme) =>
theme.setColors(['5865F2']).setGradientAngle(90).setBaseMix(100),
);
expect(message.toJSON()).toStrictEqual({
...base,
shared_client_theme: {
colors: ['5865F2'],
gradient_angle: 90,
base_mix: 100,
},
});
});
test('GIVEN a message with a shared client theme then cleared THEN shared_client_theme is undefined', () => {
const message = new MessageBuilder()
.setContent('foo')
.setSharedClientTheme(new SharedClientThemeBuilder().setColors(['5865F2']).setGradientAngle(0).setBaseMix(50))
.clearSharedClientTheme();
expect(message.toJSON()).toStrictEqual({
...base,
content: 'foo',
shared_client_theme: undefined,
});
});
test('GIVEN a SharedClientThemeBuilder with too many colors THEN it throws', () => {
const theme = new SharedClientThemeBuilder()
.setColors(['111111', '222222', '333333', '444444', '555555', '666666'])
.setGradientAngle(0)
.setBaseMix(50);
expect(() => theme.toJSON()).toThrow();
});
test('GIVEN a SharedClientThemeBuilder with out of range gradient angle THEN it throws', () => {
const theme = new SharedClientThemeBuilder().setColors(['5865F2']).setGradientAngle(400).setBaseMix(50);
expect(() => theme.toJSON()).toThrow();
});
test('GIVEN a SharedClientThemeBuilder with out of range base mix THEN it throws', () => {
const theme = new SharedClientThemeBuilder().setColors(['5865F2']).setGradientAngle(0).setBaseMix(150);
expect(() => theme.toJSON()).toThrow();
});
test('GIVEN a shared client theme with base_theme set THEN clearBaseTheme works correctly', () => {
const theme = new SharedClientThemeBuilder()
.setColors(['5865F2'])
.setGradientAngle(0)
.setBaseMix(50)
.setBaseTheme(BaseThemeType.Light)
.clearBaseTheme();
expect(theme.toJSON(false)).toStrictEqual({
colors: ['5865F2'],
gradient_angle: 0,
base_mix: 50,
base_theme: undefined,
});
});
});
});

View File

@@ -88,6 +88,7 @@ export * from './messages/Assertions.js';
export * from './messages/Attachment.js';
export * from './messages/Message.js';
export * from './messages/MessageReference.js';
export * from './messages/SharedClientTheme.js';
export * from './util/normalizeArray.js';
export * from './util/resolveBuilder.js';

View File

@@ -39,7 +39,7 @@ const channelMixinOptionPredicate = z.object({
const autocompleteMixinOptionPredicate = z.object({
autocomplete: z.literal(true),
choices: z.union([z.never(), z.never().array(), z.undefined()]),
choices: z.array(z.any()).length(0).optional(),
});
const choiceValueStringPredicate = z.string().min(1).max(100);

View File

@@ -1,5 +1,11 @@
import { Buffer } from 'node:buffer';
import { AllowedMentionsTypes, ComponentType, MessageFlags, MessageReferenceType } from 'discord-api-types/v10';
import {
AllowedMentionsTypes,
BaseThemeType,
ComponentType,
MessageFlags,
MessageReferenceType,
} from 'discord-api-types/v10';
import { z } from 'zod';
import { snowflakePredicate } from '../Assertions.js';
import { embedPredicate } from './embed/Assertions.js';
@@ -79,18 +85,29 @@ const basicActionRowPredicate = z.object({
.array(),
});
export const sharedClientThemePredicate = z.object({
colors: z
.array(z.string().regex(/^[\da-f]{6}$/i))
.min(1)
.max(5),
gradient_angle: z.int().min(0).max(360),
base_mix: z.int().min(0).max(100),
base_theme: z.enum(BaseThemeType).nullish(),
});
const messageNoComponentsV2Predicate = baseMessagePredicate
.extend({
content: z.string().max(2_000).optional(),
embeds: embedPredicate.array().max(10).optional(),
sticker_ids: z.array(z.string()).max(3).optional(),
poll: pollPredicate.optional(),
shared_client_theme: sharedClientThemePredicate.optional(),
components: basicActionRowPredicate.array().max(5).optional(),
flags: z
.int()
.optional()
.refine((flags) => !flags || (flags & MessageFlags.IsComponentsV2) === 0, {
error: 'Cannot set content, embeds, stickers, or poll with IsComponentsV2 flag set',
error: 'Cannot set content, embeds, stickers, poll, or shared client theme with IsComponentsV2 flag set',
}),
})
.refine(
@@ -100,8 +117,11 @@ const messageNoComponentsV2Predicate = baseMessagePredicate
data.poll !== undefined ||
(data.attachments !== undefined && data.attachments.length > 0) ||
(data.components !== undefined && data.components.length > 0) ||
(data.sticker_ids !== undefined && data.sticker_ids.length > 0),
{ error: 'Messages must have content, embeds, a poll, attachments, components or stickers' },
(data.sticker_ids !== undefined && data.sticker_ids.length > 0) ||
data.shared_client_theme !== undefined,
{
error: 'Messages must have content, embeds, a poll, attachments, components, stickers, or a shared client theme',
},
);
const allTopLevelComponentsPredicate = z
@@ -134,6 +154,7 @@ const messageComponentsV2Predicate = baseMessagePredicate.extend({
embeds: z.array(z.never()).nullish(),
sticker_ids: z.array(z.never()).nullish(),
poll: z.null().optional(),
shared_client_theme: z.null().optional(),
});
export const messagePredicate = z.union([messageNoComponentsV2Predicate, messageComponentsV2Predicate]);

View File

@@ -17,6 +17,7 @@ import type {
APISeparatorComponent,
APITextDisplayComponent,
APIMessageTopLevelComponent,
APIMessageSharedClientTheme,
} from 'discord-api-types/v10';
import { ActionRowBuilder } from '../components/ActionRow.js';
import { ComponentBuilder } from '../components/Component.js';
@@ -35,13 +36,14 @@ import { AllowedMentionsBuilder } from './AllowedMentions.js';
import { fileBodyMessagePredicate, messagePredicate } from './Assertions.js';
import { AttachmentBuilder } from './Attachment.js';
import { MessageReferenceBuilder } from './MessageReference.js';
import { SharedClientThemeBuilder } from './SharedClientTheme.js';
import { EmbedBuilder } from './embed/Embed.js';
import { PollBuilder } from './poll/Poll.js';
export interface MessageBuilderData extends Partial<
Omit<
RESTPostAPIChannelMessageJSONBody,
'allowed_mentions' | 'attachments' | 'components' | 'embeds' | 'message_reference' | 'poll'
'allowed_mentions' | 'attachments' | 'components' | 'embeds' | 'message_reference' | 'poll' | 'shared_client_theme'
>
> {
allowed_mentions?: AllowedMentionsBuilder;
@@ -50,6 +52,7 @@ export interface MessageBuilderData extends Partial<
embeds: EmbedBuilder[];
message_reference?: MessageReferenceBuilder;
poll?: PollBuilder;
shared_client_theme?: SharedClientThemeBuilder;
}
/**
@@ -90,7 +93,16 @@ export class MessageBuilder
* @param data - The API data to create this message with
*/
public constructor(data: Partial<RESTPostAPIChannelMessageJSONBody> = {}) {
const { attachments = [], embeds = [], components = [], message_reference, poll, allowed_mentions, ...rest } = data;
const {
attachments = [],
embeds = [],
components = [],
message_reference,
poll,
allowed_mentions,
shared_client_theme,
...rest
} = data;
this.data = {
...structuredClone(rest),
@@ -100,6 +112,7 @@ export class MessageBuilder
poll: poll && new PollBuilder(poll),
components: components.map((component) => createComponentBuilder(component)),
message_reference: message_reference && new MessageReferenceBuilder(message_reference),
shared_client_theme: shared_client_theme && new SharedClientThemeBuilder(shared_client_theme),
};
}
@@ -636,6 +649,39 @@ export class MessageBuilder
return this;
}
/**
* Sets the shared client theme for this message.
*
* @param theme - The shared client theme to set
*/
public setSharedClientTheme(
theme:
| APIMessageSharedClientTheme
| SharedClientThemeBuilder
| ((builder: SharedClientThemeBuilder) => SharedClientThemeBuilder),
): this {
this.data.shared_client_theme = resolveBuilder(theme, SharedClientThemeBuilder);
return this;
}
/**
* Updates the shared client theme for this message (and creates it if it doesn't exist).
*
* @param updater - The function to update the shared client theme with
*/
public updateSharedClientTheme(updater: (builder: SharedClientThemeBuilder) => void): this {
updater((this.data.shared_client_theme ??= new SharedClientThemeBuilder()));
return this;
}
/**
* Clears the shared client theme for this message.
*/
public clearSharedClientTheme(): this {
this.data.shared_client_theme = undefined;
return this;
}
/**
* Serializes this builder to API-compatible JSON data.
*
@@ -644,7 +690,8 @@ export class MessageBuilder
* @param validationOverride - Force validation to run/not run regardless of your global preference
*/
public toJSON(validationOverride?: boolean): RESTPostAPIChannelMessageJSONBody {
const { poll, allowed_mentions, attachments, embeds, components, message_reference, ...rest } = this.data;
const { poll, allowed_mentions, attachments, embeds, components, message_reference, shared_client_theme, ...rest } =
this.data;
const data = {
...structuredClone(rest),
@@ -656,6 +703,7 @@ export class MessageBuilder
// Here, the messagePredicate does specific constraints rather than using the componentPredicate
components: components.map((component) => component.toJSON(validationOverride)),
message_reference: message_reference?.toJSON(false),
shared_client_theme: shared_client_theme?.toJSON(false),
};
validate(messagePredicate, data, validationOverride);

View File

@@ -0,0 +1,91 @@
import type { JSONEncodable } from '@discordjs/util';
import type { APIMessageSharedClientTheme, BaseThemeType } from 'discord-api-types/v10';
import { normalizeArray, type RestOrArray } from '../util/normalizeArray.js';
import { validate } from '../util/validation.js';
import { sharedClientThemePredicate } from './Assertions.js';
/**
* A builder that creates API-compatible JSON data for shared client themes.
*/
export class SharedClientThemeBuilder implements JSONEncodable<APIMessageSharedClientTheme> {
/**
* The API data associated with this shared client theme.
*/
private readonly data: Partial<APIMessageSharedClientTheme>;
/**
* Creates a new shared client theme builder.
*
* @param data - The API data to create this shared client theme with
*/
public constructor(data: Partial<APIMessageSharedClientTheme> = {}) {
this.data = structuredClone(data);
}
/**
* Sets the colors of this theme.
*
* @remarks
* A maximum of 5 hexadecimal-encoded colors may be provided.
* @param colors - The hexadecimal-encoded colors to set (e.g. `'5865F2'`)
*/
public setColors(...colors: RestOrArray<string>): this {
this.data.colors = normalizeArray(colors);
return this;
}
/**
* Sets the gradient angle of this theme.
*
* @remarks
* The value must be between `0` and `360` (inclusive).
* @param angle - The gradient angle (direction of theme colors)
*/
public setGradientAngle(angle: number): this {
this.data.gradient_angle = angle;
return this;
}
/**
* Sets the base mix (intensity) of this theme.
*
* @remarks
* The value must be between `0` and `100` (inclusive).
* @param baseMix - The base mix intensity
*/
public setBaseMix(baseMix: number): this {
this.data.base_mix = baseMix;
return this;
}
/**
* Sets the base theme (mode) of this theme.
*
* @param baseTheme - The base theme mode
*/
public setBaseTheme(baseTheme: BaseThemeType): this {
this.data.base_theme = baseTheme;
return this;
}
/**
* Clears the base theme of this theme.
*/
public clearBaseTheme(): this {
this.data.base_theme = undefined;
return this;
}
/**
* Serializes this builder to API-compatible JSON data.
*
* Note that by disabling validation, there is no guarantee that the resulting object will be valid.
*
* @param validationOverride - Force validation to run/not run regardless of your global preference
*/
public toJSON(validationOverride?: boolean): APIMessageSharedClientTheme {
const data = structuredClone(this.data);
validate(sharedClientThemePredicate, data, validationOverride);
return data as APIMessageSharedClientTheme;
}
}

View File

@@ -65,5 +65,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/collection
[npm]: https://www.npmjs.com/package/@discordjs/collection
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -0,0 +1,16 @@
import { expectTypeOf, test } from 'vitest';
import { Collection, type ReadonlyCollection } from '../src/index.js';
test('ReadonlyCollection#tap preserves the readonly type', () => {
const readonly: ReadonlyCollection<string, number> = new Collection([['a', 1]]);
expectTypeOf(readonly.tap(() => {})).toEqualTypeOf<ReadonlyCollection<string, number>>();
expectTypeOf(readonly.tap(() => {}, null)).toEqualTypeOf<ReadonlyCollection<string, number>>();
});
test('ReadonlyCollection#each preserves the readonly type', () => {
const readonly: ReadonlyCollection<string, number> = new Collection([['a', 1]]);
expectTypeOf(readonly.each(() => {})).toEqualTypeOf<ReadonlyCollection<string, number>>();
expectTypeOf(readonly.each(() => {}, null)).toEqualTypeOf<ReadonlyCollection<string, number>>();
});

View File

@@ -5,9 +5,22 @@
*/
export type ReadonlyCollection<Key, Value> = Omit<
Collection<Key, Value>,
keyof Map<Key, Value> | 'ensure' | 'reverse' | 'sort' | 'sweep'
keyof Map<Key, Value> | 'each' | 'ensure' | 'reverse' | 'sort' | 'sweep' | 'tap'
> &
ReadonlyMap<Key, Value>;
ReadonlyMap<Key, Value> & {
each(
fn: (value: Value, key: Key, collection: ReadonlyCollection<Key, Value>) => void,
): ReadonlyCollection<Key, Value>;
each<This>(
fn: (this: This, value: Value, key: Key, collection: ReadonlyCollection<Key, Value>) => void,
thisArg: This,
): ReadonlyCollection<Key, Value>;
tap(fn: (collection: ReadonlyCollection<Key, Value>) => void): ReadonlyCollection<Key, Value>;
tap<This>(
fn: (this: This, collection: ReadonlyCollection<Key, Value>) => void,
thisArg: This,
): ReadonlyCollection<Key, Value>;
};
export interface Collection<Key, Value> {
/**

View File

@@ -126,5 +126,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/core
[npm]: https://www.npmjs.com/package/@discordjs/core
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -3,6 +3,7 @@
import type { RequestData, REST } from '@discordjs/rest';
import {
Routes,
type RESTGetAPIApplicationActivityInstanceResult,
type RESTGetAPIApplicationEmojiResult,
type RESTGetAPIApplicationEmojisResult,
type RESTGetCurrentApplicationResult,
@@ -136,4 +137,23 @@ export class ApplicationsAPI {
) {
await this.rest.delete(Routes.applicationEmoji(applicationId, emojiId), { auth, signal });
}
/**
* Fetches an activity instance of an application
*
* @see {@link https://docs.discord.com/developers/resources/application#get-application-activity-instance}
* @param applicationId - The id of the application to fetch the activity instance of
* @param instanceId - The id of the activity instance to fetch
* @param options - The options for fetching the activity instance
*/
public async getActivityInstance(
applicationId: Snowflake,
instanceId: string,
{ auth, signal }: Pick<RequestData, 'auth' | 'signal'> = {},
) {
return this.rest.get(Routes.applicationActivityInstance(applicationId, instanceId), {
auth,
signal,
}) as Promise<RESTGetAPIApplicationActivityInstanceResult>;
}
}

View File

@@ -5,6 +5,7 @@ import {
Routes,
type RESTGetAPIChannelThreadMemberQuery,
type RESTGetAPIChannelThreadMemberResult,
type RESTGetAPIChannelThreadMembersQuery,
type RESTGetAPIChannelThreadMembersResult,
type Snowflake,
} from 'discord-api-types/v10';
@@ -112,16 +113,22 @@ export class ThreadsAPI {
}
/**
* Fetches all members of a thread
* Fetches members of a thread
*
* @see {@link https://discord.com/developers/docs/resources/channel#list-thread-members}
* @param threadId - The id of the thread to fetch the members from
* @param query - The query for fetching the members
* @param options - The options for fetching the members
*/
public async getAllMembers(threadId: Snowflake, { auth, signal }: Pick<RequestData, 'auth' | 'signal'> = {}) {
public async getMembers(
threadId: Snowflake,
query: RESTGetAPIChannelThreadMembersQuery = {},
{ auth, signal }: Pick<RequestData, 'auth' | 'signal'> = {},
) {
return this.rest.get(Routes.threadMembers(threadId), {
auth,
signal,
query: makeURLSearchParams(query),
}) as Promise<RESTGetAPIChannelThreadMembersResult>;
}
}

View File

@@ -142,7 +142,7 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/discord.js
[npm]: https://www.npmjs.com/package/discord.js
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[rpc]: https://www.npmjs.com/package/discord-rpc
[rpc-source]: https://github.com/discordjs/RPC
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -1,5 +1,6 @@
'use strict';
const { ChannelType } = require('discord-api-types/v10');
const { Poll } = require('../../structures/Poll.js');
const { PollAnswer } = require('../../structures/PollAnswer.js');
const { Partials } = require('../../util/Partials.js');
@@ -33,10 +34,16 @@ class Action {
const payloadData = {};
const id = data.channel_id ?? data.id;
if (!('recipients' in data)) {
// Try to resolve the recipient, but do not add the client user.
if ('recipients' in data) {
// Try to resolve the recipient, but do not add if already existing in recipients.
const recipient = data.author ?? data.user ?? { id: data.user_id };
if (recipient.id !== this.client.user.id) payloadData.recipients = [recipient];
if (!data.recipients.some(existingRecipient => recipient.id === existingRecipient.id)) {
payloadData.recipients = [...data.recipients, recipient];
}
} else if (data.type === ChannelType.DM || data.type === ChannelType.GroupDM) {
// Try to resolve the recipient.
const recipient = data.author ?? data.user ?? { id: data.user_id };
payloadData.recipients = [recipient];
}
if (id !== undefined) payloadData.id = id;

View File

@@ -21,7 +21,9 @@ class InteractionCreateAction extends Action {
const client = this.client;
// Resolve and cache partial channels for Interaction#channel getter
const channel = data.channel && this.getChannel(data.channel);
const channel =
data.channel &&
this.getChannel({ ...data.channel, ...('recipients' in data.channel ? { user: data.user } : undefined) });
// Do not emit this for interactions that cache messages that are non-text-based.
let InteractionClass;

View File

@@ -10,6 +10,7 @@ class MessageCreateAction extends Action {
id: data.channel_id,
author: data.author,
...('guild_id' in data && { guild_id: data.guild_id }),
...('channel_type' in data && { type: data.channel_type }),
});
if (channel) {
if (!channel.isTextBased()) return {};

View File

@@ -103,6 +103,8 @@ exports.VoiceStateManager = require('./managers/VoiceStateManager.js').VoiceStat
// Structures
exports.ActionRow = require('./structures/ActionRow.js').ActionRow;
exports.Activity = require('./structures/Presence.js').Activity;
exports.ActivityInstance = require('./structures/ActivityInstance.js').ActivityInstance;
exports.ActivityLocation = require('./structures/ActivityLocation.js').ActivityLocation;
exports.AnnouncementChannel = require('./structures/AnnouncementChannel.js').AnnouncementChannel;
exports.AnonymousGuild = require('./structures/AnonymousGuild.js').AnonymousGuild;
exports.AuthorizingIntegrationOwners =

View File

@@ -66,7 +66,14 @@ class UserManager extends CachedManager {
}
const data = await this.client.rest.post(Routes.userChannels(), { body: { recipient_id: id } });
return this.client.channels._add(data, null, { cache });
return this.client.channels._add(
{
...data,
...('recipients' in data ? { recipients: [...data.recipients, { id: this.client.user.id }] } : undefined),
},
null,
{ cache },
);
}
/**

View File

@@ -7,6 +7,7 @@ const { setTimeout: sleep } = require('node:timers/promises');
const { Collection } = require('@discordjs/collection');
const { range } = require('@discordjs/util');
const { AsyncEventEmitter } = require('@vladfrangu/async_event_emitter');
const { APIVersion, RouteBases } = require('discord-api-types/v10');
const { DiscordjsError, DiscordjsTypeError, DiscordjsRangeError, ErrorCodes } = require('../errors/index.js');
const { fetchRecommendedShardCount } = require('../util/Util.js');
const { Shard } = require('./Shard.js');
@@ -43,6 +44,8 @@ class ShardingManager extends AsyncEventEmitter {
* @property {string[]} [shardArgs=[]] Arguments to pass to the shard script when spawning
* @property {string[]} [execArgv=[]] Arguments to pass to the shard script executable when spawning
* @property {string} [token] Token to use for automatic shard count and passing to shards
* @property {string} [api='https://discord.com/api'] The base API URL
* @property {string} [version='10'] The API version to use
*/
/**
@@ -164,6 +167,20 @@ class ShardingManager extends AsyncEventEmitter {
*/
this.token = _options.token?.replace(/^bot\s*/i, '') ?? null;
/**
* The base API URL
*
* @type {string}
*/
this.api = _options.api ?? RouteBases.api;
/**
* The API version to use
*
* @type {string}
*/
this.version = _options.version ?? APIVersion;
/**
* A collection of shards that this manager has spawned
*
@@ -217,7 +234,10 @@ class ShardingManager extends AsyncEventEmitter {
let shardAmount = amount;
if (shardAmount === 'auto') {
// eslint-disable-next-line require-atomic-updates
shardAmount = await fetchRecommendedShardCount(this.token);
shardAmount = await fetchRecommendedShardCount(this.token, {
api: this.api,
version: this.version,
});
} else {
if (typeof shardAmount !== 'number' || Number.isNaN(shardAmount)) {
throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'Amount of shards', 'a number.');

View File

@@ -0,0 +1,52 @@
'use strict';
const { ActivityLocation } = require('./ActivityLocation.js');
const { Base } = require('./Base.js');
/**
* Represents an activity instance.
*
* @extends {Base}
*/
class ActivityInstance extends Base {
constructor(client, data) {
super(client);
/**
* The id of the application
*
* @type {Snowflake}
*/
this.applicationId = data.application_id;
/**
* The activity instance id
*
* @type {string}
*/
this.instanceId = data.instance_id;
/**
* The unique identifier for the launch
*
* @type {Snowflake}
*/
this.launchId = data.launch_id;
/**
* The location the instance is running in
*
* @type {ActivityLocation}
*/
this.location = new ActivityLocation(client, data.location);
/**
* The ids of the users connected to the instance
*
* @type {Snowflake[]}
*/
this.users = data.users;
}
}
exports.ActivityInstance = ActivityInstance;

View File

@@ -0,0 +1,65 @@
'use strict';
const { Base } = require('./Base.js');
/**
* Represents the location of an activity instance.
*
* @extends {Base}
*/
class ActivityLocation extends Base {
constructor(client, data) {
super(client);
/**
* The unique identifier for the location
*
* @type {string}
*/
this.id = data.id;
/**
* The kind of location
*
* @type {ActivityLocationKind}
*/
this.kind = data.kind;
/**
* The id of the channel
*
* @type {Snowflake}
*/
this.channelId = data.channel_id;
/**
* The id of the guild
*
* @type {?Snowflake}
*/
this.guildId = data.guild_id ?? null;
}
/**
* The channel of this activity location
*
* @type {?Channel}
* @readonly
*/
get channel() {
return this.client.channels.cache.get(this.channelId) ?? null;
}
/**
* The guild of this activity location
*
* @type {?Guild}
* @readonly
*/
get guild() {
if (!this.guildId) return null;
return this.client.guilds.cache.get(this.guildId) ?? null;
}
}
exports.ActivityLocation = ActivityLocation;

View File

@@ -9,6 +9,7 @@ const { SubscriptionManager } = require('../managers/SubscriptionManager.js');
const { ApplicationFlagsBitField } = require('../util/ApplicationFlagsBitField.js');
const { resolveImage } = require('../util/DataResolver.js');
const { PermissionsBitField } = require('../util/PermissionsBitField.js');
const { ActivityInstance } = require('./ActivityInstance.js');
const { ApplicationRoleConnectionMetadata } = require('./ApplicationRoleConnectionMetadata.js');
const { SKU } = require('./SKU.js');
const { Team } = require('./Team.js');
@@ -446,6 +447,17 @@ class ClientApplication extends Application {
const skus = await this.client.rest.get(Routes.skus(this.id));
return skus.reduce((coll, sku) => coll.set(sku.id, new SKU(this.client, sku)), new Collection());
}
/**
* Fetches an activity instance for this application.
*
* @param {string} instanceId The id of the activity instance
* @returns {Promise<ActivityInstance>}
*/
async fetchActivityInstance(instanceId) {
const data = await this.client.rest.get(Routes.applicationActivityInstance(this.id, instanceId));
return new ActivityInstance(this.client, data);
}
}
exports.ClientApplication = ClientApplication;

View File

@@ -97,7 +97,7 @@ class CommandInteractionOptionResolver {
*
* @param {string} name The name of the option.
* @param {ApplicationCommandOptionType[]} allowedTypes The allowed types of the option.
* @param {string[]} properties The properties to check for for `required`.
* @param {string[]} properties The properties to check for `required`.
* @param {boolean} required Whether to throw an error if the option is not found.
* @returns {?CommandInteractionOption} The option, if found.
* @private

View File

@@ -32,17 +32,19 @@ class DMChannel extends BaseChannel {
super._patch(data);
if (data.recipients) {
const recipient = data.recipients[0];
/**
* The recipient's id
* The recipients' ids
*
* @type {Snowflake}
* @type {Snowflake[]}
*/
this.recipientId = recipient.id;
this.recipientIds = [
...new Set([...(this.recipientIds ?? []), ...data.recipients.map(recipient => recipient.id)]),
];
if ('username' in recipient || this.client.options.partials.includes(Partials.User)) {
this.client.users._add(recipient);
for (const recipient of data.recipients) {
if ('username' in recipient || this.client.options.partials.includes(Partials.User)) {
this.client.users._add(recipient);
}
}
}
@@ -78,7 +80,21 @@ class DMChannel extends BaseChannel {
}
/**
* The recipient on the other end of the DM
* The recipient's id, if this is a DMChannel with the client user.
*
* @type {?Snowflake}
* @readonly
*/
get recipientId() {
if (this.recipientIds.includes(this.client.user.id)) {
return this.recipientIds.find(recipientId => recipientId !== this.client.user.id) ?? null;
}
return null;
}
/**
* The recipient on the other end of the DM, if this is a DMChannel with the client user.
*
* @type {?User}
* @readonly

View File

@@ -499,6 +499,34 @@ class Message extends Base {
} else {
this.call ??= null;
}
/**
* The shared client theme sent with this message
*
* @typedef {Object} SharedClientTheme
* @property {string[]} colors The hexadecimal-encoded colors of the theme (max of 5)
* @property {number} gradientAngle The direction of the theme's colors (0360)
* @property {number} baseMix The intensity of the theme's colors (0100)
* @property {?BaseThemeType} [baseTheme] The mode of the theme
*/
if (data.shared_client_theme) {
/**
* The shared client theme sent with this message
*
* @type {?SharedClientTheme}
*/
this.sharedClientTheme = {
colors: data.shared_client_theme.colors,
gradientAngle: data.shared_client_theme.gradient_angle,
baseMix: data.shared_client_theme.base_mix,
};
if ('base_theme' in data.shared_client_theme) {
this.sharedClientTheme.baseTheme = data.shared_client_theme.base_theme;
}
} else {
this.sharedClientTheme ??= null;
}
}
/**

View File

@@ -231,6 +231,18 @@ class MessagePayload {
};
}
let shared_client_theme;
if (this.options.sharedClientTheme) {
shared_client_theme = isJSONEncodable(this.options.sharedClientTheme)
? this.options.sharedClientTheme.toJSON()
: {
colors: this.options.sharedClientTheme.colors,
gradient_angle: this.options.sharedClientTheme.gradientAngle,
base_mix: this.options.sharedClientTheme.baseMix,
base_theme: this.options.sharedClientTheme.baseTheme,
};
}
this.body = {
content,
tts,
@@ -253,6 +265,7 @@ class MessagePayload {
thread_name: threadName,
applied_tags: appliedTags,
poll,
shared_client_theme,
};
return this;
}

View File

@@ -79,7 +79,7 @@ class ModalComponentResolver {
*
* @param {string} customId The custom id of the component.
* @param {ComponentType[]} allowedTypes The allowed types of the component.
* @param {string[]} properties The properties to check for for `required`.
* @param {string[]} properties The properties to check for `required`.
* @param {boolean} required Whether to throw an error if the component value(s) are not found.
* @returns {ModalData} The option, if found.
* @private

View File

@@ -141,9 +141,9 @@ class Role extends Base {
}
/**
* The tags this role has
* The tags a role has
*
* @type {?Object}
* @typedef {Object} RoleTagData
* @property {Snowflake} [botId] The id of the bot this role belongs to
* @property {Snowflake|string} [integrationId] The id of the integration this role belongs to
* @property {true} [premiumSubscriberRole] Whether this is the guild's premium subscription role
@@ -151,6 +151,12 @@ class Role extends Base {
* @property {true} [availableForPurchase] Whether this role is available for purchase
* @property {true} [guildConnections] Whether this role is a guild's linked role
*/
/**
* The tags this role has
*
* @type {?RoleTagData}
*/
this.tags = data.tags ? {} : null;
if (data.tags) {
if ('bot_id' in data.tags) {

View File

@@ -5,6 +5,11 @@
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ActivityFlags}
*/
/**
* @external ActivityLocationKind
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ActivityLocationKind}
*/
/**
* @external ActivityType
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ActivityType}
@@ -340,6 +345,11 @@
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/AuditLogEvent}
*/
/**
* @external BaseThemeType
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/BaseThemeType}
*/
/**
* @external ButtonStyle
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ButtonStyle}

View File

@@ -3,7 +3,7 @@
const { parse } = require('node:path');
const { Collection } = require('@discordjs/collection');
const { lazy } = require('@discordjs/util');
const { ChannelType, RouteBases, Routes } = require('discord-api-types/v10');
const { APIVersion, ChannelType, Routes, RouteBases } = require('discord-api-types/v10');
const { fetch } = require('undici');
const { Colors } = require('./Colors.js');
// eslint-disable-next-line import-x/order
@@ -67,6 +67,8 @@ function flatten(obj, ...props) {
* @typedef {Object} FetchRecommendedShardCountOptions
* @property {number} [guildsPerShard=1000] Number of guilds assigned per shard
* @property {number} [multipleOf=1] The multiple the shard count should round up to. (16 for large bot sharding)
* @property {string} [api='https://discord.com/api'] The base API URL
* @property {string} [version='10'] The API version to use
*/
/**
@@ -76,9 +78,12 @@ function flatten(obj, ...props) {
* @param {FetchRecommendedShardCountOptions} [options] Options for fetching the recommended shard count
* @returns {Promise<number>} The recommended number of shards
*/
async function fetchRecommendedShardCount(token, { guildsPerShard = 1_000, multipleOf = 1 } = {}) {
async function fetchRecommendedShardCount(
token,
{ guildsPerShard = 1_000, multipleOf = 1, api = RouteBases.api, version = APIVersion } = {},
) {
if (!token) throw new DiscordjsError(ErrorCodes.TokenMissing);
const response = await fetch(RouteBases.api + Routes.gatewayBot(), {
const response = await fetch(`${api}/v${version}${Routes.gatewayBot()}`, {
method: 'GET',
headers: { Authorization: `Bot ${token.replace(/^bot\s*/i, '')}` },
});

View File

@@ -9,6 +9,9 @@ import { WebSocketManager, WebSocketManagerOptions } from '@discordjs/ws';
import { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';
import {
ActivityFlags,
APIActivityInstance,
APIActivityLocation,
ActivityLocationKind,
ActivityType,
APIActionRowComponent,
APIApplicationCommand,
@@ -61,6 +64,7 @@ import {
APIMessageComponentInteraction,
APIMessageMentionableSelectInteractionData,
APIMessageRoleSelectInteractionData,
APIMessageSharedClientTheme,
APIMessageStringSelectInteractionData,
APIMessageTopLevelComponent,
APIMessageUserSelectInteractionData,
@@ -115,6 +119,7 @@ import {
AutoModerationRuleEventType,
AutoModerationRuleKeywordPresetType,
AutoModerationRuleTriggerType,
BaseThemeType,
ButtonStyle,
ChannelFlags,
ChannelType,
@@ -246,6 +251,25 @@ export class Activity {
public toString(): string;
}
export class ActivityInstance extends Base {
private constructor(client: Client<true>, data: APIActivityInstance);
public applicationId: Snowflake;
public instanceId: string;
public launchId: Snowflake;
public location: ActivityLocation;
public users: Snowflake[];
}
export class ActivityLocation extends Base {
private constructor(client: Client<true>, data: APIActivityLocation);
public id: string;
public kind: ActivityLocationKind;
public channelId: Snowflake;
public guildId: Snowflake | null;
public get channel(): Channel | null;
public get guild(): Guild | null;
}
export type ActivityFlagsString = keyof typeof ActivityFlags;
export interface BaseComponentData {
@@ -1004,6 +1028,7 @@ export class ClientApplication extends Application {
public roleConnectionsVerificationURL: string | null;
public edit(options: ClientApplicationEditOptions): Promise<ClientApplication>;
public fetch(): Promise<ClientApplication>;
public fetchActivityInstance(instanceId: string): Promise<ActivityInstance>;
public fetchRoleConnectionMetadataRecords(): Promise<ApplicationRoleConnectionMetadata[]>;
public fetchSKUs(): Promise<Collection<Snowflake, SKU>>;
public editRoleConnectionMetadataRecords(
@@ -1292,7 +1317,8 @@ export interface DMChannel
export class DMChannel extends BaseChannel {
private constructor(client: Client<true>, data?: RawDMChannelData);
public flags: Readonly<ChannelFlagsBitField>;
public recipientId: Snowflake;
public get recipientId(): Snowflake | null;
public recipientIds: Snowflake[];
public get recipient(): User | null;
public type: ChannelType.DM;
public fetch(force?: boolean): Promise<this>;
@@ -2128,6 +2154,13 @@ export interface MessageCall {
participants: readonly Snowflake[];
}
export interface SharedClientTheme {
baseMix: number;
baseTheme?: BaseThemeType | null;
colors: readonly string[];
gradientAngle: number;
}
export type MessageComponentType =
| ComponentType.Button
| ComponentType.ChannelSelect
@@ -2223,6 +2256,7 @@ export class Message<InGuild extends boolean = boolean> extends Base {
public tts: boolean;
public poll: Poll | null;
public call: MessageCall | null;
public sharedClientTheme: SharedClientTheme | null;
public type: MessageType;
public get url(): string;
public webhookId: Snowflake | null;
@@ -3266,6 +3300,8 @@ export class ShardingManager extends AsyncEventEmitter<ShardingManagerEventTypes
public token: string | null;
public totalShards: number | 'auto';
public shardList: number[] | 'auto';
public api: string;
public version: string;
public broadcast(message: unknown): Promise<Shard[]>;
public broadcastEval<Result>(fn: (client: Client) => Awaitable<Result>): Promise<Serialized<Result>[]>;
public broadcastEval<Result, Context>(
@@ -3288,8 +3324,10 @@ export class ShardingManager extends AsyncEventEmitter<ShardingManagerEventTypes
}
export interface FetchRecommendedShardCountOptions {
api?: string;
guildsPerShard?: number;
multipleOf?: number;
version?: string;
}
export {
@@ -6789,6 +6827,7 @@ export interface BaseMessageCreateOptions
extends BaseMessageSendOptions, MessageOptionsPoll, MessageOptionsFlags, MessageOptionsTTS, MessageOptionsStickers {
enforceNonce?: boolean;
nonce?: number | string;
sharedClientTheme?: JSONEncodable<APIMessageSharedClientTheme> | SharedClientTheme;
}
export interface MessageCreateOptions extends BaseMessageCreateOptions {
@@ -7150,6 +7189,7 @@ export interface SetRolePositionOptions {
export type ShardingManagerMode = 'process' | 'worker';
export interface ShardingManagerOptions {
api?: string;
execArgv?: readonly string[];
mode?: ShardingManagerMode;
respawn?: boolean;
@@ -7158,6 +7198,7 @@ export interface ShardingManagerOptions {
silent?: boolean;
token?: string;
totalShards?: number | 'auto';
version?: string;
}
export interface ShowModalOptions {

View File

@@ -44,5 +44,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/docgen
[npm]: https://www.npmjs.com/package/@discordjs/docgen
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -82,5 +82,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/formatters
[npm]: https://www.npmjs.com/package/@discordjs/formatters
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -58,5 +58,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/next
[npm]: https://www.npmjs.com/package/@discordjs/next
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -66,5 +66,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/proxy
[npm]: https://www.npmjs.com/package/@discordjs/proxy
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -135,5 +135,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/rest
[npm]: https://www.npmjs.com/package/@discordjs/rest
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -42,5 +42,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord]: https://discord.gg/djs
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/scripts
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -66,5 +66,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/structures
[npm]: https://www.npmjs.com/package/@discordjs/structures
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -16,6 +16,7 @@ import type {
APIUserSelectComponent,
} from 'discord-api-types/v10';
import {
BaseThemeType,
MessageReferenceType,
MessageType,
MessageFlags,
@@ -28,6 +29,7 @@ import {
import { describe, expect, test } from 'vitest';
import { Attachment } from '../src/messages/Attachment.js';
import { Message } from '../src/messages/Message.js';
import { SharedClientTheme } from '../src/messages/SharedClientTheme.js';
import { ContainerComponent } from '../src/messages/components/ContainerComponent.js';
import { Embed } from '../src/messages/embeds/Embed.js';
import { User } from '../src/users/User.js';
@@ -476,3 +478,32 @@ describe('message with components', () => {
expect(containerInstance.spoiler).toBe(container.spoiler);
});
});
describe('SharedClientTheme structure', () => {
const rawTheme = {
colors: ['5865F2', '7258F2', '9858F2'],
gradient_angle: 45,
base_mix: 58,
base_theme: BaseThemeType.Dark,
};
test('GIVEN a shared client theme THEN exposes all getters correctly', () => {
const instance = new SharedClientTheme(rawTheme);
expect(instance.colors).toStrictEqual(rawTheme.colors);
expect(instance.gradientAngle).toBe(rawTheme.gradient_angle);
expect(instance.baseMix).toBe(rawTheme.base_mix);
expect(instance.baseTheme).toBe(BaseThemeType.Dark);
expect(instance.toJSON()).toEqual(rawTheme);
});
test('GIVEN a shared client theme without base_theme THEN baseTheme is undefined', () => {
const { base_theme: _, ...withoutTheme } = rawTheme;
const instance = new SharedClientTheme(withoutTheme);
expect(instance.baseTheme).toBeUndefined();
});
test('GIVEN a shared client theme with null base_theme THEN baseTheme is null', () => {
const instance = new SharedClientTheme({ ...rawTheme, base_theme: null });
expect(instance.baseTheme).toBeNull();
});
});

View File

@@ -13,7 +13,7 @@ import type { Partialize } from '../utils/types.js';
* Represents a message on Discord.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has substructures `Message`, `Channel`, `MessageActivity`, `MessageCall`, `MessageReference`, `Attachment`, `Application`, `ChannelMention`, `Reaction`, `Poll`, `ResolvedInteractionData`, `RoleSubscriptionData`, `Sticker`, all the different `Component`s, ... which need to be instantiated and stored by an extending class using it
* @remarks has substructures `Message`, `Channel`, `MessageActivity`, `MessageCall`, `MessageReference`, `SharedClientTheme`, `Attachment`, `Application`, `ChannelMention`, `Reaction`, `Poll`, `ResolvedInteractionData`, `RoleSubscriptionData`, `Sticker`, all the different `Component`s, ... which need to be instantiated and stored by an extending class using it
*/
export class Message<Omitted extends keyof APIMessage | '' = 'edited_timestamp' | 'timestamp'> extends Structure<
APIMessage,

View File

@@ -0,0 +1,57 @@
import type { APIMessageSharedClientTheme } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { kData } from '../utils/symbols.js';
import type { Partialize } from '../utils/types.js';
/**
* Represents the shared client theme sent with a Discord message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @see {@link https://docs.discord.com/developers/resources/message#shared-client-theme-object}
*/
export class SharedClientTheme<Omitted extends keyof APIMessageSharedClientTheme | '' = ''> extends Structure<
APIMessageSharedClientTheme,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each SharedClientTheme.
*/
public static override DataTemplate: Partial<APIMessageSharedClientTheme> = {};
/**
* @param data - The raw data received from the API for the shared client theme
*/
public constructor(data: Partialize<APIMessageSharedClientTheme, Omitted>) {
super(data);
}
/**
* The hexadecimal-encoded colors of this theme (max of 5)
*/
public get colors() {
return this[kData].colors;
}
/**
* The gradient angle (direction) of this theme's colors (0360)
*/
public get gradientAngle() {
return this[kData].gradient_angle;
}
/**
* The base mix (intensity) of this theme's colors (0100)
*/
public get baseMix() {
return this[kData].base_mix;
}
/**
* The base theme mode
*
* @see {@link https://docs.discord.com/developers/resources/message#base-theme-types}
*/
public get baseTheme() {
return this[kData].base_theme;
}
}

View File

@@ -14,3 +14,4 @@ export * from './ModalSubmitInteractionMetadata.js';
export * from './Reaction.js';
export * from './ReactionCountDetails.js';
export * from './RoleSubscriptionData.js';
export * from './SharedClientTheme.js';

View File

@@ -42,5 +42,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord]: https://discord.gg/djs
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/ui
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -63,5 +63,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/util
[npm]: https://www.npmjs.com/package/@discordjs/util
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -115,6 +115,6 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/voice
[npm]: https://www.npmjs.com/package/@discordjs/voice
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md
[voice-examples]: https://github.com/discordjs/voice-examples

View File

@@ -220,5 +220,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/ws
[npm]: https://www.npmjs.com/package/@discordjs/ws
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md