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'); exports.WebSocketShard = require('./client/websocket/WebSocketShard');
// Structures // Structures
exports.ActionRow = require('./structures/ActionRow');
exports.Activity = require('./structures/Presence').Activity; exports.Activity = require('./structures/Presence').Activity;
exports.AnonymousGuild = require('./structures/AnonymousGuild'); exports.AnonymousGuild = require('./structures/AnonymousGuild');
exports.Application = require('./structures/interfaces/Application'); exports.Application = require('./structures/interfaces/Application');
@@ -80,6 +81,7 @@ exports.BaseGuild = require('./structures/BaseGuild');
exports.BaseGuildEmoji = require('./structures/BaseGuildEmoji'); exports.BaseGuildEmoji = require('./structures/BaseGuildEmoji');
exports.BaseGuildTextChannel = require('./structures/BaseGuildTextChannel'); exports.BaseGuildTextChannel = require('./structures/BaseGuildTextChannel');
exports.BaseGuildVoiceChannel = require('./structures/BaseGuildVoiceChannel'); exports.BaseGuildVoiceChannel = require('./structures/BaseGuildVoiceChannel');
exports.ButtonComponent = require('./structures/ButtonComponent');
exports.ButtonInteraction = require('./structures/ButtonInteraction'); exports.ButtonInteraction = require('./structures/ButtonInteraction');
exports.CategoryChannel = require('./structures/CategoryChannel'); exports.CategoryChannel = require('./structures/CategoryChannel');
exports.Channel = require('./structures/Channel').Channel; exports.Channel = require('./structures/Channel').Channel;
@@ -92,7 +94,7 @@ exports.Collector = require('./structures/interfaces/Collector');
exports.CommandInteractionOptionResolver = require('./structures/CommandInteractionOptionResolver'); exports.CommandInteractionOptionResolver = require('./structures/CommandInteractionOptionResolver');
exports.ContextMenuCommandInteraction = require('./structures/ContextMenuCommandInteraction'); exports.ContextMenuCommandInteraction = require('./structures/ContextMenuCommandInteraction');
exports.DMChannel = require('./structures/DMChannel'); exports.DMChannel = require('./structures/DMChannel');
exports.Embed = require('@discordjs/builders').Embed; exports.Embed = require('./structures/Embed');
exports.UnsafeEmbed = require('@discordjs/builders').UnsafeEmbed; exports.UnsafeEmbed = require('@discordjs/builders').UnsafeEmbed;
exports.Emoji = require('./structures/Emoji').Emoji; exports.Emoji = require('./structures/Emoji').Emoji;
exports.Guild = require('./structures/Guild').Guild; exports.Guild = require('./structures/Guild').Guild;
@@ -131,6 +133,7 @@ exports.ReactionCollector = require('./structures/ReactionCollector');
exports.ReactionEmoji = require('./structures/ReactionEmoji'); exports.ReactionEmoji = require('./structures/ReactionEmoji');
exports.RichPresenceAssets = require('./structures/Presence').RichPresenceAssets; exports.RichPresenceAssets = require('./structures/Presence').RichPresenceAssets;
exports.Role = require('./structures/Role').Role; exports.Role = require('./structures/Role').Role;
exports.SelectMenuComponent = require('./structures/SelectMenuComponent');
exports.SelectMenuInteraction = require('./structures/SelectMenuInteraction'); exports.SelectMenuInteraction = require('./structures/SelectMenuInteraction');
exports.StageChannel = require('./structures/StageChannel'); exports.StageChannel = require('./structures/StageChannel');
exports.StageInstance = require('./structures/StageInstance').StageInstance; 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.StickerFormatType = require('discord-api-types/v9').StickerFormatType;
exports.UserFlags = require('discord-api-types/v9').UserFlags; exports.UserFlags = require('discord-api-types/v9').UserFlags;
exports.WebhookType = require('discord-api-types/v9').WebhookType; 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.UnsafeButtonComponent = require('@discordjs/builders').UnsafeButtonComponent;
exports.SelectMenuComponent = require('@discordjs/builders').SelectMenuComponent;
exports.UnsafeSelectMenuComponent = require('@discordjs/builders').UnsafeSelectMenuComponent; exports.UnsafeSelectMenuComponent = require('@discordjs/builders').UnsafeSelectMenuComponent;
exports.SelectMenuOption = require('@discordjs/builders').SelectMenuOption; exports.SelectMenuOption = require('@discordjs/builders').SelectMenuOption;
exports.UnsafeSelectMenuOption = require('@discordjs/builders').UnsafeSelectMenuOption; 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'; 'use strict';
const { Buffer } = require('node:buffer'); 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 { MessageFlags } = require('discord-api-types/v9');
const { RangeError } = require('../errors'); const { RangeError } = require('../errors');
const Components = require('../util/Components');
const DataResolver = require('../util/DataResolver'); const DataResolver = require('../util/DataResolver');
const Embeds = require('../util/Embeds');
const MessageFlagsBitField = require('../util/MessageFlagsBitField'); const MessageFlagsBitField = require('../util/MessageFlagsBitField');
const Util = require('../util/Util'); 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 username;
let avatarURL; let avatarURL;
@@ -190,7 +194,9 @@ class MessagePayload {
content, content,
tts, tts,
nonce, 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, components,
username, username,
avatar_url: avatarURL, 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 { import {
ActionRow, ActionRow as BuilderActionRow,
ActionRowComponent, ActionRowComponent,
blockQuote, blockQuote,
bold, bold,
ButtonComponent, ButtonComponent as BuilderButtonComponent,
channelMention, channelMention,
codeBlock, codeBlock,
Component, Component,
Embed, Embed as BuildersEmbed,
formatEmoji, formatEmoji,
hideLinkEmbed, hideLinkEmbed,
hyperlink, hyperlink,
@@ -16,7 +16,7 @@ import {
memberNicknameMention, memberNicknameMention,
quote, quote,
roleMention, roleMention,
SelectMenuComponent, SelectMenuComponent as BuilderSelectMenuComponent,
spoiler, spoiler,
strikethrough, strikethrough,
time, time,
@@ -90,6 +90,8 @@ import {
GuildSystemChannelFlags, GuildSystemChannelFlags,
GatewayIntentBits, GatewayIntentBits,
ActivityFlags, ActivityFlags,
APIMessageComponentEmoji,
EmbedType,
} from 'discord-api-types/v9'; } from 'discord-api-types/v9';
import { ChildProcess } from 'node:child_process'; import { ChildProcess } from 'node:child_process';
import { EventEmitter } from 'node:events'; import { EventEmitter } from 'node:events';
@@ -188,6 +190,20 @@ export class Activity {
export type ActivityFlagsString = keyof typeof ActivityFlags; 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> { export class ActivityFlagsBitField extends BitField<ActivityFlagsString> {
public static Flags: typeof ActivityFlags; public static Flags: typeof ActivityFlags;
public static resolve(bit?: BitFieldResolvable<ActivityFlagsString, number>): number; 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'>; 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 { export interface MappedChannelCategoryTypes {
[ChannelType.GuildNews]: NewsChannel; [ChannelType.GuildNews]: NewsChannel;
[ChannelType.GuildVoice]: VoiceChannel; [ChannelType.GuildVoice]: VoiceChannel;
@@ -2354,6 +2406,18 @@ export class Formatters extends null {
public static userMention: typeof userMention; 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 { export class VoiceChannel extends BaseGuildVoiceChannel {
public readonly speakable: boolean; public readonly speakable: boolean;
public type: ChannelType.GuildVoice; public type: ChannelType.GuildVoice;
@@ -3354,10 +3418,6 @@ export interface ThreadMemberFetchOptions extends BaseFetchOptions {
member?: UserResolvable; member?: UserResolvable;
} }
export interface BaseMessageComponentOptions {
type?: ComponentType;
}
export type BitFieldResolvable<T extends string, N extends number | bigint> = export type BitFieldResolvable<T extends string, N extends number | bigint> =
| RecursiveReadonlyArray<T | N | `${bigint}` | Readonly<BitField<T, N>>> | RecursiveReadonlyArray<T | N | `${bigint}` | Readonly<BitField<T, N>>>
| T | T
@@ -4469,37 +4529,33 @@ export interface MakeErrorOptions {
export type MemberMention = UserMention | `<@!${Snowflake}>`; export type MemberMention = UserMention | `<@!${Snowflake}>`;
export type ActionRowComponentOptions = export type ActionRowComponentOptions =
| (Required<BaseMessageComponentOptions> & MessageButtonOptions) | (Required<BaseComponentData> & ButtonComponentData)
| (Required<BaseMessageComponentOptions> & MessageSelectMenuOptions); | (Required<BaseComponentData> & SelectMenuComponentData);
export type MessageActionRowComponentResolvable = ActionRowComponent | ActionRowComponentOptions; export type MessageActionRowComponentResolvable = ActionRowComponent | ActionRowComponentOptions;
export interface ActionRowOptions extends BaseMessageComponentOptions {
components: ActionRowComponent[];
}
export interface MessageActivity { export interface MessageActivity {
partyId: string; partyId: string;
type: number; type: number;
} }
export interface BaseButtonOptions extends BaseMessageComponentOptions { export interface BaseButtonComponentData extends BaseComponentData {
disabled?: boolean; disabled?: boolean;
emoji?: EmojiIdentifierResolvable; emoji?: APIMessageComponentEmoji;
label?: string; label?: string;
} }
export interface LinkButtonOptions extends BaseButtonOptions { export interface LinkButtonComponentData extends BaseButtonComponentData {
style: 'Link' | ButtonStyle.Link; style: ButtonStyle.Link;
url: string; url: string;
} }
export interface InteractionButtonOptions extends BaseButtonOptions { export interface InteractionButtonComponentData extends BaseButtonComponentData {
style: Exclude<ButtonStyle, ButtonStyle.Link>; style: Exclude<ButtonStyle, ButtonStyle.Link>;
customId: string; customId: string;
} }
export type MessageButtonOptions = InteractionButtonOptions | LinkButtonOptions; export type ButtonComponentData = InteractionButtonComponentData | LinkButtonComponentData;
export interface MessageCollectorOptions extends CollectorOptions<[Message]> { export interface MessageCollectorOptions extends CollectorOptions<[Message]> {
max?: number; max?: number;
@@ -4518,12 +4574,6 @@ export type MessageChannelComponentCollectorOptions<T extends MessageComponentIn
'channel' | 'guild' | 'interactionType' 'channel' | 'guild' | 'interactionType'
>; >;
export type MessageComponentOptions =
| BaseMessageComponentOptions
| ActionRowOptions
| MessageButtonOptions
| MessageSelectMenuOptions;
export interface MessageEditOptions { export interface MessageEditOptions {
attachments?: MessageAttachment[]; attachments?: MessageAttachment[];
content?: string | null; content?: string | null;
@@ -4531,7 +4581,7 @@ export interface MessageEditOptions {
files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[]; files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[];
flags?: BitFieldResolvable<MessageFlagsString, number>; flags?: BitFieldResolvable<MessageFlagsString, number>;
allowedMentions?: MessageMentionOptions; allowedMentions?: MessageMentionOptions;
components?: (ActionRow<ActionRowComponent> | (Required<BaseMessageComponentOptions> & ActionRowOptions))[]; components?: (ActionRow<ActionRowComponent> | (Required<BaseComponentData> & ActionRowData))[];
} }
export interface MessageEvent { export interface MessageEvent {
@@ -4568,7 +4618,7 @@ export interface MessageOptions {
nonce?: string | number; nonce?: string | number;
content?: string | null; content?: string | null;
embeds?: (Embed | APIEmbed)[]; embeds?: (Embed | APIEmbed)[];
components?: (ActionRow<ActionRowComponent> | (Required<BaseMessageComponentOptions> & ActionRowOptions))[]; components?: (ActionRow<ActionRowComponent> | (Required<BaseComponentData> & ActionRowData))[];
allowedMentions?: MessageMentionOptions; allowedMentions?: MessageMentionOptions;
files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[]; files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[];
reply?: ReplyOptions; reply?: ReplyOptions;
@@ -4593,12 +4643,12 @@ export interface MessageReference {
export type MessageResolvable = Message | Snowflake; export type MessageResolvable = Message | Snowflake;
export interface MessageSelectMenuOptions extends BaseMessageComponentOptions { export interface SelectMenuComponentData extends BaseComponentData {
customId?: string; customId?: string;
disabled?: boolean; disabled?: boolean;
maxValues?: number; maxValues?: number;
minValues?: number; minValues?: number;
options?: MessageSelectOptionData[]; options?: SelectMenuComponentOptionData[];
placeholder?: string; placeholder?: string;
} }
@@ -4610,10 +4660,10 @@ export interface MessageSelectOption {
value: string; value: string;
} }
export interface MessageSelectOptionData { export interface SelectMenuComponentOptionData {
default?: boolean; default?: boolean;
description?: string; description?: string;
emoji?: EmojiIdentifierResolvable; emoji?: APIMessageComponentEmoji;
label: string; label: string;
value: string; value: string;
} }
@@ -5116,15 +5166,11 @@ export {
WebhookType, WebhookType,
} from 'discord-api-types/v9'; } from 'discord-api-types/v9';
export { export {
ActionRow,
ButtonComponent,
UnsafeButtonComponent, UnsafeButtonComponent,
SelectMenuComponent,
UnsafeSelectMenuComponent, UnsafeSelectMenuComponent,
SelectMenuOption, SelectMenuOption,
UnsafeSelectMenuOption, UnsafeSelectMenuOption,
ActionRowComponent, ActionRowComponent,
Embed,
UnsafeEmbed, UnsafeEmbed,
} from '@discordjs/builders'; } from '@discordjs/builders';
export { DiscordAPIError, HTTPError, RateLimitError } from '@discordjs/rest'; export { DiscordAPIError, HTTPError, RateLimitError } from '@discordjs/rest';

View File

@@ -16,6 +16,7 @@ import {
InteractionType, InteractionType,
GatewayIntentBits, GatewayIntentBits,
PermissionFlagsBits, PermissionFlagsBits,
ButtonStyle,
} from 'discord-api-types/v9'; } from 'discord-api-types/v9';
import { AuditLogEvent } from 'discord-api-types/v9'; import { AuditLogEvent } from 'discord-api-types/v9';
import { import {
@@ -1322,3 +1323,19 @@ expectType<CategoryChannel | NewsChannel | StageChannel | StoreChannel | TextCha
NonThreadGuildBasedChannel, NonThreadGuildBasedChannel,
); );
expectType<NewsChannel | TextChannel | ThreadChannel>(GuildTextBasedChannel); 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],
});