mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
feat(components): Add unsafe message component builders (#7387)
This commit is contained in:
@@ -5,7 +5,7 @@ import {
|
||||
ComponentType,
|
||||
} from 'discord-api-types/v9';
|
||||
import { buttonLabelValidator, buttonStyleValidator } from '../../src/components/Assertions';
|
||||
import { ButtonComponent } from '../../src/components/Button';
|
||||
import { ButtonComponent } from '../../src/components/button/Button';
|
||||
|
||||
const buttonComponent = () => new ButtonComponent();
|
||||
|
||||
|
||||
42
packages/builders/src/components/button/Button.ts
Normal file
42
packages/builders/src/components/button/Button.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import type { ButtonStyle, APIMessageComponentEmoji, APIButtonComponent } from 'discord-api-types/v9';
|
||||
import {
|
||||
buttonLabelValidator,
|
||||
buttonStyleValidator,
|
||||
customIdValidator,
|
||||
disabledValidator,
|
||||
emojiValidator,
|
||||
urlValidator,
|
||||
validateRequiredButtonParameters,
|
||||
} from '../Assertions';
|
||||
import { UnsafeButtonComponent } from './UnsafeButton';
|
||||
|
||||
export class ButtonComponent extends UnsafeButtonComponent {
|
||||
public override setStyle(style: ButtonStyle) {
|
||||
return super.setStyle(buttonStyleValidator.parse(style));
|
||||
}
|
||||
|
||||
public override setURL(url: string) {
|
||||
return super.setURL(urlValidator.parse(url));
|
||||
}
|
||||
|
||||
public override setCustomId(customId: string) {
|
||||
return super.setCustomId(customIdValidator.parse(customId));
|
||||
}
|
||||
|
||||
public override setEmoji(emoji: APIMessageComponentEmoji) {
|
||||
return super.setEmoji(emojiValidator.parse(emoji));
|
||||
}
|
||||
|
||||
public override setDisabled(disabled: boolean) {
|
||||
return super.setDisabled(disabledValidator.parse(disabled));
|
||||
}
|
||||
|
||||
public override setLabel(label: string) {
|
||||
return super.setLabel(buttonLabelValidator.parse(label));
|
||||
}
|
||||
|
||||
public override toJSON(): APIButtonComponent {
|
||||
validateRequiredButtonParameters(this.style, this.label, this.emoji, this.custom_id, this.url);
|
||||
return super.toJSON();
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,12 @@
|
||||
import { APIButtonComponent, APIMessageComponentEmoji, ButtonStyle, ComponentType } from 'discord-api-types/v9';
|
||||
import {
|
||||
buttonLabelValidator,
|
||||
buttonStyleValidator,
|
||||
customIdValidator,
|
||||
disabledValidator,
|
||||
emojiValidator,
|
||||
urlValidator,
|
||||
validateRequiredButtonParameters,
|
||||
} from './Assertions';
|
||||
import type { Component } from './Component';
|
||||
ComponentType,
|
||||
ButtonStyle,
|
||||
type APIMessageComponentEmoji,
|
||||
type APIButtonComponent,
|
||||
} from 'discord-api-types/v9';
|
||||
import type { Component } from '../Component';
|
||||
|
||||
export class ButtonComponent implements Component {
|
||||
export class UnsafeButtonComponent implements Component {
|
||||
public readonly type = ComponentType.Button as const;
|
||||
public readonly style!: ButtonStyle;
|
||||
public readonly label?: string;
|
||||
@@ -41,7 +37,6 @@ export class ButtonComponent implements Component {
|
||||
* @param style The style of the button
|
||||
*/
|
||||
public setStyle(style: ButtonStyle) {
|
||||
buttonStyleValidator.parse(style);
|
||||
Reflect.set(this, 'style', style);
|
||||
return this;
|
||||
}
|
||||
@@ -51,7 +46,6 @@ export class ButtonComponent implements Component {
|
||||
* @param url The URL to open when this button is clicked
|
||||
*/
|
||||
public setURL(url: string) {
|
||||
urlValidator.parse(url);
|
||||
Reflect.set(this, 'url', url);
|
||||
return this;
|
||||
}
|
||||
@@ -61,7 +55,6 @@ export class ButtonComponent implements Component {
|
||||
* @param customId The custom ID to use for this button
|
||||
*/
|
||||
public setCustomId(customId: string) {
|
||||
customIdValidator.parse(customId);
|
||||
Reflect.set(this, 'custom_id', customId);
|
||||
return this;
|
||||
}
|
||||
@@ -71,7 +64,6 @@ export class ButtonComponent implements Component {
|
||||
* @param emoji The emoji to display on this button
|
||||
*/
|
||||
public setEmoji(emoji: APIMessageComponentEmoji) {
|
||||
emojiValidator.parse(emoji);
|
||||
Reflect.set(this, 'emoji', emoji);
|
||||
return this;
|
||||
}
|
||||
@@ -81,7 +73,6 @@ export class ButtonComponent implements Component {
|
||||
* @param disabled Whether or not to disable this button or not
|
||||
*/
|
||||
public setDisabled(disabled: boolean) {
|
||||
disabledValidator.parse(disabled);
|
||||
Reflect.set(this, 'disabled', disabled);
|
||||
return this;
|
||||
}
|
||||
@@ -91,13 +82,11 @@ export class ButtonComponent implements Component {
|
||||
* @param label The label to display on this button
|
||||
*/
|
||||
public setLabel(label: string) {
|
||||
buttonLabelValidator.parse(label);
|
||||
Reflect.set(this, 'label', label);
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APIButtonComponent {
|
||||
validateRequiredButtonParameters(this.style, this.label, this.emoji, this.custom_id, this.url);
|
||||
return {
|
||||
...this,
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { APISelectMenuComponent, ComponentType } from 'discord-api-types/v9';
|
||||
import type { APISelectMenuComponent } from 'discord-api-types/v9';
|
||||
import {
|
||||
customIdValidator,
|
||||
disabledValidator,
|
||||
@@ -6,106 +6,34 @@ import {
|
||||
placeholderValidator,
|
||||
validateRequiredSelectMenuParameters,
|
||||
} from '../Assertions';
|
||||
import type { Component } from '../Component';
|
||||
import { SelectMenuOption } from './SelectMenuOption';
|
||||
import { UnsafeSelectMenuComponent } from './UnsafeSelectMenu';
|
||||
|
||||
/**
|
||||
* Represents a select menu component
|
||||
*/
|
||||
export class SelectMenuComponent 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;
|
||||
|
||||
public constructor(data?: APISelectMenuComponent & { type?: ComponentType.SelectMenu }) {
|
||||
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;
|
||||
export class SelectMenuComponent extends UnsafeSelectMenuComponent {
|
||||
public override setPlaceholder(placeholder: string) {
|
||||
return super.setPlaceholder(placeholderValidator.parse(placeholder));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the placeholder for this select menu
|
||||
* @param placeholder The placeholder to use for this select menu
|
||||
*/
|
||||
public setPlaceholder(placeholder: string) {
|
||||
placeholderValidator.parse(placeholder);
|
||||
Reflect.set(this, 'placeholder', placeholder);
|
||||
return this;
|
||||
public override setMinValues(minValues: number) {
|
||||
return super.setMinValues(minMaxValidator.parse(minValues));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
minMaxValidator.parse(minValues);
|
||||
Reflect.set(this, 'min_values', minValues);
|
||||
return this;
|
||||
public override setMaxValues(maxValues: number) {
|
||||
return super.setMaxValues(minMaxValidator.parse(maxValues));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
minMaxValidator.parse(maxValues);
|
||||
Reflect.set(this, 'max_values', maxValues);
|
||||
return this;
|
||||
public override setCustomId(customId: string) {
|
||||
return super.setCustomId(customIdValidator.parse(customId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom Id for this select menu
|
||||
* @param customId The custom ID to use for this select menu
|
||||
*/
|
||||
public setCustomId(customId: string) {
|
||||
customIdValidator.parse(customId);
|
||||
Reflect.set(this, 'custom_id', customId);
|
||||
return this;
|
||||
public override setDisabled(disabled: boolean) {
|
||||
return super.setDisabled(disabledValidator.parse(disabled));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not this select menu is disabled
|
||||
* @param disabled Whether or not this select menu is disabled
|
||||
*/
|
||||
public setDisabled(disabled: boolean) {
|
||||
disabledValidator.parse(disabled);
|
||||
Reflect.set(this, 'disabled', disabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds options to this select menu
|
||||
* @param options The options to add to this select menu
|
||||
* @returns
|
||||
*/
|
||||
public addOptions(...options: SelectMenuOption[]) {
|
||||
this.options.push(...options);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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]);
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APISelectMenuComponent {
|
||||
public override toJSON(): APISelectMenuComponent {
|
||||
validateRequiredSelectMenuParameters(this.options, this.custom_id);
|
||||
return {
|
||||
...this,
|
||||
options: this.options.map((option) => option.toJSON()),
|
||||
};
|
||||
return super.toJSON();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,79 +5,26 @@ import {
|
||||
labelValueValidator,
|
||||
validateRequiredSelectMenuOptionParameters,
|
||||
} from '../Assertions';
|
||||
import { UnsafeSelectMenuOption } from './UnsafeSelectMenuOption';
|
||||
|
||||
/**
|
||||
* Represents an option within a select menu component
|
||||
*/
|
||||
export class SelectMenuOption {
|
||||
public readonly label!: string;
|
||||
public readonly value!: string;
|
||||
public readonly description?: string;
|
||||
public readonly emoji?: APIMessageComponentEmoji;
|
||||
public readonly default?: boolean;
|
||||
|
||||
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;
|
||||
export class SelectMenuOption extends UnsafeSelectMenuOption {
|
||||
public override setDescription(description: string) {
|
||||
return super.setDescription(labelValueValidator.parse(description));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the label of this option
|
||||
* @param label The label to show on this option
|
||||
*/
|
||||
public setLabel(label: string) {
|
||||
Reflect.set(this, 'label', label);
|
||||
return this;
|
||||
public override setDefault(isDefault: boolean) {
|
||||
return super.setDefault(defaultValidator.parse(isDefault));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of this option
|
||||
* @param value The value of this option
|
||||
*/
|
||||
public setValue(value: string) {
|
||||
Reflect.set(this, 'value', value);
|
||||
return this;
|
||||
public override setEmoji(emoji: APIMessageComponentEmoji) {
|
||||
return super.setEmoji(emojiValidator.parse(emoji));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description of this option.
|
||||
* @param description The description of this option
|
||||
*/
|
||||
public setDescription(description: string) {
|
||||
labelValueValidator.parse(description);
|
||||
Reflect.set(this, 'description', description);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this option is selected by default
|
||||
* @param isDefault Whether or not this option is selected by default
|
||||
*/
|
||||
public setDefault(isDefault: boolean) {
|
||||
defaultValidator.parse(isDefault);
|
||||
Reflect.set(this, 'default', isDefault);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the emoji to display on this button
|
||||
* @param emoji The emoji to display on this button
|
||||
*/
|
||||
public setEmoji(emoji: APIMessageComponentEmoji) {
|
||||
emojiValidator.parse(emoji);
|
||||
Reflect.set(this, 'emoji', emoji);
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APISelectMenuOption {
|
||||
public override toJSON(): APISelectMenuOption {
|
||||
validateRequiredSelectMenuOptionParameters(this.label, this.value);
|
||||
return {
|
||||
...this,
|
||||
};
|
||||
return super.toJSON();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import { ComponentType, type APISelectMenuComponent } from 'discord-api-types/v9';
|
||||
import type { Component } from '../Component';
|
||||
import { SelectMenuOption } from './SelectMenuOption';
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the placeholder for this select menu
|
||||
* @param placeholder The placeholder to use for this select menu
|
||||
*/
|
||||
public setPlaceholder(placeholder: string) {
|
||||
Reflect.set(this, 'placeholder', placeholder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets thes 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);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets thes 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);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom Id 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);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not this select menu is disabled
|
||||
* @param disabled Whether or not this select menu is disabled
|
||||
*/
|
||||
public setDisabled(disabled: boolean) {
|
||||
Reflect.set(this, 'disabled', disabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds options to this select menu
|
||||
* @param options The options to add to this select menu
|
||||
* @returns
|
||||
*/
|
||||
public addOptions(...options: SelectMenuOption[]) {
|
||||
this.options.push(...options);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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]);
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APISelectMenuComponent {
|
||||
return {
|
||||
...this,
|
||||
options: this.options.map((option) => option.toJSON()),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import type { APIMessageComponentEmoji, APISelectMenuOption } from 'discord-api-types/v9';
|
||||
|
||||
/**
|
||||
* 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(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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the label of this option
|
||||
* @param label The label to show on this option
|
||||
*/
|
||||
public setLabel(label: string) {
|
||||
Reflect.set(this, 'label', label);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of this option
|
||||
* @param value The value of this option
|
||||
*/
|
||||
public setValue(value: string) {
|
||||
Reflect.set(this, 'value', value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description of this option.
|
||||
* @param description The description of this option
|
||||
*/
|
||||
public setDescription(description: string) {
|
||||
Reflect.set(this, 'description', description);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this option is selected by default
|
||||
* @param isDefault Whether or not this option is selected by default
|
||||
*/
|
||||
public setDefault(isDefault: boolean) {
|
||||
Reflect.set(this, 'default', isDefault);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the emoji to display on this button
|
||||
* @param emoji The emoji to display on this button
|
||||
*/
|
||||
public setEmoji(emoji: APIMessageComponentEmoji) {
|
||||
Reflect.set(this, 'emoji', emoji);
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APISelectMenuOption {
|
||||
return {
|
||||
...this,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,18 @@
|
||||
export * as EmbedAssertions from './messages/embed/Assertions';
|
||||
export * from './messages/embed/Embed';
|
||||
export * from './messages/formatters';
|
||||
export * from './messages/embed/UnsafeEmbed';
|
||||
|
||||
export * as ComponentAssertions from './components/Assertions';
|
||||
export * from './components/ActionRow';
|
||||
export * from './components/Button';
|
||||
export * from './components/button/Button';
|
||||
export * from './components/Component';
|
||||
export * from './components/Components';
|
||||
export * from './components/selectMenu/SelectMenu';
|
||||
export * from './components/selectMenu/SelectMenuOption';
|
||||
export * from './components/button/UnsafeButton';
|
||||
export * from './components/selectMenu/UnsafeSelectMenu';
|
||||
export * from './components/selectMenu/UnsafeSelectMenuOption';
|
||||
|
||||
export * as SlashCommandAssertions from './interactions/slashCommands/Assertions';
|
||||
export * from './interactions/slashCommands/SlashCommandBuilder';
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
import type {
|
||||
APIEmbed,
|
||||
APIEmbedAuthor,
|
||||
APIEmbedField,
|
||||
APIEmbedFooter,
|
||||
APIEmbedImage,
|
||||
APIEmbedProvider,
|
||||
APIEmbedThumbnail,
|
||||
APIEmbedVideo,
|
||||
} from 'discord-api-types/v9';
|
||||
import type { APIEmbedField } from 'discord-api-types/v9';
|
||||
import {
|
||||
authorNamePredicate,
|
||||
colorPredicate,
|
||||
@@ -22,300 +13,86 @@ import {
|
||||
urlPredicate,
|
||||
validateFieldLength,
|
||||
} from './Assertions';
|
||||
|
||||
export interface AuthorOptions {
|
||||
name: string;
|
||||
url?: string;
|
||||
iconURL?: string;
|
||||
}
|
||||
|
||||
export interface FooterOptions {
|
||||
text: string;
|
||||
iconURL?: string;
|
||||
}
|
||||
import { AuthorOptions, FooterOptions, UnsafeEmbed } from './UnsafeEmbed';
|
||||
|
||||
/**
|
||||
* Represents an embed in a message (image/video preview, rich embed, etc.)
|
||||
*/
|
||||
export class Embed implements APIEmbed {
|
||||
/**
|
||||
* An array of fields of this embed
|
||||
*/
|
||||
public readonly fields: APIEmbedField[];
|
||||
|
||||
/**
|
||||
* The embed title
|
||||
*/
|
||||
public readonly title?: string;
|
||||
|
||||
/**
|
||||
* The embed description
|
||||
*/
|
||||
public readonly description?: string;
|
||||
|
||||
/**
|
||||
* The embed url
|
||||
*/
|
||||
public readonly url?: string;
|
||||
|
||||
/**
|
||||
* The embed color
|
||||
*/
|
||||
public readonly color?: number;
|
||||
|
||||
/**
|
||||
* The timestamp of the embed in the ISO format
|
||||
*/
|
||||
public readonly timestamp?: string;
|
||||
|
||||
/**
|
||||
* The embed thumbnail data
|
||||
*/
|
||||
public readonly thumbnail?: APIEmbedThumbnail;
|
||||
|
||||
/**
|
||||
* The embed image data
|
||||
*/
|
||||
public readonly image?: APIEmbedImage;
|
||||
|
||||
/**
|
||||
* Received video data
|
||||
*/
|
||||
public readonly video?: APIEmbedVideo;
|
||||
|
||||
/**
|
||||
* The embed author data
|
||||
*/
|
||||
public readonly author?: APIEmbedAuthor;
|
||||
|
||||
/**
|
||||
* Received data about the embed provider
|
||||
*/
|
||||
public readonly provider?: APIEmbedProvider;
|
||||
|
||||
/**
|
||||
* The embed footer data
|
||||
*/
|
||||
public readonly footer?: APIEmbedFooter;
|
||||
|
||||
public constructor(data: APIEmbed = {}) {
|
||||
this.title = data.title;
|
||||
this.description = data.description;
|
||||
this.url = data.url;
|
||||
this.color = data.color;
|
||||
this.thumbnail = data.thumbnail;
|
||||
this.image = data.image;
|
||||
this.video = data.video;
|
||||
this.author = data.author;
|
||||
this.provider = data.provider;
|
||||
this.footer = data.footer;
|
||||
this.fields = data.fields ?? [];
|
||||
|
||||
if (data.timestamp) this.timestamp = new Date(data.timestamp).toISOString();
|
||||
}
|
||||
|
||||
/**
|
||||
* The accumulated length for the embed title, description, fields, footer text, and author name
|
||||
*/
|
||||
public get length(): number {
|
||||
return (
|
||||
(this.title?.length ?? 0) +
|
||||
(this.description?.length ?? 0) +
|
||||
this.fields.reduce((prev, curr) => prev + curr.name.length + curr.value.length, 0) +
|
||||
(this.footer?.text.length ?? 0) +
|
||||
(this.author?.name.length ?? 0)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field to the embed (max 25)
|
||||
*
|
||||
* @param field The field to add.
|
||||
*/
|
||||
public addField(field: APIEmbedField): this {
|
||||
return this.addFields(field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds fields to the embed (max 25)
|
||||
*
|
||||
* @param fields The fields to add
|
||||
*/
|
||||
public addFields(...fields: APIEmbedField[]): this {
|
||||
// Data assertions
|
||||
embedFieldsArrayPredicate.parse(fields);
|
||||
|
||||
export class Embed extends UnsafeEmbed {
|
||||
public override addFields(...fields: APIEmbedField[]): this {
|
||||
// Ensure adding these fields won't exceed the 25 field limit
|
||||
validateFieldLength(this.fields, fields.length);
|
||||
|
||||
this.fields.push(...Embed.normalizeFields(...fields));
|
||||
return this;
|
||||
// Data assertions
|
||||
return super.addFields(...embedFieldsArrayPredicate.parse(fields));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes, replaces, or inserts fields in the embed (max 25)
|
||||
*
|
||||
* @param index The index to start at
|
||||
* @param deleteCount The number of fields to remove
|
||||
* @param fields The replacing field objects
|
||||
*/
|
||||
public spliceFields(index: number, deleteCount: number, ...fields: APIEmbedField[]): this {
|
||||
// Data assertions
|
||||
embedFieldsArrayPredicate.parse(fields);
|
||||
|
||||
public override spliceFields(index: number, deleteCount: number, ...fields: APIEmbedField[]): this {
|
||||
// Ensure adding these fields won't exceed the 25 field limit
|
||||
validateFieldLength(this.fields, fields.length - deleteCount);
|
||||
|
||||
this.fields.splice(index, deleteCount, ...Embed.normalizeFields(...fields));
|
||||
return this;
|
||||
// Data assertions
|
||||
return super.spliceFields(index, deleteCount, ...embedFieldsArrayPredicate.parse(fields));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the embed's fields (max 25).
|
||||
* @param fields The fields to set
|
||||
*/
|
||||
public setFields(...fields: APIEmbedField[]) {
|
||||
this.spliceFields(0, this.fields.length, ...fields);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the author of this embed
|
||||
*
|
||||
* @param options The options for the author
|
||||
*/
|
||||
public setAuthor(options: AuthorOptions | null): this {
|
||||
public override setAuthor(options: AuthorOptions | null): this {
|
||||
if (options === null) {
|
||||
Reflect.set(this, 'author', undefined);
|
||||
return this;
|
||||
return super.setAuthor(null);
|
||||
}
|
||||
|
||||
const { name, iconURL, url } = options;
|
||||
// Data assertions
|
||||
authorNamePredicate.parse(name);
|
||||
urlPredicate.parse(iconURL);
|
||||
urlPredicate.parse(url);
|
||||
authorNamePredicate.parse(options.name);
|
||||
urlPredicate.parse(options.iconURL);
|
||||
urlPredicate.parse(options.url);
|
||||
|
||||
Reflect.set(this, 'author', { name, url, icon_url: iconURL });
|
||||
return this;
|
||||
return super.setAuthor(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color of this embed
|
||||
*
|
||||
* @param color The color of the embed
|
||||
*/
|
||||
public setColor(color: number | null): this {
|
||||
public override setColor(color: number | null): this {
|
||||
// Data assertions
|
||||
colorPredicate.parse(color);
|
||||
|
||||
Reflect.set(this, 'color', color ?? undefined);
|
||||
return this;
|
||||
return super.setColor(colorPredicate.parse(color));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description of this embed
|
||||
*
|
||||
* @param description The description
|
||||
*/
|
||||
public setDescription(description: string | null): this {
|
||||
public override setDescription(description: string | null): this {
|
||||
// Data assertions
|
||||
descriptionPredicate.parse(description);
|
||||
|
||||
Reflect.set(this, 'description', description ?? undefined);
|
||||
return this;
|
||||
return super.setDescription(descriptionPredicate.parse(description));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the footer of this embed
|
||||
*
|
||||
* @param options The options for the footer
|
||||
*/
|
||||
public setFooter(options: FooterOptions | null): this {
|
||||
public override setFooter(options: FooterOptions | null): this {
|
||||
if (options === null) {
|
||||
Reflect.set(this, 'footer', undefined);
|
||||
return this;
|
||||
return super.setFooter(null);
|
||||
}
|
||||
|
||||
const { text, iconURL } = options;
|
||||
// Data assertions
|
||||
footerTextPredicate.parse(text);
|
||||
urlPredicate.parse(iconURL);
|
||||
footerTextPredicate.parse(options.text);
|
||||
urlPredicate.parse(options.iconURL);
|
||||
|
||||
Reflect.set(this, 'footer', { text, icon_url: iconURL });
|
||||
return this;
|
||||
return super.setFooter(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the image of this embed
|
||||
*
|
||||
* @param url The URL of the image
|
||||
*/
|
||||
public setImage(url: string | null): this {
|
||||
public override setImage(url: string | null): this {
|
||||
// Data assertions
|
||||
urlPredicate.parse(url);
|
||||
|
||||
Reflect.set(this, 'image', url ? { url } : undefined);
|
||||
return this;
|
||||
return super.setImage(urlPredicate.parse(url)!);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the thumbnail of this embed
|
||||
*
|
||||
* @param url The URL of the thumbnail
|
||||
*/
|
||||
public setThumbnail(url: string | null): this {
|
||||
public override setThumbnail(url: string | null): this {
|
||||
// Data assertions
|
||||
urlPredicate.parse(url);
|
||||
|
||||
Reflect.set(this, 'thumbnail', url ? { url } : undefined);
|
||||
return this;
|
||||
return super.setThumbnail(urlPredicate.parse(url)!);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timestamp of this embed
|
||||
*
|
||||
* @param timestamp The timestamp or date
|
||||
*/
|
||||
public setTimestamp(timestamp: number | Date | null = Date.now()): this {
|
||||
public override setTimestamp(timestamp: number | Date | null = Date.now()): this {
|
||||
// Data assertions
|
||||
timestampPredicate.parse(timestamp);
|
||||
|
||||
Reflect.set(this, 'timestamp', timestamp ? new Date(timestamp).toISOString() : undefined);
|
||||
return this;
|
||||
return super.setTimestamp(timestampPredicate.parse(timestamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the title of this embed
|
||||
*
|
||||
* @param title The title
|
||||
*/
|
||||
public setTitle(title: string | null): this {
|
||||
public override setTitle(title: string | null): this {
|
||||
// Data assertions
|
||||
titlePredicate.parse(title);
|
||||
|
||||
Reflect.set(this, 'title', title ?? undefined);
|
||||
return this;
|
||||
return super.setTitle(titlePredicate.parse(title));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL of this embed
|
||||
*
|
||||
* @param url The URL
|
||||
*/
|
||||
public setURL(url: string | null): this {
|
||||
public override setURL(url: string | null): this {
|
||||
// Data assertions
|
||||
urlPredicate.parse(url);
|
||||
|
||||
Reflect.set(this, 'url', url ?? undefined);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the embed to a plain object
|
||||
*/
|
||||
public toJSON(): APIEmbed {
|
||||
return { ...this };
|
||||
return super.setURL(urlPredicate.parse(url)!);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -323,7 +100,7 @@ export class Embed implements APIEmbed {
|
||||
*
|
||||
* @param fields Fields to normalize
|
||||
*/
|
||||
public static normalizeFields(...fields: APIEmbedField[]): APIEmbedField[] {
|
||||
public static override normalizeFields(...fields: APIEmbedField[]): APIEmbedField[] {
|
||||
return fields.flat(Infinity).map((field) => {
|
||||
fieldNamePredicate.parse(field.name);
|
||||
fieldValuePredicate.parse(field.value);
|
||||
|
||||
271
packages/builders/src/messages/embed/UnsafeEmbed.ts
Normal file
271
packages/builders/src/messages/embed/UnsafeEmbed.ts
Normal file
@@ -0,0 +1,271 @@
|
||||
import type {
|
||||
APIEmbed,
|
||||
APIEmbedAuthor,
|
||||
APIEmbedField,
|
||||
APIEmbedFooter,
|
||||
APIEmbedImage,
|
||||
APIEmbedProvider,
|
||||
APIEmbedThumbnail,
|
||||
APIEmbedVideo,
|
||||
} from 'discord-api-types/v9';
|
||||
import { Embed } from './Embed';
|
||||
|
||||
export interface AuthorOptions {
|
||||
name: string;
|
||||
url?: string;
|
||||
iconURL?: string;
|
||||
}
|
||||
|
||||
export interface FooterOptions {
|
||||
text: string;
|
||||
iconURL?: string;
|
||||
}
|
||||
|
||||
export class UnsafeEmbed implements APIEmbed {
|
||||
/**
|
||||
* An array of fields of this embed
|
||||
*/
|
||||
public readonly fields: APIEmbedField[];
|
||||
|
||||
/**
|
||||
* The embed title
|
||||
*/
|
||||
public readonly title?: string;
|
||||
|
||||
/**
|
||||
* The embed description
|
||||
*/
|
||||
public readonly description?: string;
|
||||
|
||||
/**
|
||||
* The embed url
|
||||
*/
|
||||
public readonly url?: string;
|
||||
|
||||
/**
|
||||
* The embed color
|
||||
*/
|
||||
public readonly color?: number;
|
||||
|
||||
/**
|
||||
* The timestamp of the embed in the ISO format
|
||||
*/
|
||||
public readonly timestamp?: string;
|
||||
|
||||
/**
|
||||
* The embed thumbnail data
|
||||
*/
|
||||
public readonly thumbnail?: APIEmbedThumbnail;
|
||||
|
||||
/**
|
||||
* The embed image data
|
||||
*/
|
||||
public readonly image?: APIEmbedImage;
|
||||
|
||||
/**
|
||||
* Received video data
|
||||
*/
|
||||
public readonly video?: APIEmbedVideo;
|
||||
|
||||
/**
|
||||
* The embed author data
|
||||
*/
|
||||
public readonly author?: APIEmbedAuthor;
|
||||
|
||||
/**
|
||||
* Received data about the embed provider
|
||||
*/
|
||||
public readonly provider?: APIEmbedProvider;
|
||||
|
||||
/**
|
||||
* The embed footer data
|
||||
*/
|
||||
public readonly footer?: APIEmbedFooter;
|
||||
|
||||
public constructor(data: APIEmbed = {}) {
|
||||
this.title = data.title;
|
||||
this.description = data.description;
|
||||
this.url = data.url;
|
||||
this.color = data.color;
|
||||
this.thumbnail = data.thumbnail;
|
||||
this.image = data.image;
|
||||
this.video = data.video;
|
||||
this.author = data.author;
|
||||
this.provider = data.provider;
|
||||
this.footer = data.footer;
|
||||
this.fields = data.fields ?? [];
|
||||
|
||||
if (data.timestamp) this.timestamp = new Date(data.timestamp).toISOString();
|
||||
}
|
||||
|
||||
/**
|
||||
* The accumulated length for the embed title, description, fields, footer text, and author name
|
||||
*/
|
||||
public get length(): number {
|
||||
return (
|
||||
(this.title?.length ?? 0) +
|
||||
(this.description?.length ?? 0) +
|
||||
this.fields.reduce((prev, curr) => prev + curr.name.length + curr.value.length, 0) +
|
||||
(this.footer?.text.length ?? 0) +
|
||||
(this.author?.name.length ?? 0)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field to the embed (max 25)
|
||||
*
|
||||
* @param field The field to add.
|
||||
*/
|
||||
public addField(field: APIEmbedField): this {
|
||||
return this.addFields(field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds fields to the embed (max 25)
|
||||
*
|
||||
* @param fields The fields to add
|
||||
*/
|
||||
public addFields(...fields: APIEmbedField[]): this {
|
||||
this.fields.push(...Embed.normalizeFields(...fields));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes, replaces, or inserts fields in the embed (max 25)
|
||||
*
|
||||
* @param index The index to start at
|
||||
* @param deleteCount The number of fields to remove
|
||||
* @param fields The replacing field objects
|
||||
*/
|
||||
public spliceFields(index: number, deleteCount: number, ...fields: APIEmbedField[]): this {
|
||||
this.fields.splice(index, deleteCount, ...Embed.normalizeFields(...fields));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the embed's fields (max 25).
|
||||
* @param fields The fields to set
|
||||
*/
|
||||
public setFields(...fields: APIEmbedField[]) {
|
||||
this.spliceFields(0, this.fields.length, ...fields);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the author of this embed
|
||||
*
|
||||
* @param options The options for the author
|
||||
*/
|
||||
public setAuthor(options: AuthorOptions | null): this {
|
||||
if (options === null) {
|
||||
Reflect.set(this, 'author', undefined);
|
||||
return this;
|
||||
}
|
||||
|
||||
Reflect.set(this, 'author', { name: options.name, url: options.url, icon_url: options.iconURL });
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color of this embed
|
||||
*
|
||||
* @param color The color of the embed
|
||||
*/
|
||||
public setColor(color: number | null): this {
|
||||
Reflect.set(this, 'color', color ?? undefined);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description of this embed
|
||||
*
|
||||
* @param description The description
|
||||
*/
|
||||
public setDescription(description: string | null): this {
|
||||
Reflect.set(this, 'description', description ?? undefined);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the footer of this embed
|
||||
*
|
||||
* @param options The options for the footer
|
||||
*/
|
||||
public setFooter(options: FooterOptions | null): this {
|
||||
if (options === null) {
|
||||
Reflect.set(this, 'footer', undefined);
|
||||
return this;
|
||||
}
|
||||
|
||||
Reflect.set(this, 'footer', { text: options.text, icon_url: options.iconURL });
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the image of this embed
|
||||
*
|
||||
* @param url The URL of the image
|
||||
*/
|
||||
public setImage(url: string | null): this {
|
||||
Reflect.set(this, 'image', url ? { url } : undefined);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the thumbnail of this embed
|
||||
*
|
||||
* @param url The URL of the thumbnail
|
||||
*/
|
||||
public setThumbnail(url: string | null): this {
|
||||
Reflect.set(this, 'thumbnail', url ? { url } : undefined);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timestamp of this embed
|
||||
*
|
||||
* @param timestamp The timestamp or date
|
||||
*/
|
||||
public setTimestamp(timestamp: number | Date | null = Date.now()): this {
|
||||
Reflect.set(this, 'timestamp', timestamp ? new Date(timestamp).toISOString() : undefined);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the title of this embed
|
||||
*
|
||||
* @param title The title
|
||||
*/
|
||||
public setTitle(title: string | null): this {
|
||||
Reflect.set(this, 'title', title ?? undefined);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL of this embed
|
||||
*
|
||||
* @param url The URL
|
||||
*/
|
||||
public setURL(url: string | null): this {
|
||||
Reflect.set(this, 'url', url ?? undefined);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the embed to a plain object
|
||||
*/
|
||||
public toJSON(): APIEmbed {
|
||||
return { ...this };
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes field input and resolves strings
|
||||
*
|
||||
* @param fields Fields to normalize
|
||||
*/
|
||||
public static normalizeFields(...fields: APIEmbedField[]): APIEmbedField[] {
|
||||
return fields
|
||||
.flat(Infinity)
|
||||
.map((field) => ({ name: field.name, value: field.value, inline: field.inline ?? undefined }));
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,7 @@ exports.CommandInteractionOptionResolver = require('./structures/CommandInteract
|
||||
exports.ContextMenuCommandInteraction = require('./structures/ContextMenuCommandInteraction');
|
||||
exports.DMChannel = require('./structures/DMChannel');
|
||||
exports.Embed = require('@discordjs/builders').Embed;
|
||||
exports.UnsafeEmbed = require('@discordjs/builders').UnsafeEmbed;
|
||||
exports.Emoji = require('./structures/Emoji').Emoji;
|
||||
exports.Guild = require('./structures/Guild').Guild;
|
||||
exports.GuildAuditLogs = require('./structures/GuildAuditLogs');
|
||||
@@ -182,8 +183,11 @@ exports.UserFlags = require('discord-api-types/v9').UserFlags;
|
||||
exports.WebhookType = require('discord-api-types/v9').WebhookType;
|
||||
exports.ActionRow = require('@discordjs/builders').ActionRow;
|
||||
exports.ButtonComponent = require('@discordjs/builders').ButtonComponent;
|
||||
exports.UnsafeButtonComponent = require('@discordjs/builders').UnsafeButtonComponent;
|
||||
exports.SelectMenuComponent = require('@discordjs/builders').SelectMenuComponent;
|
||||
exports.UnsafeSelectMenuComponent = require('@discordjs/builders').UnsafeButtonComponent;
|
||||
exports.SelectMenuOption = require('@discordjs/builders').SelectMenuOption;
|
||||
exports.UnsafeSelectMenuOption = require('@discordjs/builders').UnsafeSelectMenuOption;
|
||||
exports.DiscordAPIError = require('@discordjs/rest').DiscordAPIError;
|
||||
exports.HTTPError = require('@discordjs/rest').HTTPError;
|
||||
exports.RateLimitError = require('@discordjs/rest').RateLimitError;
|
||||
|
||||
7
packages/discord.js/typings/index.d.ts
vendored
7
packages/discord.js/typings/index.d.ts
vendored
@@ -4615,8 +4615,6 @@ export interface MessageMentionOptions {
|
||||
|
||||
export type MessageMentionTypes = 'roles' | 'users' | 'everyone';
|
||||
|
||||
export { Embed };
|
||||
|
||||
export interface MessageOptions {
|
||||
tts?: boolean;
|
||||
nonce?: string | number;
|
||||
@@ -5216,8 +5214,13 @@ export {
|
||||
export {
|
||||
ActionRow,
|
||||
ButtonComponent,
|
||||
UnsafeButtonComponent,
|
||||
SelectMenuComponent,
|
||||
UnsafeSelectMenuComponent,
|
||||
SelectMenuOption,
|
||||
UnsafeSelectMenuOption,
|
||||
ActionRowComponent,
|
||||
Embed,
|
||||
UnsafeEmbed,
|
||||
} from '@discordjs/builders';
|
||||
export { DiscordAPIError, HTTPError, RateLimitError } from '@discordjs/rest';
|
||||
|
||||
Reference in New Issue
Block a user