From 28dd65d3229dd722bf1322c0c7e83cbefee07c12 Mon Sep 17 00:00:00 2001 From: Kshitij Anurag <230598819+kshitijanurag@users.noreply.github.com> Date: Sat, 23 May 2026 04:01:00 +0530 Subject: [PATCH] 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 Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../__tests__/messages/message.test.ts | 91 ++++++++++++++++++- packages/builders/src/index.ts | 1 + packages/builders/src/messages/Assertions.ts | 29 +++++- packages/builders/src/messages/Message.ts | 54 ++++++++++- .../src/messages/SharedClientTheme.ts | 91 +++++++++++++++++++ packages/discord.js/src/structures/Message.js | 28 ++++++ .../src/structures/MessagePayload.js | 13 +++ packages/discord.js/src/util/APITypes.js | 5 + packages/discord.js/typings/index.d.ts | 11 +++ packages/structures/__tests__/message.test.ts | 31 +++++++ packages/structures/src/messages/Message.ts | 2 +- .../src/messages/SharedClientTheme.ts | 57 ++++++++++++ packages/structures/src/messages/index.ts | 1 + 13 files changed, 404 insertions(+), 10 deletions(-) create mode 100644 packages/builders/src/messages/SharedClientTheme.ts create mode 100644 packages/structures/src/messages/SharedClientTheme.ts diff --git a/packages/builders/__tests__/messages/message.test.ts b/packages/builders/__tests__/messages/message.test.ts index 01d94c880..e54b5dbc9 100644 --- a/packages/builders/__tests__/messages/message.test.ts +++ b/packages/builders/__tests__/messages/message.test.ts @@ -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, + }); }); }); }); diff --git a/packages/builders/src/index.ts b/packages/builders/src/index.ts index deeee34ab..b0f3d6e48 100644 --- a/packages/builders/src/index.ts +++ b/packages/builders/src/index.ts @@ -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'; diff --git a/packages/builders/src/messages/Assertions.ts b/packages/builders/src/messages/Assertions.ts index a913deac8..55fdd09ac 100644 --- a/packages/builders/src/messages/Assertions.ts +++ b/packages/builders/src/messages/Assertions.ts @@ -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]); diff --git a/packages/builders/src/messages/Message.ts b/packages/builders/src/messages/Message.ts index 66feafa48..976156508 100644 --- a/packages/builders/src/messages/Message.ts +++ b/packages/builders/src/messages/Message.ts @@ -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 = {}) { - 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); diff --git a/packages/builders/src/messages/SharedClientTheme.ts b/packages/builders/src/messages/SharedClientTheme.ts new file mode 100644 index 000000000..05a4e4964 --- /dev/null +++ b/packages/builders/src/messages/SharedClientTheme.ts @@ -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 { + /** + * The API data associated with this shared client theme. + */ + private readonly data: Partial; + + /** + * Creates a new shared client theme builder. + * + * @param data - The API data to create this shared client theme with + */ + public constructor(data: Partial = {}) { + 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): 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; + } +} diff --git a/packages/discord.js/src/structures/Message.js b/packages/discord.js/src/structures/Message.js index a0103ed67..203099080 100644 --- a/packages/discord.js/src/structures/Message.js +++ b/packages/discord.js/src/structures/Message.js @@ -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 (0–360) + * @property {number} baseMix The intensity of the theme's colors (0–100) + * @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; + } } /** diff --git a/packages/discord.js/src/structures/MessagePayload.js b/packages/discord.js/src/structures/MessagePayload.js index 597c47bbd..9c1037e1d 100644 --- a/packages/discord.js/src/structures/MessagePayload.js +++ b/packages/discord.js/src/structures/MessagePayload.js @@ -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; } diff --git a/packages/discord.js/src/util/APITypes.js b/packages/discord.js/src/util/APITypes.js index 1e11f8692..25b8bb56a 100644 --- a/packages/discord.js/src/util/APITypes.js +++ b/packages/discord.js/src/util/APITypes.js @@ -340,6 +340,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} diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 998b97a2a..bada3c717 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -61,6 +61,7 @@ import { APIMessageComponentInteraction, APIMessageMentionableSelectInteractionData, APIMessageRoleSelectInteractionData, + APIMessageSharedClientTheme, APIMessageStringSelectInteractionData, APIMessageTopLevelComponent, APIMessageUserSelectInteractionData, @@ -115,6 +116,7 @@ import { AutoModerationRuleEventType, AutoModerationRuleKeywordPresetType, AutoModerationRuleTriggerType, + BaseThemeType, ButtonStyle, ChannelFlags, ChannelType, @@ -2129,6 +2131,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 @@ -2224,6 +2233,7 @@ export class Message 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; @@ -6790,6 +6800,7 @@ export interface BaseMessageCreateOptions extends BaseMessageSendOptions, MessageOptionsPoll, MessageOptionsFlags, MessageOptionsTTS, MessageOptionsStickers { enforceNonce?: boolean; nonce?: number | string; + sharedClientTheme?: JSONEncodable | SharedClientTheme; } export interface MessageCreateOptions extends BaseMessageCreateOptions { diff --git a/packages/structures/__tests__/message.test.ts b/packages/structures/__tests__/message.test.ts index 5c739f7bd..049f62154 100644 --- a/packages/structures/__tests__/message.test.ts +++ b/packages/structures/__tests__/message.test.ts @@ -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(); + }); +}); diff --git a/packages/structures/src/messages/Message.ts b/packages/structures/src/messages/Message.ts index b30a46e77..a256f67f1 100644 --- a/packages/structures/src/messages/Message.ts +++ b/packages/structures/src/messages/Message.ts @@ -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 extends Structure< APIMessage, diff --git a/packages/structures/src/messages/SharedClientTheme.ts b/packages/structures/src/messages/SharedClientTheme.ts new file mode 100644 index 000000000..374f0c0e2 --- /dev/null +++ b/packages/structures/src/messages/SharedClientTheme.ts @@ -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 extends Structure< + APIMessageSharedClientTheme, + Omitted +> { + /** + * The template used for removing data from the raw data stored for each SharedClientTheme. + */ + public static override DataTemplate: Partial = {}; + + /** + * @param data - The raw data received from the API for the shared client theme + */ + public constructor(data: Partialize) { + 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 (0–360) + */ + public get gradientAngle() { + return this[kData].gradient_angle; + } + + /** + * The base mix (intensity) of this theme's colors (0–100) + */ + 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; + } +} diff --git a/packages/structures/src/messages/index.ts b/packages/structures/src/messages/index.ts index a64aa3e1a..abd36c2d5 100644 --- a/packages/structures/src/messages/index.ts +++ b/packages/structures/src/messages/index.ts @@ -14,3 +14,4 @@ export * from './ModalSubmitInteractionMetadata.js'; export * from './Reaction.js'; export * from './ReactionCountDetails.js'; export * from './RoleSubscriptionData.js'; +export * from './SharedClientTheme.js';