mirror of
https://github.com/discordjs/discord.js.git
synced 2026-06-01 08:30:08 +00:00
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>
This commit is contained in:
@@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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);
|
||||
|
||||
91
packages/builders/src/messages/SharedClientTheme.ts
Normal file
91
packages/builders/src/messages/SharedClientTheme.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
11
packages/discord.js/typings/index.d.ts
vendored
11
packages/discord.js/typings/index.d.ts
vendored
@@ -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<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;
|
||||
@@ -6790,6 +6800,7 @@ export interface BaseMessageCreateOptions
|
||||
extends BaseMessageSendOptions, MessageOptionsPoll, MessageOptionsFlags, MessageOptionsTTS, MessageOptionsStickers {
|
||||
enforceNonce?: boolean;
|
||||
nonce?: number | string;
|
||||
sharedClientTheme?: JSONEncodable<APIMessageSharedClientTheme> | SharedClientTheme;
|
||||
}
|
||||
|
||||
export interface MessageCreateOptions extends BaseMessageCreateOptions {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
57
packages/structures/src/messages/SharedClientTheme.ts
Normal file
57
packages/structures/src/messages/SharedClientTheme.ts
Normal 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 (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;
|
||||
}
|
||||
}
|
||||
@@ -14,3 +14,4 @@ export * from './ModalSubmitInteractionMetadata.js';
|
||||
export * from './Reaction.js';
|
||||
export * from './ReactionCountDetails.js';
|
||||
export * from './RoleSubscriptionData.js';
|
||||
export * from './SharedClientTheme.js';
|
||||
|
||||
Reference in New Issue
Block a user