Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(components): Add unsafe message component builders #7387

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/builders/__tests__/components/button.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
42 changes: 42 additions & 0 deletions packages/builders/src/components/button/Button.ts
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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,
};
Expand Down
102 changes: 15 additions & 87 deletions packages/builders/src/components/selectMenu/SelectMenu.ts
Original file line number Diff line number Diff line change
@@ -1,111 +1,39 @@
import { APISelectMenuComponent, ComponentType } from 'discord-api-types/v9';
import type { APISelectMenuComponent } from 'discord-api-types/v9';
import {
customIdValidator,
disabledValidator,
minMaxValidator,
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;
}

/**
* 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;
}

/**
* 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;
}

/**
* 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;
export class SelectMenuComponent extends UnsafeSelectMenuComponent {
public override setPlaceholder(placeholder: string) {
return super.setPlaceholder(placeholderValidator.parse(placeholder));
}

/**
* 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 setMinValues(minValues: number) {
return super.setMinValues(minMaxValidator.parse(minValues));
}

/**
* 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;
public override setMaxValues(maxValues: number) {
return super.setMaxValues(minMaxValidator.parse(maxValues));
}

/**
* 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;
public override setCustomId(customId: string) {
return super.setCustomId(customIdValidator.parse(customId));
}

/**
* 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 override setDisabled(disabled: boolean) {
return super.setDisabled(disabledValidator.parse(disabled));
}

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();
}
}
73 changes: 10 additions & 63 deletions packages/builders/src/components/selectMenu/SelectMenuOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
* 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) {
labelValueValidator.parse(description);
Reflect.set(this, 'description', description);
return this;
export class SelectMenuOption extends UnsafeSelectMenuOption {
public override setDescription(description: string) {
return super.setDescription(labelValueValidator.parse(description));
}

/**
* 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;
public override setDefault(isDefault: boolean) {
return super.setDefault(defaultValidator.parse(isDefault));
}

/**
* 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 override setEmoji(emoji: APIMessageComponentEmoji) {
return super.setEmoji(emojiValidator.parse(emoji));
}

public toJSON(): APISelectMenuOption {
public override toJSON(): APISelectMenuOption {
validateRequiredSelectMenuOptionParameters(this.label, this.value);
return {
...this,
};
return super.toJSON();
}
}