refactor: allow discord.js builders to accept camelCase (#7424)

This commit is contained in:
Suneet Tipirneni
2022-02-13 06:27:42 -05:00
committed by GitHub
parent f4953647ff
commit 94bf727cc3
10 changed files with 313 additions and 43 deletions

View File

@@ -70,6 +70,7 @@ exports.WebSocketManager = require('./client/websocket/WebSocketManager');
exports.WebSocketShard = require('./client/websocket/WebSocketShard');
// Structures
exports.ActionRow = require('./structures/ActionRow');
exports.Activity = require('./structures/Presence').Activity;
exports.AnonymousGuild = require('./structures/AnonymousGuild');
exports.Application = require('./structures/interfaces/Application');
@@ -80,6 +81,7 @@ exports.BaseGuild = require('./structures/BaseGuild');
exports.BaseGuildEmoji = require('./structures/BaseGuildEmoji');
exports.BaseGuildTextChannel = require('./structures/BaseGuildTextChannel');
exports.BaseGuildVoiceChannel = require('./structures/BaseGuildVoiceChannel');
exports.ButtonComponent = require('./structures/ButtonComponent');
exports.ButtonInteraction = require('./structures/ButtonInteraction');
exports.CategoryChannel = require('./structures/CategoryChannel');
exports.Channel = require('./structures/Channel').Channel;
@@ -92,7 +94,7 @@ exports.Collector = require('./structures/interfaces/Collector');
exports.CommandInteractionOptionResolver = require('./structures/CommandInteractionOptionResolver');
exports.ContextMenuCommandInteraction = require('./structures/ContextMenuCommandInteraction');
exports.DMChannel = require('./structures/DMChannel');
exports.Embed = require('@discordjs/builders').Embed;
exports.Embed = require('./structures/Embed');
exports.UnsafeEmbed = require('@discordjs/builders').UnsafeEmbed;
exports.Emoji = require('./structures/Emoji').Emoji;
exports.Guild = require('./structures/Guild').Guild;
@@ -131,6 +133,7 @@ exports.ReactionCollector = require('./structures/ReactionCollector');
exports.ReactionEmoji = require('./structures/ReactionEmoji');
exports.RichPresenceAssets = require('./structures/Presence').RichPresenceAssets;
exports.Role = require('./structures/Role').Role;
exports.SelectMenuComponent = require('./structures/SelectMenuComponent');
exports.SelectMenuInteraction = require('./structures/SelectMenuInteraction');
exports.StageChannel = require('./structures/StageChannel');
exports.StageInstance = require('./structures/StageInstance').StageInstance;
@@ -190,10 +193,7 @@ exports.StickerType = require('discord-api-types/v9').StickerType;
exports.StickerFormatType = require('discord-api-types/v9').StickerFormatType;
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').UnsafeSelectMenuComponent;
exports.SelectMenuOption = require('@discordjs/builders').SelectMenuOption;
exports.UnsafeSelectMenuOption = require('@discordjs/builders').UnsafeSelectMenuOption;

View File

@@ -0,0 +1,14 @@
'use strict';
const { ActionRow: BuildersActionRow } = require('@discordjs/builders');
const Components = require('../util/Components');
class ActionRow extends BuildersActionRow {
constructor(data) {
// TODO: Simplify when getters PR is merged.
const initData = Components.transformJSON(data);
super({ ...initData, components: initData.components ?? [] });
}
}
module.exports = ActionRow;

View File

@@ -0,0 +1,12 @@
'use strict';
const { ButtonComponent: BuildersButtonComponent } = require('@discordjs/builders');
const Components = require('../util/Components');
class ButtonComponent extends BuildersButtonComponent {
constructor(data) {
super(Components.transformJSON(data));
}
}
module.exports = ButtonComponent;

View File

@@ -0,0 +1,12 @@
'use strict';
const { Embed: BuildersEmbed } = require('@discordjs/builders');
const Embeds = require('../util/Embeds');
class Embed extends BuildersEmbed {
constructor(data) {
super({ ...Embeds.transformJSON(data) });
}
}
module.exports = Embed;

View File

@@ -1,10 +1,12 @@
'use strict';
const { Buffer } = require('node:buffer');
const { createComponent, Embed } = require('@discordjs/builders');
const { Embed, isJSONEncodable } = require('@discordjs/builders');
const { MessageFlags } = require('discord-api-types/v9');
const { RangeError } = require('../errors');
const Components = require('../util/Components');
const DataResolver = require('../util/DataResolver');
const Embeds = require('../util/Embeds');
const MessageFlagsBitField = require('../util/MessageFlagsBitField');
const Util = require('../util/Util');
@@ -131,7 +133,9 @@ class MessagePayload {
}
}
const components = this.options.components?.map(c => createComponent(c).toJSON());
const components = this.options.components?.map(c =>
isJSONEncodable(c) ? c.toJSON() : Components.transformJSON(c),
);
let username;
let avatarURL;
@@ -190,7 +194,9 @@ class MessagePayload {
content,
tts,
nonce,
embeds: this.options.embeds?.map(embed => (embed instanceof Embed ? embed : new Embed(embed)).toJSON()),
embeds: this.options.embeds?.map(embed =>
embed instanceof Embed ? embed.toJSON() : Embeds.transformJSON(embed),
),
components,
username,
avatar_url: avatarURL,

View File

@@ -0,0 +1,12 @@
'use strict';
const { SelectMenuComponent: BuildersSelectMenuComponent } = require('@discordjs/builders');
const Components = require('../util/Components');
class SelectMenuComponent extends BuildersSelectMenuComponent {
constructor(data) {
super(Components.transformJSON(data));
}
}
module.exports = SelectMenuComponent;

View File

@@ -0,0 +1,70 @@
'use strict';
/**
* @typedef {Object} BaseComponentData
* @property {ComponentType} type
*/
/**
* @typedef {BaseComponentData} ActionRowData
* @property {ComponentData[]} components
*/
/**
* @typedef {BaseComponentData} ButtonComponentData
* @property {ButtonStyle} style
* @property {?boolean} disabled
* @property {string} label
* @property {?APIComponentEmoji} emoji
* @property {?string} customId
* @property {?string} url
*/
/**
* @typedef {object} SelectMenuComponentOptionData
* @property {string} label
* @property {string} value
* @property {?string} description
* @property {?APIComponentEmoji} emoji
* @property {?boolean} default
*/
/**
* @typedef {BaseComponentData} SelectMenuComponentData
* @property {string} customId
* @property {?boolean} disabled
* @property {?number} maxValues
* @property {?number} minValues
* @property {?SelectMenuComponentOptionData[]} options
* @property {?string} placeholder
*/
/**
* @typedef {ActionRowData|ButtonComponentData|SelectMenuComponentData} ComponentData
*/
class Components extends null {
/**
* Transforms json data into api-compatible json data.
* @param {ComponentData|APIMessageComponent} data The data to transform.
* @returns {APIMessageComponentData}
*/
static transformJSON(data) {
return {
type: data?.type,
custom_id: data?.customId ?? data?.custom_id,
disabled: data?.disabled,
style: data?.style,
label: data?.label,
emoji: data?.emoji,
url: data?.url,
options: data?.options,
placeholder: data?.placeholder,
min_values: data?.minValues ?? data?.min_values,
max_values: data?.maxValues ?? data?.max_values,
components: data?.components?.map(c => Components.transformJSON(c)),
};
}
}
module.exports = Components;

View File

@@ -0,0 +1,81 @@
'use strict';
/**
* @typedef {Object} EmbedData
* @property {?string} title
* @property {?EmbedType} type
* @property {?string} description
* @property {?string} url
* @property {?string} timestamp
* @property {?number} color
* @property {?EmbedFooterData} footer
* @property {?EmbedImageData} image
* @property {?EmbedImageData} thumbnail
* @property {?EmbedProviderData} provider
* @property {?EmbedAuthorData} author
* @property {?EmbedFieldData[]} fields
*/
/**
* @typedef {Object} EmbedFooterData
* @property {string} text
* @property {?string} iconURL
*/
/**
* @typedef {Object} EmbedImageData
* @property {?string} url
*/
/**
* @typedef {Object} EmbedProviderData
* @property {?string} name
* @property {?string} url
*/
/**
* @typedef {Object} EmbedAuthorData
* @property {string} name
* @property {?string} url
* @property {?string} iconURL
*/
/**
* @typedef {Object} EmbedFieldData
* @property {string} name
* @property {string} value
* @property {?boolean} inline
*/
class Embeds extends null {
/**
* Transforms json data into api-compatible json data.
* @param {EmbedData|APIEmbed} data The data to transform.
* @returns {APIEmbed}
*/
static transformJSON(data) {
return {
title: data?.title,
type: data?.type,
description: data?.description,
url: data?.url,
timestamp: data?.timestamp,
color: data?.color,
footer: {
test: data?.footer?.text,
icon_url: data?.footer?.iconURL ?? data?.footer?.icon_url,
},
image: data?.image,
thumbnail: data?.thumbnail,
provider: data?.provider,
author: {
name: data?.author?.name,
text: data?.author?.text,
icon_url: data?.author?.iconURL ?? data?.author?.icon_url,
},
fields: data?.fields,
};
}
}
module.exports = Embeds;

View File

@@ -1,13 +1,13 @@
import {
ActionRow,
ActionRow as BuilderActionRow,
ActionRowComponent,
blockQuote,
bold,
ButtonComponent,
ButtonComponent as BuilderButtonComponent,
channelMention,
codeBlock,
Component,
Embed,
Embed as BuildersEmbed,
formatEmoji,
hideLinkEmbed,
hyperlink,
@@ -16,7 +16,7 @@ import {
memberNicknameMention,
quote,
roleMention,
SelectMenuComponent,
SelectMenuComponent as BuilderSelectMenuComponent,
spoiler,
strikethrough,
time,
@@ -90,6 +90,8 @@ import {
GuildSystemChannelFlags,
GatewayIntentBits,
ActivityFlags,
APIMessageComponentEmoji,
EmbedType,
} from 'discord-api-types/v9';
import { ChildProcess } from 'node:child_process';
import { EventEmitter } from 'node:events';
@@ -188,6 +190,20 @@ export class Activity {
export type ActivityFlagsString = keyof typeof ActivityFlags;
export interface BaseComponentData {
type?: ComponentType;
}
export type ActionRowComponentData = ButtonComponentData | SelectMenuComponentData;
export interface ActionRowData extends BaseComponentData {
components: ActionRowComponentData[];
}
export class ActionRow<T extends ActionRowComponent = ActionRowComponent> extends BuilderActionRow<T> {
constructor(data?: ActionRowData | APIActionRowComponent);
}
export class ActivityFlagsBitField extends BitField<ActivityFlagsString> {
public static Flags: typeof ActivityFlags;
public static resolve(bit?: BitFieldResolvable<ActivityFlagsString, number>): number;
@@ -451,6 +467,42 @@ export class ButtonInteraction<Cached extends CacheType = CacheType> extends Mes
public inRawGuild(): this is ButtonInteraction<'raw'>;
}
export class ButtonComponent extends BuilderButtonComponent {
public constructor(data?: ButtonComponentData | APIButtonComponent);
}
export class SelectMenuComponent extends BuilderSelectMenuComponent {
public constructor(data?: SelectMenuComponentData | APISelectMenuComponent);
}
export interface EmbedData {
title?: string;
type?: EmbedType;
description?: string;
url?: string;
timestamp?: string;
color?: number;
footer?: EmbedFooterData;
image?: EmbedImageData;
thumbnail?: EmbedImageData;
provider?: EmbedProviderData;
author?: EmbedAuthorData;
fields?: EmbedFieldData[];
}
export interface EmbedImageData {
url?: string;
}
export interface EmbedProviderData {
name?: string;
url?: string;
}
export class Embed extends BuildersEmbed {
public constructor(data?: EmbedData | APIEmbed);
}
export interface MappedChannelCategoryTypes {
[ChannelType.GuildNews]: NewsChannel;
[ChannelType.GuildVoice]: VoiceChannel;
@@ -2354,6 +2406,18 @@ export class Formatters extends null {
public static userMention: typeof userMention;
}
export type ComponentData = ActionRowComponentData | ButtonComponentData | SelectMenuComponentData;
export class Components extends null {
private constructor();
public static transformJSON(data: ComponentData | APIMessageComponent): APIMessageComponent;
}
export class Embeds extends null {
private constructor();
public static transformJSON(data: EmbedData | APIEmbed): APIEmbed;
}
export class VoiceChannel extends BaseGuildVoiceChannel {
public readonly speakable: boolean;
public type: ChannelType.GuildVoice;
@@ -3354,10 +3418,6 @@ export interface ThreadMemberFetchOptions extends BaseFetchOptions {
member?: UserResolvable;
}
export interface BaseMessageComponentOptions {
type?: ComponentType;
}
export type BitFieldResolvable<T extends string, N extends number | bigint> =
| RecursiveReadonlyArray<T | N | `${bigint}` | Readonly<BitField<T, N>>>
| T
@@ -4469,37 +4529,33 @@ export interface MakeErrorOptions {
export type MemberMention = UserMention | `<@!${Snowflake}>`;
export type ActionRowComponentOptions =
| (Required<BaseMessageComponentOptions> & MessageButtonOptions)
| (Required<BaseMessageComponentOptions> & MessageSelectMenuOptions);
| (Required<BaseComponentData> & ButtonComponentData)
| (Required<BaseComponentData> & SelectMenuComponentData);
export type MessageActionRowComponentResolvable = ActionRowComponent | ActionRowComponentOptions;
export interface ActionRowOptions extends BaseMessageComponentOptions {
components: ActionRowComponent[];
}
export interface MessageActivity {
partyId: string;
type: number;
}
export interface BaseButtonOptions extends BaseMessageComponentOptions {
export interface BaseButtonComponentData extends BaseComponentData {
disabled?: boolean;
emoji?: EmojiIdentifierResolvable;
emoji?: APIMessageComponentEmoji;
label?: string;
}
export interface LinkButtonOptions extends BaseButtonOptions {
style: 'Link' | ButtonStyle.Link;
export interface LinkButtonComponentData extends BaseButtonComponentData {
style: ButtonStyle.Link;
url: string;
}
export interface InteractionButtonOptions extends BaseButtonOptions {
export interface InteractionButtonComponentData extends BaseButtonComponentData {
style: Exclude<ButtonStyle, ButtonStyle.Link>;
customId: string;
}
export type MessageButtonOptions = InteractionButtonOptions | LinkButtonOptions;
export type ButtonComponentData = InteractionButtonComponentData | LinkButtonComponentData;
export interface MessageCollectorOptions extends CollectorOptions<[Message]> {
max?: number;
@@ -4518,12 +4574,6 @@ export type MessageChannelComponentCollectorOptions<T extends MessageComponentIn
'channel' | 'guild' | 'interactionType'
>;
export type MessageComponentOptions =
| BaseMessageComponentOptions
| ActionRowOptions
| MessageButtonOptions
| MessageSelectMenuOptions;
export interface MessageEditOptions {
attachments?: MessageAttachment[];
content?: string | null;
@@ -4531,7 +4581,7 @@ export interface MessageEditOptions {
files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[];
flags?: BitFieldResolvable<MessageFlagsString, number>;
allowedMentions?: MessageMentionOptions;
components?: (ActionRow<ActionRowComponent> | (Required<BaseMessageComponentOptions> & ActionRowOptions))[];
components?: (ActionRow<ActionRowComponent> | (Required<BaseComponentData> & ActionRowData))[];
}
export interface MessageEvent {
@@ -4568,7 +4618,7 @@ export interface MessageOptions {
nonce?: string | number;
content?: string | null;
embeds?: (Embed | APIEmbed)[];
components?: (ActionRow<ActionRowComponent> | (Required<BaseMessageComponentOptions> & ActionRowOptions))[];
components?: (ActionRow<ActionRowComponent> | (Required<BaseComponentData> & ActionRowData))[];
allowedMentions?: MessageMentionOptions;
files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[];
reply?: ReplyOptions;
@@ -4593,12 +4643,12 @@ export interface MessageReference {
export type MessageResolvable = Message | Snowflake;
export interface MessageSelectMenuOptions extends BaseMessageComponentOptions {
export interface SelectMenuComponentData extends BaseComponentData {
customId?: string;
disabled?: boolean;
maxValues?: number;
minValues?: number;
options?: MessageSelectOptionData[];
options?: SelectMenuComponentOptionData[];
placeholder?: string;
}
@@ -4610,10 +4660,10 @@ export interface MessageSelectOption {
value: string;
}
export interface MessageSelectOptionData {
export interface SelectMenuComponentOptionData {
default?: boolean;
description?: string;
emoji?: EmojiIdentifierResolvable;
emoji?: APIMessageComponentEmoji;
label: string;
value: string;
}
@@ -5116,15 +5166,11 @@ export {
WebhookType,
} from 'discord-api-types/v9';
export {
ActionRow,
ButtonComponent,
UnsafeButtonComponent,
SelectMenuComponent,
UnsafeSelectMenuComponent,
SelectMenuOption,
UnsafeSelectMenuOption,
ActionRowComponent,
Embed,
UnsafeEmbed,
} from '@discordjs/builders';
export { DiscordAPIError, HTTPError, RateLimitError } from '@discordjs/rest';

View File

@@ -16,6 +16,7 @@ import {
InteractionType,
GatewayIntentBits,
PermissionFlagsBits,
ButtonStyle,
} from 'discord-api-types/v9';
import { AuditLogEvent } from 'discord-api-types/v9';
import {
@@ -1322,3 +1323,19 @@ expectType<CategoryChannel | NewsChannel | StageChannel | StoreChannel | TextCha
NonThreadGuildBasedChannel,
);
expectType<NewsChannel | TextChannel | ThreadChannel>(GuildTextBasedChannel);
const button = new ButtonComponent({
label: 'test',
style: ButtonStyle.Primary,
customId: 'test',
});
const selectMenu = new SelectMenuComponent({
maxValues: 10,
minValues: 2,
customId: 'test',
});
new ActionRow({
components: [selectMenu, button],
});