mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-13 18:13:29 +01:00
refactor: make public builder props getters (#7422)
Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> Co-authored-by: Antonio Román <kyradiscord@gmail.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { APIActionRowComponent, ComponentType } from 'discord-api-types/v9';
|
||||
import { type APIActionRowComponent, ComponentType } from 'discord-api-types/v9';
|
||||
import type { ButtonComponent, SelectMenuComponent } from '..';
|
||||
import type { Component } from './Component';
|
||||
import { Component } from './Component';
|
||||
import { createComponent } from './Components';
|
||||
|
||||
export type MessageComponent = ActionRowComponent | ActionRow;
|
||||
@@ -8,16 +8,17 @@ export type MessageComponent = ActionRowComponent | ActionRow;
|
||||
export type ActionRowComponent = ButtonComponent | SelectMenuComponent;
|
||||
|
||||
// TODO: Add valid form component types
|
||||
|
||||
/**
|
||||
* Represents an action row component
|
||||
*/
|
||||
export class ActionRow<T extends ActionRowComponent = ActionRowComponent> implements Component {
|
||||
public readonly components: T[] = [];
|
||||
public readonly type = ComponentType.ActionRow;
|
||||
export class ActionRow<T extends ActionRowComponent = ActionRowComponent> extends Component<
|
||||
Omit<Partial<APIActionRowComponent> & { type: ComponentType.ActionRow }, 'components'>
|
||||
> {
|
||||
public readonly components: T[];
|
||||
|
||||
public constructor(data?: APIActionRowComponent & { type?: ComponentType.ActionRow }) {
|
||||
this.components = (data?.components.map(createComponent) ?? []) as T[];
|
||||
public constructor({ components, ...data }: Partial<APIActionRowComponent> = {}) {
|
||||
super({ type: ComponentType.ActionRow, ...data });
|
||||
this.components = (components?.map((c) => createComponent(c)) ?? []) as T[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,14 +35,14 @@ export class ActionRow<T extends ActionRowComponent = ActionRowComponent> implem
|
||||
* Sets the components in this action row
|
||||
* @param components The components to set this row to
|
||||
*/
|
||||
public setComponents(...components: T[]) {
|
||||
Reflect.set(this, 'components', [...components]);
|
||||
public setComponents(components: T[]) {
|
||||
this.components.splice(0, this.components.length, ...components);
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APIActionRowComponent {
|
||||
return {
|
||||
...this,
|
||||
...this.data,
|
||||
components: this.components.map((component) => component.toJSON()),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export function validateRequiredSelectMenuOptionParameters(label?: string, value
|
||||
export const urlValidator = z.string().url();
|
||||
|
||||
export function validateRequiredButtonParameters(
|
||||
style: ButtonStyle,
|
||||
style?: ButtonStyle,
|
||||
label?: string,
|
||||
emoji?: APIMessageComponentEmoji,
|
||||
customId?: string,
|
||||
|
||||
@@ -1,12 +1,33 @@
|
||||
import type { APIMessageComponent, ComponentType } from 'discord-api-types/v9';
|
||||
import type { JSONEncodable } from '../util/jsonEncodable';
|
||||
import type { APIBaseMessageComponent, APIMessageComponent, ComponentType } from 'discord-api-types/v9';
|
||||
|
||||
/**
|
||||
* Represents a discord component
|
||||
*/
|
||||
export interface Component extends JSONEncodable<APIMessageComponent> {
|
||||
export abstract class Component<
|
||||
DataType extends Partial<APIBaseMessageComponent<ComponentType>> & {
|
||||
type: ComponentType;
|
||||
} = APIBaseMessageComponent<ComponentType>,
|
||||
> implements JSONEncodable<APIMessageComponent>
|
||||
{
|
||||
/**
|
||||
* The API data associated with this component
|
||||
*/
|
||||
protected readonly data: DataType;
|
||||
|
||||
/**
|
||||
* Converts this component to an API-compatible JSON object
|
||||
*/
|
||||
public abstract toJSON(): APIMessageComponent;
|
||||
|
||||
public constructor(data: DataType) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of this component
|
||||
*/
|
||||
readonly type: ComponentType;
|
||||
public get type(): DataType['type'] {
|
||||
return this.data.type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,11 @@ export function createComponent<C extends MessageComponent>(data: C): C;
|
||||
export function createComponent(data: APIMessageComponent | MessageComponent): Component {
|
||||
switch (data.type) {
|
||||
case ComponentType.ActionRow:
|
||||
return data instanceof ActionRow ? data : new ActionRow(data);
|
||||
return (data instanceof ActionRow ? data : new ActionRow(data)) as Component;
|
||||
case ComponentType.Button:
|
||||
return data instanceof ButtonComponent ? data : new ButtonComponent(data);
|
||||
return (data instanceof ButtonComponent ? data : new ButtonComponent(data)) as Component;
|
||||
case ComponentType.SelectMenu:
|
||||
return data instanceof SelectMenuComponent ? data : new SelectMenuComponent(data);
|
||||
return (data instanceof SelectMenuComponent ? data : new SelectMenuComponent(data)) as Component;
|
||||
default:
|
||||
// @ts-expect-error
|
||||
throw new Error(`Cannot serialize component type: ${data.type as number}`);
|
||||
|
||||
@@ -10,6 +10,9 @@ import {
|
||||
} from '../Assertions';
|
||||
import { UnsafeButtonComponent } from './UnsafeButton';
|
||||
|
||||
/**
|
||||
* Represents a validated button component
|
||||
*/
|
||||
export class ButtonComponent extends UnsafeButtonComponent {
|
||||
public override setStyle(style: ButtonStyle) {
|
||||
return super.setStyle(buttonStyleValidator.parse(style));
|
||||
@@ -36,7 +39,7 @@ export class ButtonComponent extends UnsafeButtonComponent {
|
||||
}
|
||||
|
||||
public override toJSON(): APIButtonComponent {
|
||||
validateRequiredButtonParameters(this.style, this.label, this.emoji, this.custom_id, this.url);
|
||||
validateRequiredButtonParameters(this.style, this.label, this.emoji, this.customId, this.url);
|
||||
return super.toJSON();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,33 +3,59 @@ import {
|
||||
ButtonStyle,
|
||||
type APIMessageComponentEmoji,
|
||||
type APIButtonComponent,
|
||||
type APIButtonComponentWithURL,
|
||||
type APIButtonComponentWithCustomId,
|
||||
} from 'discord-api-types/v9';
|
||||
import type { Component } from '../Component';
|
||||
import { Component } from '../Component';
|
||||
|
||||
export class UnsafeButtonComponent implements Component {
|
||||
public readonly type = ComponentType.Button as const;
|
||||
public readonly style!: ButtonStyle;
|
||||
public readonly label?: string;
|
||||
public readonly emoji?: APIMessageComponentEmoji;
|
||||
public readonly disabled?: boolean;
|
||||
public readonly custom_id!: string;
|
||||
public readonly url!: string;
|
||||
/**
|
||||
* Represents a non-validated button component
|
||||
*/
|
||||
export class UnsafeButtonComponent extends Component<Partial<APIButtonComponent> & { type: ComponentType.Button }> {
|
||||
public constructor(data?: Partial<APIButtonComponent>) {
|
||||
super({ type: ComponentType.Button, ...data });
|
||||
}
|
||||
|
||||
public constructor(data?: APIButtonComponent & { type?: ComponentType.Button }) {
|
||||
/* eslint-disable @typescript-eslint/non-nullable-type-assertion-style */
|
||||
this.style = data?.style as ButtonStyle;
|
||||
this.label = data?.label;
|
||||
this.emoji = data?.emoji;
|
||||
this.disabled = data?.disabled;
|
||||
/**
|
||||
* The style of this button
|
||||
*/
|
||||
public get style() {
|
||||
return this.data.style;
|
||||
}
|
||||
|
||||
// This if/else makes typescript happy
|
||||
if (data?.style === ButtonStyle.Link) {
|
||||
this.url = data.url;
|
||||
} else {
|
||||
this.custom_id = data?.custom_id as string;
|
||||
}
|
||||
/**
|
||||
* The label of this button
|
||||
*/
|
||||
public get label() {
|
||||
return this.data.label;
|
||||
}
|
||||
|
||||
/* eslint-enable @typescript-eslint/non-nullable-type-assertion-style */
|
||||
/**
|
||||
* The emoji used in this button
|
||||
*/
|
||||
public get emoji() {
|
||||
return this.data.emoji;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this button is disabled
|
||||
*/
|
||||
public get disabled() {
|
||||
return this.data.disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* The custom id of this button (only defined on non-link buttons)
|
||||
*/
|
||||
public get customId(): string | undefined {
|
||||
return (this.data as APIButtonComponentWithCustomId).custom_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL of this button (only defined on link buttons)
|
||||
*/
|
||||
public get url(): string | undefined {
|
||||
return (this.data as APIButtonComponentWithURL).url;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,7 +63,7 @@ export class UnsafeButtonComponent implements Component {
|
||||
* @param style The style of the button
|
||||
*/
|
||||
public setStyle(style: ButtonStyle) {
|
||||
Reflect.set(this, 'style', style);
|
||||
this.data.style = style;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -46,16 +72,16 @@ export class UnsafeButtonComponent implements Component {
|
||||
* @param url The URL to open when this button is clicked
|
||||
*/
|
||||
public setURL(url: string) {
|
||||
Reflect.set(this, 'url', url);
|
||||
(this.data as APIButtonComponentWithURL).url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom Id for this button
|
||||
* @param customId The custom ID to use for this button
|
||||
* @param customId The custom id to use for this button
|
||||
*/
|
||||
public setCustomId(customId: string) {
|
||||
Reflect.set(this, 'custom_id', customId);
|
||||
(this.data as APIButtonComponentWithCustomId).custom_id = customId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -64,7 +90,7 @@ export class UnsafeButtonComponent implements Component {
|
||||
* @param emoji The emoji to display on this button
|
||||
*/
|
||||
public setEmoji(emoji: APIMessageComponentEmoji) {
|
||||
Reflect.set(this, 'emoji', emoji);
|
||||
this.data.emoji = emoji;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -73,7 +99,7 @@ export class UnsafeButtonComponent implements Component {
|
||||
* @param disabled Whether or not to disable this button or not
|
||||
*/
|
||||
public setDisabled(disabled: boolean) {
|
||||
Reflect.set(this, 'disabled', disabled);
|
||||
this.data.disabled = disabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -82,13 +108,14 @@ export class UnsafeButtonComponent implements Component {
|
||||
* @param label The label to display on this button
|
||||
*/
|
||||
public setLabel(label: string) {
|
||||
Reflect.set(this, 'label', label);
|
||||
this.data.label = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APIButtonComponent {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return {
|
||||
...this,
|
||||
};
|
||||
...this.data,
|
||||
} as APIButtonComponent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { UnsafeSelectMenuComponent } from './UnsafeSelectMenu';
|
||||
|
||||
/**
|
||||
* Represents a select menu component
|
||||
* Represents a validated select menu component
|
||||
*/
|
||||
export class SelectMenuComponent extends UnsafeSelectMenuComponent {
|
||||
public override setPlaceholder(placeholder: string) {
|
||||
@@ -33,7 +33,7 @@ export class SelectMenuComponent extends UnsafeSelectMenuComponent {
|
||||
}
|
||||
|
||||
public override toJSON(): APISelectMenuComponent {
|
||||
validateRequiredSelectMenuParameters(this.options, this.custom_id);
|
||||
validateRequiredSelectMenuParameters(this.options, this.customId);
|
||||
return super.toJSON();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
import { UnsafeSelectMenuOption } from './UnsafeSelectMenuOption';
|
||||
|
||||
/**
|
||||
* Represents an option within a select menu component
|
||||
* Represents a validated option within a select menu component
|
||||
*/
|
||||
export class SelectMenuOption extends UnsafeSelectMenuOption {
|
||||
public override setDescription(description: string) {
|
||||
|
||||
@@ -1,28 +1,54 @@
|
||||
import { ComponentType, type APISelectMenuComponent } from 'discord-api-types/v9';
|
||||
import type { Component } from '../Component';
|
||||
import { SelectMenuOption } from './SelectMenuOption';
|
||||
import { Component } from '../Component';
|
||||
import { UnsafeSelectMenuOption } from './UnsafeSelectMenuOption';
|
||||
|
||||
/**
|
||||
* Represents a non-validated select menu component
|
||||
*/
|
||||
export class UnsafeSelectMenuComponent implements Component {
|
||||
public readonly type = ComponentType.SelectMenu as const;
|
||||
public readonly options: SelectMenuOption[];
|
||||
public readonly placeholder?: string;
|
||||
public readonly min_values?: number;
|
||||
public readonly max_values?: number;
|
||||
public readonly custom_id!: string;
|
||||
public readonly disabled?: boolean;
|
||||
export class UnsafeSelectMenuComponent extends Component<
|
||||
Partial<Omit<APISelectMenuComponent, 'options'>> & { type: ComponentType.SelectMenu }
|
||||
> {
|
||||
public readonly options: UnsafeSelectMenuOption[];
|
||||
|
||||
public constructor(data?: APISelectMenuComponent) {
|
||||
this.options = data?.options.map((option) => new SelectMenuOption(option)) ?? [];
|
||||
this.placeholder = data?.placeholder;
|
||||
this.min_values = data?.min_values;
|
||||
this.max_values = data?.max_values;
|
||||
/* eslint-disable @typescript-eslint/non-nullable-type-assertion-style */
|
||||
this.custom_id = data?.custom_id as string;
|
||||
/* eslint-enable @typescript-eslint/non-nullable-type-assertion-style */
|
||||
this.disabled = data?.disabled;
|
||||
public constructor(data?: Partial<APISelectMenuComponent>) {
|
||||
const { options, ...initData } = data ?? {};
|
||||
super({ type: ComponentType.SelectMenu, ...initData });
|
||||
this.options = options?.map((o) => new UnsafeSelectMenuOption(o)) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* The placeholder for this select menu
|
||||
*/
|
||||
public get placeholder() {
|
||||
return this.data.placeholder;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum amount of options that can be selected
|
||||
*/
|
||||
public get maxValues() {
|
||||
return this.data.max_values;
|
||||
}
|
||||
|
||||
/**
|
||||
* The minimum amount of options that must be selected
|
||||
*/
|
||||
public get minValues() {
|
||||
return this.data.min_values;
|
||||
}
|
||||
|
||||
/**
|
||||
* The custom id of this select menu
|
||||
*/
|
||||
public get customId() {
|
||||
return this.data.custom_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this select menu is disabled
|
||||
*/
|
||||
public get disabled() {
|
||||
return this.data.disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,34 +56,34 @@ export class UnsafeSelectMenuComponent implements Component {
|
||||
* @param placeholder The placeholder to use for this select menu
|
||||
*/
|
||||
public setPlaceholder(placeholder: string) {
|
||||
Reflect.set(this, 'placeholder', placeholder);
|
||||
this.data.placeholder = placeholder;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets thes minimum values that must be selected in the select menu
|
||||
* Sets the minimum values that must be selected in the select menu
|
||||
* @param minValues The minimum values that must be selected
|
||||
*/
|
||||
public setMinValues(minValues: number) {
|
||||
Reflect.set(this, 'min_values', minValues);
|
||||
this.data.min_values = minValues;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets thes maximum values that must be selected in the select menu
|
||||
* Sets the maximum values that must be selected in the select menu
|
||||
* @param minValues The maximum values that must be selected
|
||||
*/
|
||||
public setMaxValues(maxValues: number) {
|
||||
Reflect.set(this, 'max_values', maxValues);
|
||||
this.data.max_values = maxValues;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom Id for this select menu
|
||||
* @param customId The custom ID to use for this select menu
|
||||
* @param customId The custom id to use for this select menu
|
||||
*/
|
||||
public setCustomId(customId: string) {
|
||||
Reflect.set(this, 'custom_id', customId);
|
||||
this.data.custom_id = customId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -66,7 +92,7 @@ export class UnsafeSelectMenuComponent implements Component {
|
||||
* @param disabled Whether or not this select menu is disabled
|
||||
*/
|
||||
public setDisabled(disabled: boolean) {
|
||||
Reflect.set(this, 'disabled', disabled);
|
||||
this.data.disabled = disabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -75,7 +101,7 @@ export class UnsafeSelectMenuComponent implements Component {
|
||||
* @param options The options to add to this select menu
|
||||
* @returns
|
||||
*/
|
||||
public addOptions(...options: SelectMenuOption[]) {
|
||||
public addOptions(...options: UnsafeSelectMenuOption[]) {
|
||||
this.options.push(...options);
|
||||
return this;
|
||||
}
|
||||
@@ -84,15 +110,16 @@ export class UnsafeSelectMenuComponent implements Component {
|
||||
* Sets the options on this select menu
|
||||
* @param options The options to set on this select menu
|
||||
*/
|
||||
public setOptions(...options: SelectMenuOption[]) {
|
||||
Reflect.set(this, 'options', [...options]);
|
||||
public setOptions(options: UnsafeSelectMenuOption[]) {
|
||||
this.options.splice(0, this.options.length, ...options);
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APISelectMenuComponent {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return {
|
||||
...this,
|
||||
options: this.options.map((option) => option.toJSON()),
|
||||
};
|
||||
...this.data,
|
||||
options: this.options.map((o) => o.toJSON()),
|
||||
} as APISelectMenuComponent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,20 +4,41 @@ import type { APIMessageComponentEmoji, APISelectMenuOption } from 'discord-api-
|
||||
* Represents a non-validated option within a select menu component
|
||||
*/
|
||||
export class UnsafeSelectMenuOption {
|
||||
public readonly label!: string;
|
||||
public readonly value!: string;
|
||||
public readonly description?: string;
|
||||
public readonly emoji?: APIMessageComponentEmoji;
|
||||
public readonly default?: boolean;
|
||||
public constructor(protected data: Partial<APISelectMenuOption> = {}) {}
|
||||
|
||||
public constructor(data?: APISelectMenuOption) {
|
||||
/* eslint-disable @typescript-eslint/non-nullable-type-assertion-style */
|
||||
this.label = data?.label as string;
|
||||
this.value = data?.value as string;
|
||||
/* eslint-enable @typescript-eslint/non-nullable-type-assertion-style */
|
||||
this.description = data?.description;
|
||||
this.emoji = data?.emoji;
|
||||
this.default = data?.default;
|
||||
/**
|
||||
* The label for this option
|
||||
*/
|
||||
public get label() {
|
||||
return this.data.label;
|
||||
}
|
||||
|
||||
/**
|
||||
* The value for this option
|
||||
*/
|
||||
public get value() {
|
||||
return this.data.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The description for this option
|
||||
*/
|
||||
public get description() {
|
||||
return this.data.description;
|
||||
}
|
||||
|
||||
/**
|
||||
* The emoji for this option
|
||||
*/
|
||||
public get emoji() {
|
||||
return this.data.emoji;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this option is selected by default
|
||||
*/
|
||||
public get default() {
|
||||
return this.data.default;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -25,7 +46,7 @@ export class UnsafeSelectMenuOption {
|
||||
* @param label The label to show on this option
|
||||
*/
|
||||
public setLabel(label: string) {
|
||||
Reflect.set(this, 'label', label);
|
||||
this.data.label = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -34,7 +55,7 @@ export class UnsafeSelectMenuOption {
|
||||
* @param value The value of this option
|
||||
*/
|
||||
public setValue(value: string) {
|
||||
Reflect.set(this, 'value', value);
|
||||
this.data.value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -43,16 +64,16 @@ export class UnsafeSelectMenuOption {
|
||||
* @param description The description of this option
|
||||
*/
|
||||
public setDescription(description: string) {
|
||||
Reflect.set(this, 'description', description);
|
||||
this.data.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this option is selected by default
|
||||
* @param isDefault Whether or not this option is selected by default
|
||||
* @param isDefault Whether this option is selected by default
|
||||
*/
|
||||
public setDefault(isDefault: boolean) {
|
||||
Reflect.set(this, 'default', isDefault);
|
||||
this.data.default = isDefault;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -61,13 +82,14 @@ export class UnsafeSelectMenuOption {
|
||||
* @param emoji The emoji to display on this button
|
||||
*/
|
||||
public setEmoji(emoji: APIMessageComponentEmoji) {
|
||||
Reflect.set(this, 'emoji', emoji);
|
||||
this.data.emoji = emoji;
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APISelectMenuOption {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return {
|
||||
...this,
|
||||
};
|
||||
...this.data,
|
||||
} as APISelectMenuOption;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user