refactor: builders (#10448)

BREAKING CHANGE: formatters export removed (prev. deprecated)
BREAKING CHANGE: `SelectMenuBuilder` and `SelectMenuOptionBuilder` have been removed (prev. deprecated)
BREAKING CHANGE: `EmbedBuilder` no longer takes camalCase options
BREAKING CHANGE: `ActionRowBuilder` now has specialized `[add/set]X` methods as opposed to the current `[add/set]Components`
BREAKING CHANGE: Removed `equals` methods
BREAKING CHANGE: Sapphire -> zod for validation
BREAKING CHANGE: Removed the ability to pass `null`/`undefined` to clear fields, use `clearX()` instead
BREAKING CHANGE: Renamed all "slash command" symbols to instead use "chat input command"
BREAKING CHANGE: Removed `ContextMenuCommandBuilder` in favor of `MessageCommandBuilder` and `UserCommandBuilder`
BREAKING CHANGE: Removed support for passing the "string key"s of enums
BREAKING CHANGE: Removed `Button` class in favor for specialized classes depending on the style
BREAKING CHANGE: Removed nested `addX` styled-methods in favor of plural `addXs`

Co-authored-by: Vlad Frangu <me@vladfrangu.dev>
Co-authored-by: Almeida <github@almeidx.dev>
This commit is contained in:
Denis Cristea
2024-10-01 19:11:56 +03:00
committed by GitHub
parent c633d5c7f6
commit ab32f26cbb
91 changed files with 3772 additions and 3824 deletions

View File

@@ -1,115 +1,13 @@
import {
ComponentType,
type APIButtonComponent,
type APIButtonComponentWithCustomId,
type APIButtonComponentWithSKUId,
type APIButtonComponentWithURL,
type APIMessageComponentEmoji,
type ButtonStyle,
type Snowflake,
} from 'discord-api-types/v10';
import {
buttonLabelValidator,
buttonStyleValidator,
customIdValidator,
disabledValidator,
emojiValidator,
urlValidator,
validateRequiredButtonParameters,
} from '../Assertions.js';
import type { APIButtonComponent } from 'discord-api-types/v10';
import { isValidationEnabled } from '../../util/validation.js';
import { buttonPredicate } from '../Assertions.js';
import { ComponentBuilder } from '../Component.js';
/**
* A builder that creates API-compatible JSON data for buttons.
*/
export class ButtonBuilder extends ComponentBuilder<APIButtonComponent> {
/**
* Creates a new button from API data.
*
* @param data - The API data to create this button with
* @example
* Creating a button from an API data object:
* ```ts
* const button = new ButtonBuilder({
* custom_id: 'a cool button',
* style: ButtonStyle.Primary,
* label: 'Click Me',
* emoji: {
* name: 'smile',
* id: '123456789012345678',
* },
* });
* ```
* @example
* Creating a button using setters and API data:
* ```ts
* const button = new ButtonBuilder({
* style: ButtonStyle.Secondary,
* label: 'Click Me',
* })
* .setEmoji({ name: '🙂' })
* .setCustomId('another cool button');
* ```
*/
public constructor(data?: Partial<APIButtonComponent>) {
super({ type: ComponentType.Button, ...data });
}
/**
* Sets the style of this button.
*
* @param style - The style to use
*/
public setStyle(style: ButtonStyle) {
this.data.style = buttonStyleValidator.parse(style);
return this;
}
/**
* Sets the URL for this button.
*
* @remarks
* This method is only available to buttons using the `Link` button style.
* Only three types of URL schemes are currently supported: `https://`, `http://`, and `discord://`.
* @param url - The URL to use
*/
public setURL(url: string) {
(this.data as APIButtonComponentWithURL).url = urlValidator.parse(url);
return this;
}
/**
* Sets the custom id for this button.
*
* @remarks
* This method is only applicable to buttons that are not using the `Link` button style.
* @param customId - The custom id to use
*/
public setCustomId(customId: string) {
(this.data as APIButtonComponentWithCustomId).custom_id = customIdValidator.parse(customId);
return this;
}
/**
* Sets the SKU id that represents a purchasable SKU for this button.
*
* @remarks Only available when using premium-style buttons.
* @param skuId - The SKU id to use
*/
public setSKUId(skuId: Snowflake) {
(this.data as APIButtonComponentWithSKUId).sku_id = skuId;
return this;
}
/**
* Sets the emoji to display on this button.
*
* @param emoji - The emoji to use
*/
public setEmoji(emoji: APIMessageComponentEmoji) {
(this.data as Exclude<APIButtonComponent, APIButtonComponentWithSKUId>).emoji = emojiValidator.parse(emoji);
return this;
}
export abstract class BaseButtonBuilder<ButtonData extends APIButtonComponent> extends ComponentBuilder<ButtonData> {
protected declare readonly data: Partial<ButtonData>;
/**
* Sets whether this button is disabled.
@@ -117,35 +15,20 @@ export class ButtonBuilder extends ComponentBuilder<APIButtonComponent> {
* @param disabled - Whether to disable this button
*/
public setDisabled(disabled = true) {
this.data.disabled = disabledValidator.parse(disabled);
return this;
}
/**
* Sets the label for this button.
*
* @param label - The label to use
*/
public setLabel(label: string) {
(this.data as Exclude<APIButtonComponent, APIButtonComponentWithSKUId>).label = buttonLabelValidator.parse(label);
this.data.disabled = disabled;
return this;
}
/**
* {@inheritDoc ComponentBuilder.toJSON}
*/
public toJSON(): APIButtonComponent {
validateRequiredButtonParameters(
this.data.style,
(this.data as Exclude<APIButtonComponent, APIButtonComponentWithSKUId>).label,
(this.data as Exclude<APIButtonComponent, APIButtonComponentWithSKUId>).emoji,
(this.data as APIButtonComponentWithCustomId).custom_id,
(this.data as APIButtonComponentWithSKUId).sku_id,
(this.data as APIButtonComponentWithURL).url,
);
public override toJSON(validationOverride?: boolean): ButtonData {
const clone = structuredClone(this.data);
return {
...this.data,
} as APIButtonComponent;
if (validationOverride ?? isValidationEnabled()) {
buttonPredicate.parse(clone);
}
return clone as ButtonData;
}
}

View File

@@ -0,0 +1,69 @@
import { ButtonStyle, ComponentType, type APIButtonComponentWithCustomId } from 'discord-api-types/v10';
import { Mixin } from 'ts-mixer';
import { BaseButtonBuilder } from './Button.js';
import { EmojiOrLabelButtonMixin } from './mixins/EmojiOrLabelButtonMixin.js';
export type CustomIdButtonStyle = APIButtonComponentWithCustomId['style'];
/**
* A builder that creates API-compatible JSON data for buttons with custom IDs.
*/
export abstract class CustomIdButtonBuilder extends Mixin(
BaseButtonBuilder<APIButtonComponentWithCustomId>,
EmojiOrLabelButtonMixin,
) {
protected override readonly data: Partial<APIButtonComponentWithCustomId>;
protected constructor(data: Partial<APIButtonComponentWithCustomId> = {}) {
super();
this.data = { ...structuredClone(data), type: ComponentType.Button };
}
/**
* Sets the custom id for this button.
*
* @remarks
* This method is only applicable to buttons that are not using the `Link` button style.
* @param customId - The custom id to use
*/
public setCustomId(customId: string) {
this.data.custom_id = customId;
return this;
}
}
/**
* A builder that creates API-compatible JSON data for buttons with custom IDs (using the primary style).
*/
export class PrimaryButtonBuilder extends CustomIdButtonBuilder {
public constructor(data: Partial<APIButtonComponentWithCustomId> = {}) {
super({ ...data, style: ButtonStyle.Primary });
}
}
/**
* A builder that creates API-compatible JSON data for buttons with custom IDs (using the secondary style).
*/
export class SecondaryButtonBuilder extends CustomIdButtonBuilder {
public constructor(data: Partial<APIButtonComponentWithCustomId> = {}) {
super({ ...data, style: ButtonStyle.Secondary });
}
}
/**
* A builder that creates API-compatible JSON data for buttons with custom IDs (using the success style).
*/
export class SuccessButtonBuilder extends CustomIdButtonBuilder {
public constructor(data: Partial<APIButtonComponentWithCustomId> = {}) {
super({ ...data, style: ButtonStyle.Success });
}
}
/**
* A builder that creates API-compatible JSON data for buttons with custom IDs (using the danger style).
*/
export class DangerButtonBuilder extends CustomIdButtonBuilder {
public constructor(data: Partial<APIButtonComponentWithCustomId> = {}) {
super({ ...data, style: ButtonStyle.Danger });
}
}

View File

@@ -0,0 +1,34 @@
import {
ButtonStyle,
ComponentType,
type APIButtonComponent,
type APIButtonComponentWithURL,
} from 'discord-api-types/v10';
import { Mixin } from 'ts-mixer';
import { BaseButtonBuilder } from './Button.js';
import { EmojiOrLabelButtonMixin } from './mixins/EmojiOrLabelButtonMixin.js';
/**
* A builder that creates API-compatible JSON data for buttons with links.
*/
export class LinkButtonBuilder extends Mixin(BaseButtonBuilder<APIButtonComponentWithURL>, EmojiOrLabelButtonMixin) {
protected override readonly data: Partial<APIButtonComponentWithURL>;
public constructor(data: Partial<APIButtonComponent> = {}) {
super();
this.data = { ...structuredClone(data), type: ComponentType.Button, style: ButtonStyle.Link };
}
/**
* Sets the URL for this button.
*
* @remarks
* This method is only available to buttons using the `Link` button style.
* Only three types of URL schemes are currently supported: `https://`, `http://`, and `discord://`.
* @param url - The URL to use
*/
public setURL(url: string) {
this.data.url = url;
return this;
}
}

View File

@@ -0,0 +1,26 @@
import type { APIButtonComponentWithSKUId, Snowflake } from 'discord-api-types/v10';
import { ButtonStyle, ComponentType } from 'discord-api-types/v10';
import { BaseButtonBuilder } from './Button.js';
/**
* A builder that creates API-compatible JSON data for premium buttons.
*/
export class PremiumButtonBuilder extends BaseButtonBuilder<APIButtonComponentWithSKUId> {
protected override readonly data: Partial<APIButtonComponentWithSKUId>;
public constructor(data: Partial<APIButtonComponentWithSKUId> = {}) {
super();
this.data = { ...structuredClone(data), type: ComponentType.Button, style: ButtonStyle.Premium };
}
/**
* Sets the SKU id that represents a purchasable SKU for this button.
*
* @remarks Only available when using premium-style buttons.
* @param skuId - The SKU id to use
*/
public setSKUId(skuId: Snowflake) {
this.data.sku_id = skuId;
return this;
}
}

View File

@@ -0,0 +1,44 @@
import type { APIButtonComponent, APIButtonComponentWithSKUId, APIMessageComponentEmoji } from 'discord-api-types/v10';
export interface EmojiOrLabelButtonData
extends Pick<Exclude<APIButtonComponent, APIButtonComponentWithSKUId>, 'emoji' | 'label'> {}
export class EmojiOrLabelButtonMixin {
protected declare readonly data: EmojiOrLabelButtonData;
/**
* Sets the emoji to display on this button.
*
* @param emoji - The emoji to use
*/
public setEmoji(emoji: APIMessageComponentEmoji) {
this.data.emoji = emoji;
return this;
}
/**
* Clears the emoji on this button.
*/
public clearEmoji() {
this.data.emoji = undefined;
return this;
}
/**
* Sets the label for this button.
*
* @param label - The label to use
*/
public setLabel(label: string) {
this.data.label = label;
return this;
}
/**
* Clears the label on this button.
*/
public clearLabel() {
this.data.label = undefined;
return this;
}
}