From 17e74dce4526e0b3192eb52a933a945bdc1c3921 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Thu, 22 Apr 2021 09:18:45 +0530 Subject: [PATCH 01/40] make interactions model better --- mod.ts | 3 +- src/gateway/handlers/interactionCreate.ts | 37 ++- src/gateway/handlers/mod.ts | 6 +- src/interactions/slashClient.ts | 70 +++-- src/interactions/slashCommand.ts | 2 +- src/managers/channels.ts | 1 + src/rest/endpoints.ts | 4 +- src/structures/interactions.ts | 354 ++++++++++++++++++++++ src/structures/message.ts | 2 +- src/structures/slash.ts | 338 +-------------------- src/types/channel.ts | 5 +- src/types/gateway.ts | 2 +- src/types/interactions.ts | 70 +++++ src/types/messageComponents.ts | 20 ++ src/types/{slash.ts => slashCommands.ts} | 72 +---- 15 files changed, 545 insertions(+), 441 deletions(-) create mode 100644 src/structures/interactions.ts create mode 100644 src/types/interactions.ts create mode 100644 src/types/messageComponents.ts rename src/types/{slash.ts => slashCommands.ts} (55%) diff --git a/mod.ts b/mod.ts index 8536510..d05784b 100644 --- a/mod.ts +++ b/mod.ts @@ -39,7 +39,8 @@ export { GuildChannelsManager } from './src/managers/guildChannels.ts' export { GuildManager } from './src/managers/guilds.ts' export * from './src/structures/base.ts' export * from './src/structures/slash.ts' -export * from './src/types/slash.ts' +export * from './src/types/slashCommands.ts' +export * from './src/types/interactions.ts' export { GuildEmojisManager } from './src/managers/guildEmojis.ts' export { MembersManager } from './src/managers/members.ts' export { MessageReactionsManager } from './src/managers/messageReactions.ts' diff --git a/src/gateway/handlers/interactionCreate.ts b/src/gateway/handlers/interactionCreate.ts index 3d4c5d4..156441b 100644 --- a/src/gateway/handlers/interactionCreate.ts +++ b/src/gateway/handlers/interactionCreate.ts @@ -1,12 +1,18 @@ import { Guild } from '../../structures/guild.ts' import { Member } from '../../structures/member.ts' import { - Interaction, InteractionApplicationCommandResolved, - InteractionChannel + SlashCommandInteraction } from '../../structures/slash.ts' +import { + Interaction, + InteractionChannel +} from '../../structures/interactions.ts' import { GuildTextBasedChannel } from '../../structures/guildTextChannel.ts' -import { InteractionPayload } from '../../types/slash.ts' +import { + InteractionPayload, + InteractionType +} from '../../types/interactions.ts' import { UserPayload } from '../../types/user.ts' import { Permissions } from '../../utils/permissions.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts' @@ -99,12 +105,23 @@ export const interactionCreate: GatewayEventHandler = async ( } } - const interaction = new Interaction(gateway.client, d, { - member, - guild, - channel, - user, - resolved - }) + let interaction + if (d.type === InteractionType.APPLICATION_COMMAND) { + interaction = new SlashCommandInteraction(gateway.client, d, { + member, + guild, + channel, + user, + resolved + }) + } else { + interaction = new Interaction(gateway.client, d, { + member, + guild, + channel, + user + }) + } + gateway.client.emit('interactionCreate', interaction) } diff --git a/src/gateway/handlers/mod.ts b/src/gateway/handlers/mod.ts index 1c299f3..dc649e8 100644 --- a/src/gateway/handlers/mod.ts +++ b/src/gateway/handlers/mod.ts @@ -59,7 +59,8 @@ import type { EveryTextChannelTypes } from '../../utils/channel.ts' import { interactionCreate } from './interactionCreate.ts' -import type { Interaction } from '../../structures/slash.ts' +import type { Interaction } from '../../structures/interactions.ts' +import type { SlashCommandInteraction } from '../../structures/slash.ts' import type { CommandContext } from '../../commands/command.ts' import type { RequestMethods } from '../../rest/types.ts' import type { PartialInvitePayload } from '../../types/invite.ts' @@ -358,11 +359,12 @@ export type ClientEvents = { * @param channel Channel of which Webhooks were updated */ webhooksUpdate: [guild: Guild, channel: GuildTextBasedChannel] + /** * An Interaction was created * @param interaction Created interaction object */ - interactionCreate: [interaction: Interaction] + interactionCreate: [interaction: Interaction | SlashCommandInteraction] /** * When debug message was made diff --git a/src/interactions/slashClient.ts b/src/interactions/slashClient.ts index 29e808c..9efd4c9 100644 --- a/src/interactions/slashClient.ts +++ b/src/interactions/slashClient.ts @@ -1,13 +1,14 @@ import { - Interaction, + SlashCommandInteraction, InteractionApplicationCommandResolved } from '../structures/slash.ts' +import { Interaction } from '../structures/interactions.ts' import { InteractionPayload, InteractionResponsePayload, - InteractionType, - SlashCommandOptionType -} from '../types/slash.ts' + InteractionType +} from '../types/interactions.ts' +import { SlashCommandOptionType } from '../types/slashCommands.ts' import type { Client } from '../client/mod.ts' import { RESTManager } from '../rest/mod.ts' import { SlashModule } from './slashModule.ts' @@ -170,7 +171,9 @@ export class SlashClient extends HarmonyEventEmitter { } /** Get Handler for an Interaction. Supports nested sub commands and sub command groups. */ - private _getCommand(i: Interaction): SlashCommandHandler | undefined { + private _getCommand( + i: SlashCommandInteraction + ): SlashCommandHandler | undefined { return this.getHandlers().find((e) => { const hasGroupOrParent = e.group !== undefined || e.parent !== undefined const groupMatched = @@ -201,22 +204,22 @@ export class SlashClient extends HarmonyEventEmitter { } /** Process an incoming Interaction */ - private async _process(interaction: Interaction): Promise { + private async _process( + interaction: Interaction | SlashCommandInteraction + ): Promise { if (!this.enabled) return - if ( - interaction.type !== InteractionType.APPLICATION_COMMAND || - interaction.data === undefined - ) - return + if (interaction.type !== InteractionType.APPLICATION_COMMAND) return const cmd = - this._getCommand(interaction) ?? + this._getCommand(interaction as SlashCommandInteraction) ?? this.getHandlers().find((e) => e.name === '*') if (cmd?.group !== undefined) - interaction.data.options = interaction.data.options[0].options ?? [] + (interaction as SlashCommandInteraction).data.options = + (interaction as SlashCommandInteraction).data.options[0].options ?? [] if (cmd?.parent !== undefined) - interaction.data.options = interaction.data.options[0].options ?? [] + (interaction as SlashCommandInteraction).data.options = + (interaction as SlashCommandInteraction).data.options[0].options ?? [] if (cmd === undefined) return @@ -271,20 +274,31 @@ export class SlashClient extends HarmonyEventEmitter { const payload: InteractionPayload = JSON.parse(decodeText(rawbody)) // TODO: Maybe fix all this hackery going on here? - const res = new Interaction(this as any, payload, { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - user: new User(this as any, (payload.member?.user ?? payload.user)!), - member: payload.member as any, - guild: payload.guild_id as any, - channel: payload.channel_id as any, - resolved: ((payload.data - ?.resolved as unknown) as InteractionApplicationCommandResolved) ?? { - users: {}, - members: {}, - roles: {}, - channels: {} - } - }) + let res + if (payload.type === InteractionType.APPLICATION_COMMAND) { + res = new SlashCommandInteraction(this as any, payload, { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + user: new User(this as any, (payload.member?.user ?? payload.user)!), + member: payload.member as any, + guild: payload.guild_id as any, + channel: payload.channel_id as any, + resolved: ((payload.data + ?.resolved as unknown) as InteractionApplicationCommandResolved) ?? { + users: {}, + members: {}, + roles: {}, + channels: {} + } + }) + } else { + res = new Interaction(this as any, payload, { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + user: new User(this as any, (payload.member?.user ?? payload.user)!), + member: payload.member as any, + guild: payload.guild_id as any, + channel: payload.channel_id as any + }) + } res._httpRespond = async (d: InteractionResponsePayload | FormData) => await req.respond({ status: 200, diff --git a/src/interactions/slashCommand.ts b/src/interactions/slashCommand.ts index 26237d3..e079bb2 100644 --- a/src/interactions/slashCommand.ts +++ b/src/interactions/slashCommand.ts @@ -6,7 +6,7 @@ import { SlashCommandOptionType, SlashCommandPartial, SlashCommandPayload -} from '../types/slash.ts' +} from '../types/slashCommands.ts' import { Collection } from '../utils/collection.ts' import type { SlashClient, SlashCommandHandlerCallback } from './slashClient.ts' diff --git a/src/managers/channels.ts b/src/managers/channels.ts index 69efe6c..2caca4d 100644 --- a/src/managers/channels.ts +++ b/src/managers/channels.ts @@ -100,6 +100,7 @@ export class ChannelsManager extends BaseManager { content: content, embed: option?.embed, file: option?.file, + components: option?.components, files: option?.files, tts: option?.tts, allowed_mentions: option?.allowedMentions, diff --git a/src/rest/endpoints.ts b/src/rest/endpoints.ts index ff14299..31c70ce 100644 --- a/src/rest/endpoints.ts +++ b/src/rest/endpoints.ts @@ -30,11 +30,11 @@ import type { InviteWithMetadataPayload } from '../types/invite.ts' import type { RoleModifyPayload, RolePayload } from '../types/role.ts' +import type { InteractionResponsePayload } from '../types/interactions.ts' import type { - InteractionResponsePayload, SlashCommandPartial, SlashCommandPayload -} from '../types/slash.ts' +} from '../types/slashCommands.ts' import type { TemplatePayload } from '../types/template.ts' import type { UserPayload } from '../types/user.ts' import type { VoiceRegion } from '../types/voice.ts' diff --git a/src/structures/interactions.ts b/src/structures/interactions.ts new file mode 100644 index 0000000..dad5e0c --- /dev/null +++ b/src/structures/interactions.ts @@ -0,0 +1,354 @@ +import type { Client } from '../client/client.ts' +import { + AllowedMentionsPayload, + ChannelTypes, + EmbedPayload, + MessageOptions +} from '../types/channel.ts' +import { INTERACTION_CALLBACK, WEBHOOK_MESSAGE } from '../types/endpoint.ts' +import { + InteractionPayload, + InteractionResponseFlags, + InteractionResponsePayload, + InteractionResponseType, + InteractionType +} from '../types/interactions.ts' +import { + InteractionApplicationCommandData, + InteractionChannelPayload +} from '../types/slashCommands.ts' +import { Dict } from '../utils/dict.ts' +import { Permissions } from '../utils/permissions.ts' +import { SnowflakeBase } from './base.ts' +import { Channel } from './channel.ts' +import { Embed } from './embed.ts' +import { Guild } from './guild.ts' +import { GuildTextChannel } from './guildTextChannel.ts' +import { Member } from './member.ts' +import { Message } from './message.ts' +import { Role } from './role.ts' +import { TextChannel } from './textChannel.ts' +import { User } from './user.ts' + +interface WebhookMessageOptions extends MessageOptions { + embeds?: Array + name?: string + avatar?: string +} + +type AllWebhookMessageOptions = string | WebhookMessageOptions + +/** Interaction Message related Options */ +export interface InteractionMessageOptions { + content?: string + embeds?: Array + tts?: boolean + flags?: number | InteractionResponseFlags[] + allowedMentions?: AllowedMentionsPayload + /** Whether the Message Response should be Ephemeral (only visible to User) or not */ + ephemeral?: boolean +} + +export interface InteractionResponse extends InteractionMessageOptions { + /** Type of Interaction Response */ + type?: InteractionResponseType +} + +/** Represents a Channel Object for an Option in Slash Command */ +export class InteractionChannel extends SnowflakeBase { + /** Name of the Channel */ + name: string + /** Channel Type */ + type: ChannelTypes + permissions: Permissions + + constructor(client: Client, data: InteractionChannelPayload) { + super(client) + this.id = data.id + this.name = data.name + this.type = data.type + this.permissions = new Permissions(data.permissions) + } + + /** Resolve to actual Channel object if present in Cache */ + async resolve(): Promise { + return this.client.channels.get(this.id) + } +} + +export interface InteractionApplicationCommandResolved { + users: Dict + members: Dict + channels: Dict + roles: Dict +} + +export class InteractionUser extends User { + member?: Member +} + +export class Interaction extends SnowflakeBase { + /** Type of Interaction */ + type: InteractionType + /** Interaction Token */ + token: string + /** Interaction ID */ + id: string + /** Channel in which Interaction was initiated */ + channel?: TextChannel | GuildTextChannel + /** Guild in which Interaction was initiated */ + guild?: Guild + /** Member object of who initiated the Interaction */ + member?: Member + /** User object of who invoked Interaction */ + user: User + /** Whether we have responded to Interaction or not */ + responded: boolean = false + /** Whether response was deferred or not */ + deferred: boolean = false + _httpRespond?: (d: InteractionResponsePayload) => unknown + _httpResponded?: boolean + applicationID: string + /** Data sent with Interaction. Only applies to Application Command */ + data?: InteractionApplicationCommandData + + constructor( + client: Client, + data: InteractionPayload, + others: { + channel?: TextChannel | GuildTextChannel + guild?: Guild + member?: Member + user: User + } + ) { + super(client) + this.type = data.type + this.token = data.token + this.member = others.member + this.id = data.id + this.applicationID = data.application_id + this.user = others.user + this.data = data.data + this.guild = others.guild + this.channel = others.channel + } + + /** Respond to an Interaction */ + async respond(data: InteractionResponse): Promise { + if (this.responded) throw new Error('Already responded to Interaction') + let flags = 0 + if (data.ephemeral === true) flags |= InteractionResponseFlags.EPHEMERAL + if (data.flags !== undefined) { + if (Array.isArray(data.flags)) + flags = data.flags.reduce((p, a) => p | a, flags) + else if (typeof data.flags === 'number') flags |= data.flags + } + const payload: InteractionResponsePayload = { + type: data.type ?? InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, + data: + data.type === undefined || + data.type === InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE + ? { + content: data.content ?? '', + embeds: data.embeds, + tts: data.tts ?? false, + flags, + allowed_mentions: data.allowedMentions ?? undefined + } + : undefined + } + + if (this._httpRespond !== undefined && this._httpResponded !== true) { + this._httpResponded = true + await this._httpRespond(payload) + } else + await this.client.rest.post( + INTERACTION_CALLBACK(this.id, this.token), + payload + ) + this.responded = true + + return this + } + + /** Defer the Interaction i.e. let the user know bot is processing and will respond later. You only have 15 minutes to edit the response! */ + async defer(ephemeral = false): Promise { + await this.respond({ + type: InteractionResponseType.DEFERRED_CHANNEL_MESSAGE, + flags: ephemeral ? 1 << 6 : 0 + }) + this.deferred = true + return this + } + + /** Reply with a Message to the Interaction */ + async reply(content: string): Promise + async reply(options: InteractionMessageOptions): Promise + async reply( + content: string, + options: InteractionMessageOptions + ): Promise + async reply( + content: string | InteractionMessageOptions, + messageOptions?: InteractionMessageOptions + ): Promise { + let options: InteractionMessageOptions | undefined = + typeof content === 'object' ? content : messageOptions + if ( + typeof content === 'object' && + messageOptions !== undefined && + options !== undefined + ) + Object.assign(options, messageOptions) + if (options === undefined) options = {} + if (typeof content === 'string') Object.assign(options, { content }) + + if (this.deferred && this.responded) { + await this.editResponse({ + content: options.content, + embeds: options.embeds, + flags: options.flags, + allowedMentions: options.allowedMentions + }) + } else + await this.respond( + Object.assign(options, { + type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE + }) + ) + + return this + } + + /** Edit the original Interaction response */ + async editResponse(data: { + content?: string + embeds?: Array + flags?: number | number[] + allowedMentions?: AllowedMentionsPayload + }): Promise { + const url = WEBHOOK_MESSAGE(this.applicationID, this.token, '@original') + await this.client.rest.patch(url, { + content: data.content ?? '', + embeds: data.embeds ?? [], + flags: + typeof data.flags === 'object' + ? data.flags.reduce((p, a) => p | a, 0) + : data.flags, + allowed_mentions: data.allowedMentions + }) + return this + } + + /** Delete the original Interaction Response */ + async deleteResponse(): Promise { + const url = WEBHOOK_MESSAGE(this.applicationID, this.token, '@original') + await this.client.rest.delete(url) + return this + } + + get url(): string { + return `https://discord.com/api/v8/webhooks/${this.applicationID}/${this.token}` + } + + /** Send a followup message */ + async send( + text?: string | AllWebhookMessageOptions, + option?: AllWebhookMessageOptions + ): Promise { + if (typeof text === 'object') { + option = text + text = undefined + } + + if (text === undefined && option === undefined) { + throw new Error('Either text or option is necessary.') + } + + if (option instanceof Embed) + option = { + embeds: [option] + } + + const payload: any = { + content: text, + embeds: + (option as WebhookMessageOptions)?.embed !== undefined + ? [(option as WebhookMessageOptions).embed] + : (option as WebhookMessageOptions)?.embeds !== undefined + ? (option as WebhookMessageOptions).embeds + : undefined, + file: (option as WebhookMessageOptions)?.file, + files: (option as WebhookMessageOptions)?.files, + tts: (option as WebhookMessageOptions)?.tts, + allowed_mentions: (option as WebhookMessageOptions)?.allowedMentions + } + + if ((option as WebhookMessageOptions)?.name !== undefined) { + payload.username = (option as WebhookMessageOptions)?.name + } + + if ((option as WebhookMessageOptions)?.avatar !== undefined) { + payload.avatar = (option as WebhookMessageOptions)?.avatar + } + + if ( + payload.embeds !== undefined && + payload.embeds instanceof Array && + payload.embeds.length > 10 + ) + throw new Error( + `Cannot send more than 10 embeds through Interaction Webhook` + ) + + const resp = await this.client.rest.post(`${this.url}?wait=true`, payload) + + const res = new Message( + this.client, + resp, + (this as unknown) as TextChannel, + (this as unknown) as User + ) + await res.mentions.fromPayload(resp) + return res + } + + /** Edit a Followup message */ + async editMessage( + msg: Message | string, + data: { + content?: string + embeds?: Array + file?: any + allowed_mentions?: { + parse?: string + roles?: string[] + users?: string[] + everyone?: boolean + } + } + ): Promise { + await this.client.rest.patch( + WEBHOOK_MESSAGE( + this.applicationID, + this.token ?? this.client.token, + typeof msg === 'string' ? msg : msg.id + ), + data + ) + return this + } + + /** Delete a follow-up Message */ + async deleteMessage(msg: Message | string): Promise { + await this.client.rest.delete( + WEBHOOK_MESSAGE( + this.applicationID, + this.token ?? this.client.token, + typeof msg === 'string' ? msg : msg.id + ) + ) + return this + } +} diff --git a/src/structures/message.ts b/src/structures/message.ts index 1fd5e4e..5e81b82 100644 --- a/src/structures/message.ts +++ b/src/structures/message.ts @@ -20,7 +20,7 @@ import type { Guild } from './guild.ts' import { MessageReactionsManager } from '../managers/messageReactions.ts' import { MessageSticker } from './messageSticker.ts' import type { Emoji } from './emoji.ts' -import type { InteractionType } from '../types/slash.ts' +import type { InteractionType } from '../types/interactions.ts' import { encodeText } from '../utils/encoding.ts' type AllMessageOptions = MessageOptions | Embed diff --git a/src/structures/slash.ts b/src/structures/slash.ts index c222814..c23f476 100644 --- a/src/structures/slash.ts +++ b/src/structures/slash.ts @@ -1,80 +1,22 @@ import type { Client } from '../client/mod.ts' -import type { - AllowedMentionsPayload, - ChannelTypes, - EmbedPayload, - MessageOptions -} from '../types/channel.ts' -import { INTERACTION_CALLBACK, WEBHOOK_MESSAGE } from '../types/endpoint.ts' +import { InteractionPayload } from '../types/interactions.ts' import { InteractionApplicationCommandData, InteractionApplicationCommandOption, - InteractionChannelPayload, - InteractionPayload, - InteractionResponseFlags, - InteractionResponsePayload, - InteractionResponseType, - InteractionType, SlashCommandOptionType -} from '../types/slash.ts' +} from '../types/slashCommands.ts' import type { Dict } from '../utils/dict.ts' -import { Permissions } from '../utils/permissions.ts' -import { SnowflakeBase } from './base.ts' -import type { Channel } from './channel.ts' -import { Embed } from './embed.ts' import type { Guild } from './guild.ts' import type { GuildTextChannel } from './guildTextChannel.ts' import type { Member } from './member.ts' -import { Message } from './message.ts' import type { Role } from './role.ts' import type { TextChannel } from './textChannel.ts' import { User } from './user.ts' - -interface WebhookMessageOptions extends MessageOptions { - embeds?: Array - name?: string - avatar?: string -} - -type AllWebhookMessageOptions = string | WebhookMessageOptions - -/** Interaction Message related Options */ -export interface InteractionMessageOptions { - content?: string - embeds?: Array - tts?: boolean - flags?: number | InteractionResponseFlags[] - allowedMentions?: AllowedMentionsPayload - /** Whether the Message Response should be Ephemeral (only visible to User) or not */ - ephemeral?: boolean -} - -export interface InteractionResponse extends InteractionMessageOptions { - /** Type of Interaction Response */ - type?: InteractionResponseType -} - -/** Represents a Channel Object for an Option in Slash Command */ -export class InteractionChannel extends SnowflakeBase { - /** Name of the Channel */ - name: string - /** Channel Type */ - type: ChannelTypes - permissions: Permissions - - constructor(client: Client, data: InteractionChannelPayload) { - super(client) - this.id = data.id - this.name = data.name - this.type = data.type - this.permissions = new Permissions(data.permissions) - } - - /** Resolve to actual Channel object if present in Cache */ - async resolve(): Promise { - return this.client.channels.get(this.id) - } -} +import { + InteractionUser, + InteractionChannel, + Interaction +} from './interactions.ts' export interface InteractionApplicationCommandResolved { users: Dict @@ -83,36 +25,11 @@ export interface InteractionApplicationCommandResolved { roles: Dict } -export class InteractionUser extends User { - member?: Member -} - -export class Interaction extends SnowflakeBase { - /** Type of Interaction */ - type: InteractionType - /** Interaction Token */ - token: string - /** Interaction ID */ - id: string +export class SlashCommandInteraction extends Interaction { /** Data sent with Interaction. Only applies to Application Command */ - data?: InteractionApplicationCommandData - /** Channel in which Interaction was initiated */ - channel?: TextChannel | GuildTextChannel - /** Guild in which Interaction was initiated */ - guild?: Guild - /** Member object of who initiated the Interaction */ - member?: Member - /** User object of who invoked Interaction */ - user: User - /** Whether we have responded to Interaction or not */ - responded: boolean = false + data: InteractionApplicationCommandData /** Resolved data for Snowflakes in Slash Command Arguments */ resolved: InteractionApplicationCommandResolved - /** Whether response was deferred or not */ - deferred: boolean = false - _httpRespond?: (d: InteractionResponsePayload) => unknown - _httpResponded?: boolean - applicationID: string constructor( client: Client, @@ -125,26 +42,19 @@ export class Interaction extends SnowflakeBase { resolved: InteractionApplicationCommandResolved } ) { - super(client) - this.type = data.type - this.token = data.token - this.member = others.member - this.id = data.id - this.applicationID = data.application_id - this.user = others.user - this.data = data.data - this.guild = others.guild - this.channel = others.channel + super(client, data, others) + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + this.data = data.data as InteractionApplicationCommandData this.resolved = others.resolved } /** Name of the Command Used (may change with future additions to Interactions!) */ - get name(): string | undefined { - return this.data?.name + get name(): string { + return this.data.name } get options(): InteractionApplicationCommandOption[] { - return this.data?.options ?? [] + return this.data.options ?? [] } /** Get an option by name */ @@ -162,222 +72,4 @@ export class Interaction extends SnowflakeBase { return this.resolved.channels[op.value] as any else return op.value } - - /** Respond to an Interaction */ - async respond(data: InteractionResponse): Promise { - if (this.responded) throw new Error('Already responded to Interaction') - let flags = 0 - if (data.ephemeral === true) flags |= InteractionResponseFlags.EPHEMERAL - if (data.flags !== undefined) { - if (Array.isArray(data.flags)) - flags = data.flags.reduce((p, a) => p | a, flags) - else if (typeof data.flags === 'number') flags |= data.flags - } - const payload: InteractionResponsePayload = { - type: data.type ?? InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: - data.type === undefined || - data.type === InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE - ? { - content: data.content ?? '', - embeds: data.embeds, - tts: data.tts ?? false, - flags, - allowed_mentions: data.allowedMentions ?? undefined - } - : undefined - } - - if (this._httpRespond !== undefined && this._httpResponded !== true) { - this._httpResponded = true - await this._httpRespond(payload) - } else - await this.client.rest.post( - INTERACTION_CALLBACK(this.id, this.token), - payload - ) - this.responded = true - - return this - } - - /** Defer the Interaction i.e. let the user know bot is processing and will respond later. You only have 15 minutes to edit the response! */ - async defer(ephemeral = false): Promise { - await this.respond({ - type: InteractionResponseType.DEFERRED_CHANNEL_MESSAGE, - flags: ephemeral ? 1 << 6 : 0 - }) - this.deferred = true - return this - } - - /** Reply with a Message to the Interaction */ - async reply(content: string): Promise - async reply(options: InteractionMessageOptions): Promise - async reply( - content: string, - options: InteractionMessageOptions - ): Promise - async reply( - content: string | InteractionMessageOptions, - messageOptions?: InteractionMessageOptions - ): Promise { - let options: InteractionMessageOptions | undefined = - typeof content === 'object' ? content : messageOptions - if ( - typeof content === 'object' && - messageOptions !== undefined && - options !== undefined - ) - Object.assign(options, messageOptions) - if (options === undefined) options = {} - if (typeof content === 'string') Object.assign(options, { content }) - - if (this.deferred && this.responded) { - await this.editResponse({ - content: options.content, - embeds: options.embeds, - flags: options.flags, - allowedMentions: options.allowedMentions - }) - } else - await this.respond( - Object.assign(options, { - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE - }) - ) - - return this - } - - /** Edit the original Interaction response */ - async editResponse(data: { - content?: string - embeds?: Array - flags?: number | number[] - allowedMentions?: AllowedMentionsPayload - }): Promise { - const url = WEBHOOK_MESSAGE(this.applicationID, this.token, '@original') - await this.client.rest.patch(url, { - content: data.content ?? '', - embeds: data.embeds ?? [], - flags: - typeof data.flags === 'object' - ? data.flags.reduce((p, a) => p | a, 0) - : data.flags, - allowed_mentions: data.allowedMentions - }) - return this - } - - /** Delete the original Interaction Response */ - async deleteResponse(): Promise { - const url = WEBHOOK_MESSAGE(this.applicationID, this.token, '@original') - await this.client.rest.delete(url) - return this - } - - get url(): string { - return `https://discord.com/api/v8/webhooks/${this.applicationID}/${this.token}` - } - - /** Send a followup message */ - async send( - text?: string | AllWebhookMessageOptions, - option?: AllWebhookMessageOptions - ): Promise { - if (typeof text === 'object') { - option = text - text = undefined - } - - if (text === undefined && option === undefined) { - throw new Error('Either text or option is necessary.') - } - - if (option instanceof Embed) - option = { - embeds: [option] - } - - const payload: any = { - content: text, - embeds: - (option as WebhookMessageOptions)?.embed !== undefined - ? [(option as WebhookMessageOptions).embed] - : (option as WebhookMessageOptions)?.embeds !== undefined - ? (option as WebhookMessageOptions).embeds - : undefined, - file: (option as WebhookMessageOptions)?.file, - files: (option as WebhookMessageOptions)?.files, - tts: (option as WebhookMessageOptions)?.tts, - allowed_mentions: (option as WebhookMessageOptions)?.allowedMentions - } - - if ((option as WebhookMessageOptions)?.name !== undefined) { - payload.username = (option as WebhookMessageOptions)?.name - } - - if ((option as WebhookMessageOptions)?.avatar !== undefined) { - payload.avatar = (option as WebhookMessageOptions)?.avatar - } - - if ( - payload.embeds !== undefined && - payload.embeds instanceof Array && - payload.embeds.length > 10 - ) - throw new Error( - `Cannot send more than 10 embeds through Interaction Webhook` - ) - - const resp = await this.client.rest.post(`${this.url}?wait=true`, payload) - - const res = new Message( - this.client, - resp, - (this as unknown) as TextChannel, - (this as unknown) as User - ) - await res.mentions.fromPayload(resp) - return res - } - - /** Edit a Followup message */ - async editMessage( - msg: Message | string, - data: { - content?: string - embeds?: Array - file?: any - allowed_mentions?: { - parse?: string - roles?: string[] - users?: string[] - everyone?: boolean - } - } - ): Promise { - await this.client.rest.patch( - WEBHOOK_MESSAGE( - this.applicationID, - this.token ?? this.client.token, - typeof msg === 'string' ? msg : msg.id - ), - data - ) - return this - } - - /** Delete a follow-up Message */ - async deleteMessage(msg: Message | string): Promise { - await this.client.rest.delete( - WEBHOOK_MESSAGE( - this.applicationID, - this.token ?? this.client.token, - typeof msg === 'string' ? msg : msg.id - ) - ) - return this - } } diff --git a/src/types/channel.ts b/src/types/channel.ts index 8db4e5e..a07e064 100644 --- a/src/types/channel.ts +++ b/src/types/channel.ts @@ -5,8 +5,9 @@ import type { Role } from '../structures/role.ts' import type { Permissions } from '../utils/permissions.ts' import type { EmojiPayload } from './emoji.ts' import type { MemberPayload } from './guild.ts' -import type { InteractionType } from './slash.ts' +import type { InteractionType } from './interactions.ts' import type { UserPayload } from './user.ts' +import type { MessageComponentPayload } from './messageComponents.ts' export interface ChannelPayload { id: string @@ -210,6 +211,7 @@ export interface MessageOptions { files?: MessageAttachment[] allowedMentions?: AllowedMentionsPayload reply?: Message | MessageReference | string + components?: MessageComponentPayload[] } export interface ChannelMention { @@ -401,6 +403,7 @@ export interface CreateMessagePayload extends EditMessagePayload { message_reference?: MessageReference file?: MessageAttachment files?: MessageAttachment[] + components?: MessageComponentPayload[] } export interface CreateWebhookMessageBasePayload { diff --git a/src/types/gateway.ts b/src/types/gateway.ts index d5c2a64..195ff07 100644 --- a/src/types/gateway.ts +++ b/src/types/gateway.ts @@ -11,7 +11,7 @@ import type { ClientStatus } from './presence.ts' import type { RolePayload } from './role.ts' -import type { SlashCommandPayload } from './slash.ts' +import type { SlashCommandPayload } from './slashCommands.ts' import type { UserPayload } from './user.ts' /** diff --git a/src/types/interactions.ts b/src/types/interactions.ts new file mode 100644 index 0000000..41962c0 --- /dev/null +++ b/src/types/interactions.ts @@ -0,0 +1,70 @@ +import { AllowedMentionsPayload, EmbedPayload } from './channel.ts' +import type { MemberPayload } from './guild.ts' +import type { InteractionApplicationCommandData } from './slashCommands.ts' +import type { UserPayload } from './user.ts' + +export enum InteractionType { + /** Ping sent by the API (HTTP-only) */ + PING = 1, + /** Slash Command Interaction */ + APPLICATION_COMMAND = 2 +} + +export interface InteractionMemberPayload extends MemberPayload { + /** Permissions of the Member who initiated Interaction (Guild-only) */ + permissions: string +} + +export interface InteractionPayload { + /** Type of the Interaction */ + type: InteractionType + /** Token of the Interaction to respond */ + token: string + /** Member object of user who invoked */ + member?: InteractionMemberPayload + /** User who initiated Interaction (only in DMs) */ + user?: UserPayload + /** ID of the Interaction */ + id: string + /** + * Data sent with the interaction. Undefined only when Interaction is not Slash Command.* + */ + data?: InteractionApplicationCommandData + /** ID of the Guild in which Interaction was invoked */ + guild_id?: string + /** ID of the Channel in which Interaction was invoked */ + channel_id?: string + application_id: string +} + +export enum InteractionResponseType { + /** Just ack a ping, Http-only. */ + PONG = 1, + /** Send a channel message as response. */ + CHANNEL_MESSAGE_WITH_SOURCE = 4, + /** Let the user know bot is processing ("thinking") and you can edit the response later */ + DEFERRED_CHANNEL_MESSAGE = 5 +} + +export interface InteractionResponsePayload { + /** Type of the response */ + type: InteractionResponseType + /** Data to be sent with response. Optional for types: Pong, Acknowledge, Ack with Source */ + data?: InteractionResponseDataPayload +} + +export interface InteractionResponseDataPayload { + tts?: boolean + /** Text content of the Response (Message) */ + content: string + /** Upto 10 Embed Objects to send with Response */ + embeds?: EmbedPayload[] + /** Allowed Mentions object */ + allowed_mentions?: AllowedMentionsPayload + flags?: number +} + +export enum InteractionResponseFlags { + /** A Message which is only visible to Interaction User. */ + EPHEMERAL = 1 << 6 +} diff --git a/src/types/messageComponents.ts b/src/types/messageComponents.ts new file mode 100644 index 0000000..c666546 --- /dev/null +++ b/src/types/messageComponents.ts @@ -0,0 +1,20 @@ +export enum MessageComponentType { + ActionRow = 1, + Button = 2 +} + +export enum ButtonStyle { + Primary = 1, + Secondary = 2, + Success = 3, + Destructive = 4, + Link = 5 +} + +export interface MessageComponentPayload { + type: MessageComponentType + components?: MessageComponentPayload[] + label?: string + style?: ButtonStyle + url?: string +} diff --git a/src/types/slash.ts b/src/types/slashCommands.ts similarity index 55% rename from src/types/slash.ts rename to src/types/slashCommands.ts index 2791740..c0447c7 100644 --- a/src/types/slash.ts +++ b/src/types/slashCommands.ts @@ -1,9 +1,5 @@ import type { Dict } from '../utils/dict.ts' -import type { - AllowedMentionsPayload, - ChannelTypes, - EmbedPayload -} from './channel.ts' +import type { ChannelTypes } from './channel.ts' import type { MemberPayload } from './guild.ts' import type { RolePayload } from './role.ts' import type { UserPayload } from './user.ts' @@ -44,40 +40,6 @@ export interface InteractionApplicationCommandData { resolved?: InteractionApplicationCommandResolvedPayload } -export enum InteractionType { - /** Ping sent by the API (HTTP-only) */ - PING = 1, - /** Slash Command Interaction */ - APPLICATION_COMMAND = 2 -} - -export interface InteractionMemberPayload extends MemberPayload { - /** Permissions of the Member who initiated Interaction (Guild-only) */ - permissions: string -} - -export interface InteractionPayload { - /** Type of the Interaction */ - type: InteractionType - /** Token of the Interaction to respond */ - token: string - /** Member object of user who invoked */ - member?: InteractionMemberPayload - /** User who initiated Interaction (only in DMs) */ - user?: UserPayload - /** ID of the Interaction */ - id: string - /** - * Data sent with the interaction. Undefined only when Interaction is not Slash Command.* - */ - data?: InteractionApplicationCommandData - /** ID of the Guild in which Interaction was invoked */ - guild_id?: string - /** ID of the Channel in which Interaction was invoked */ - channel_id?: string - application_id: string -} - export interface SlashCommandChoice { /** (Display) name of the Choice */ name: string @@ -131,35 +93,3 @@ export interface SlashCommandPayload extends SlashCommandPartial { /** Application ID */ application_id: string } - -export enum InteractionResponseType { - /** Just ack a ping, Http-only. */ - PONG = 1, - /** Send a channel message as response. */ - CHANNEL_MESSAGE_WITH_SOURCE = 4, - /** Let the user know bot is processing ("thinking") and you can edit the response later */ - DEFERRED_CHANNEL_MESSAGE = 5 -} - -export interface InteractionResponsePayload { - /** Type of the response */ - type: InteractionResponseType - /** Data to be sent with response. Optional for types: Pong, Acknowledge, Ack with Source */ - data?: InteractionResponseDataPayload -} - -export interface InteractionResponseDataPayload { - tts?: boolean - /** Text content of the Response (Message) */ - content: string - /** Upto 10 Embed Objects to send with Response */ - embeds?: EmbedPayload[] - /** Allowed Mentions object */ - allowed_mentions?: AllowedMentionsPayload - flags?: number -} - -export enum InteractionResponseFlags { - /** A Message which is only visible to Interaction User. */ - EPHEMERAL = 1 << 6 -} From b97ec3c225c2e7df6b6210c99daebd1aa1e483c8 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 23 Apr 2021 11:07:55 +0530 Subject: [PATCH 02/40] message component interactions --- mod.ts | 4 + src/client/client.ts | 30 ++-- src/commands/extension.ts | 18 +-- src/gateway/handlers/interactionCreate.ts | 87 +++++++--- src/gateway/handlers/mod.ts | 8 +- src/interactions/slashClient.ts | 32 ++-- src/managers/_util.ts | 19 +++ src/managers/channels.ts | 10 +- src/rest/error.ts | 2 +- src/structures/interactions.ts | 33 ++-- src/structures/messageComponents.ts | 42 +++++ src/types/channel.ts | 9 +- src/types/interactions.ts | 22 ++- src/types/messageComponents.ts | 15 ++ src/utils/err_fmt.ts | 2 +- src/utils/interactions.ts | 16 ++ test/components.ts | 184 ++++++++++++++++++++++ test/music.ts | 14 +- 18 files changed, 461 insertions(+), 86 deletions(-) create mode 100644 src/managers/_util.ts create mode 100644 src/structures/messageComponents.ts create mode 100644 src/utils/interactions.ts create mode 100644 test/components.ts diff --git a/mod.ts b/mod.ts index d05784b..0a9a01a 100644 --- a/mod.ts +++ b/mod.ts @@ -39,8 +39,11 @@ export { GuildChannelsManager } from './src/managers/guildChannels.ts' export { GuildManager } from './src/managers/guilds.ts' export * from './src/structures/base.ts' export * from './src/structures/slash.ts' +export * from './src/structures/interactions.ts' +export * from './src/structures/messageComponents.ts' export * from './src/types/slashCommands.ts' export * from './src/types/interactions.ts' +export * from './src/types/messageComponents.ts' export { GuildEmojisManager } from './src/managers/guildEmojis.ts' export { MembersManager } from './src/managers/members.ts' export { MessageReactionsManager } from './src/managers/messageReactions.ts' @@ -192,3 +195,4 @@ export { isVoiceChannel, default as getChannelByType } from './src/utils/channel.ts' +export * from './src/utils/interactions.ts' diff --git a/src/client/client.ts b/src/client/client.ts index 457d77b..ea3650d 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -12,7 +12,6 @@ import { EmojisManager } from '../managers/emojis.ts' import { ActivityGame, ClientActivity } from '../types/presence.ts' import type { Extension } from '../commands/extension.ts' import { SlashClient } from '../interactions/slashClient.ts' -import type { Interaction } from '../structures/slash.ts' import { ShardManager } from './shard.ts' import { Application } from '../structures/application.ts' import { Invite } from '../structures/invite.ts' @@ -113,17 +112,6 @@ export class Client extends HarmonyEventEmitter { /** Client's presence. Startup one if set before connecting */ presence: ClientPresence = new ClientPresence() - _decoratedEvents?: { - [name: string]: (...args: any[]) => void - } - - _decoratedSlash?: Array<{ - name: string - guild?: string - parent?: string - group?: string - handler: (interaction: Interaction) => any - }> _id?: string @@ -175,13 +163,13 @@ export class Client extends HarmonyEventEmitter { this.fetchUncachedReactions = true if ( - this._decoratedEvents !== undefined && - Object.keys(this._decoratedEvents).length !== 0 + (this as any)._decoratedEvents !== undefined && + Object.keys((this as any)._decoratedEvents).length !== 0 ) { - Object.entries(this._decoratedEvents).forEach((entry) => { - this.on(entry[0] as keyof ClientEvents, entry[1].bind(this)) + Object.entries((this as any)._decoratedEvents).forEach((entry) => { + this.on(entry[0] as keyof ClientEvents, (entry as any)[1].bind(this)) }) - this._decoratedEvents = undefined + ;(this as any)._decoratedEvents = undefined } this.clientProperties = @@ -422,19 +410,23 @@ export class Client extends HarmonyEventEmitter { } /** Event decorator to create an Event handler from function */ +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function event(name?: keyof ClientEvents) { return function ( client: Client | Extension, prop: keyof ClientEvents | string ) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + const c = client as any const listener = ((client as unknown) as { [name in keyof ClientEvents]: (...args: ClientEvents[name]) => any })[(prop as unknown) as keyof ClientEvents] if (typeof listener !== 'function') throw new Error('@event decorator requires a function') - if (client._decoratedEvents === undefined) client._decoratedEvents = {} + + if (c._decoratedEvents === undefined) c._decoratedEvents = {} const key = name === undefined ? prop : name - client._decoratedEvents[key] = listener + c._decoratedEvents[key] = listener } } diff --git a/src/commands/extension.ts b/src/commands/extension.ts index 80f4bc1..1a29be1 100644 --- a/src/commands/extension.ts +++ b/src/commands/extension.ts @@ -73,27 +73,25 @@ export class Extension { /** Events registered by this Extension */ events: { [name: string]: (...args: any[]) => {} } = {} - _decoratedCommands?: { [name: string]: Command } - _decoratedEvents?: { [name: string]: (...args: any[]) => any } - constructor(client: CommandClient) { this.client = client - if (this._decoratedCommands !== undefined) { - Object.entries(this._decoratedCommands).forEach((entry) => { + const self = this as any + if (self._decoratedCommands !== undefined) { + Object.entries(self._decoratedCommands).forEach((entry: any) => { entry[1].extension = this this.commands.add(entry[1]) }) - this._decoratedCommands = undefined + self._decoratedCommands = undefined } if ( - this._decoratedEvents !== undefined && - Object.keys(this._decoratedEvents).length !== 0 + self._decoratedEvents !== undefined && + Object.keys(self._decoratedEvents).length !== 0 ) { - Object.entries(this._decoratedEvents).forEach((entry) => { + Object.entries(self._decoratedEvents).forEach((entry: any) => { this.listen(entry[0] as keyof ClientEvents, entry[1].bind(this)) }) - this._decoratedEvents = undefined + self._decoratedEvents = undefined } } diff --git a/src/gateway/handlers/interactionCreate.ts b/src/gateway/handlers/interactionCreate.ts index 156441b..f99c7e1 100644 --- a/src/gateway/handlers/interactionCreate.ts +++ b/src/gateway/handlers/interactionCreate.ts @@ -18,6 +18,11 @@ import { Permissions } from '../../utils/permissions.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts' import { User } from '../../structures/user.ts' import { Role } from '../../structures/role.ts' +import { RolePayload } from '../../types/role.ts' +import { InteractionChannelPayload } from '../../types/slashCommands.ts' +import { Message } from '../../structures/message.ts' +import { TextChannel } from '../../structures/textChannel.ts' +import { MessageComponentInteraction } from '../../structures/messageComponents.ts' export const interactionCreate: GatewayEventHandler = async ( gateway: Gateway, @@ -32,13 +37,22 @@ export const interactionCreate: GatewayEventHandler = async ( const guild = d.guild_id === undefined ? undefined - : await gateway.client.guilds.get(d.guild_id) + : (await gateway.client.guilds.get(d.guild_id)) ?? + new Guild(gateway.client, { unavailable: true, id: d.guild_id } as any) if (d.member !== undefined) await guild?.members.set(d.member.user.id, d.member) const member = d.member !== undefined - ? (((await guild?.members.get(d.member.user.id)) as unknown) as Member) + ? (await guild?.members.get(d.member.user.id))! ?? + new Member( + gateway.client, + d.member!, + new User(gateway.client, d.member.user), + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + guild!, + new Permissions(d.member.permissions) + ) : undefined if (d.user !== undefined) await gateway.client.users.set(d.user.id, d.user) const dmUser = @@ -47,9 +61,9 @@ export const interactionCreate: GatewayEventHandler = async ( const user = member !== undefined ? member.user : dmUser if (user === undefined) return - const channel = - (await gateway.client.channels.get(d.channel_id)) ?? - (await gateway.client.channels.fetch(d.channel_id)) + const channel = await gateway.client.channels.get( + d.channel_id + ) const resolved: InteractionApplicationCommandResolved = { users: {}, @@ -58,9 +72,11 @@ export const interactionCreate: GatewayEventHandler = async ( roles: {} } - if (d.data?.resolved !== undefined) { - for (const [id, data] of Object.entries(d.data.resolved.users ?? {})) { - await gateway.client.users.set(id, data) + if ((d.data as any)?.resolved !== undefined) { + for (const [id, data] of Object.entries( + (d.data as any)?.resolved.users ?? {} + )) { + await gateway.client.users.set(id, data as UserPayload) resolved.users[id] = ((await gateway.client.users.get( id )) as unknown) as User @@ -68,43 +84,69 @@ export const interactionCreate: GatewayEventHandler = async ( resolved.users[id].member = resolved.members[id] } - for (const [id, data] of Object.entries(d.data.resolved.members ?? {})) { + for (const [id, data] of Object.entries( + (d.data as any)?.resolved.members ?? {} + )) { const roles = await guild?.roles.array() let permissions = new Permissions(Permissions.DEFAULT) if (roles !== undefined) { const mRoles = roles.filter( - (r) => (data?.roles?.includes(r.id) as boolean) || r.id === guild?.id + (r) => + ((data as any)?.roles?.includes(r.id) as boolean) || + r.id === guild?.id ) permissions = new Permissions(mRoles.map((r) => r.permissions)) } - data.user = (d.data.resolved.users?.[id] as unknown) as UserPayload + ;(data as any).user = ((d.data as any).resolved.users?.[ + id + ] as unknown) as UserPayload resolved.members[id] = new Member( gateway.client, - data, + data as any, resolved.users[id], guild as Guild, permissions ) } - for (const [id, data] of Object.entries(d.data.resolved.roles ?? {})) { + for (const [id, data] of Object.entries( + (d.data as any).resolved.roles ?? {} + )) { if (guild !== undefined) { - await guild.roles.set(id, data) + await guild.roles.set(id, data as RolePayload) resolved.roles[id] = ((await guild.roles.get(id)) as unknown) as Role } else { resolved.roles[id] = new Role( gateway.client, - data, + data as any, (guild as unknown) as Guild ) } } - for (const [id, data] of Object.entries(d.data.resolved.channels ?? {})) { - resolved.channels[id] = new InteractionChannel(gateway.client, data) + for (const [id, data] of Object.entries( + (d.data as any).resolved.channels ?? {} + )) { + resolved.channels[id] = new InteractionChannel( + gateway.client, + data as InteractionChannelPayload + ) } } + let message: Message | undefined + if (d.message !== undefined) { + const channel = (await gateway.client.channels.get( + d.message.channel_id + ))! + message = new Message( + gateway.client, + d.message, + channel, + new User(gateway.client, d.message.author) + ) + } + let interaction if (d.type === InteractionType.APPLICATION_COMMAND) { interaction = new SlashCommandInteraction(gateway.client, d, { @@ -114,12 +156,21 @@ export const interactionCreate: GatewayEventHandler = async ( user, resolved }) + } else if (d.type === InteractionType.MESSAGE_COMPONENT) { + interaction = new MessageComponentInteraction(gateway.client, d, { + member, + guild, + channel, + user, + message + }) } else { interaction = new Interaction(gateway.client, d, { member, guild, channel, - user + user, + message }) } diff --git a/src/gateway/handlers/mod.ts b/src/gateway/handlers/mod.ts index dc649e8..561c898 100644 --- a/src/gateway/handlers/mod.ts +++ b/src/gateway/handlers/mod.ts @@ -69,6 +69,7 @@ import { applicationCommandCreate } from './applicationCommandCreate.ts' import { applicationCommandDelete } from './applicationCommandDelete.ts' import { applicationCommandUpdate } from './applicationCommandUpdate.ts' import type { SlashCommand } from '../../interactions/slashCommand.ts' +import { MessageComponentInteraction } from '../../structures/messageComponents.ts' export const gatewayHandlers: { [eventCode in GatewayEvents]: GatewayEventHandler | undefined @@ -364,7 +365,12 @@ export type ClientEvents = { * An Interaction was created * @param interaction Created interaction object */ - interactionCreate: [interaction: Interaction | SlashCommandInteraction] + interactionCreate: [ + interaction: + | Interaction + | SlashCommandInteraction + | MessageComponentInteraction + ] /** * When debug message was made diff --git a/src/interactions/slashClient.ts b/src/interactions/slashClient.ts index 9efd4c9..e254c16 100644 --- a/src/interactions/slashClient.ts +++ b/src/interactions/slashClient.ts @@ -17,6 +17,7 @@ import { User } from '../structures/user.ts' import { HarmonyEventEmitter } from '../utils/events.ts' import { encodeText, decodeText } from '../utils/encoding.ts' import { SlashCommandsManager } from './slashCommand.ts' +import { MessageComponentInteraction } from '../structures/messageComponents.ts' export type SlashCommandHandlerCallback = (interaction: Interaction) => unknown export interface SlashCommandHandler { @@ -77,8 +78,10 @@ export class SlashClient extends HarmonyEventEmitter { this.enabled = options.enabled ?? true - if (this.client?._decoratedSlash !== undefined) { - this.client._decoratedSlash.forEach((e) => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + const client = this.client as any + if (client?._decoratedSlash !== undefined) { + client._decoratedSlash.forEach((e: any) => { e.handler = e.handler.bind(this.client) this.handlers.push(e) }) @@ -205,7 +208,10 @@ export class SlashClient extends HarmonyEventEmitter { /** Process an incoming Interaction */ private async _process( - interaction: Interaction | SlashCommandInteraction + interaction: + | Interaction + | SlashCommandInteraction + | MessageComponentInteraction ): Promise { if (!this.enabled) return @@ -282,7 +288,7 @@ export class SlashClient extends HarmonyEventEmitter { member: payload.member as any, guild: payload.guild_id as any, channel: payload.channel_id as any, - resolved: ((payload.data + resolved: (((payload.data as any) ?.resolved as unknown) as InteractionApplicationCommandResolved) ?? { users: {}, members: {}, @@ -400,12 +406,14 @@ export class SlashClient extends HarmonyEventEmitter { /** Decorator to create a Slash Command handler */ export function slash(name?: string, guild?: string) { return function (client: Client | SlashClient | SlashModule, prop: string) { - if (client._decoratedSlash === undefined) client._decoratedSlash = [] + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + const c = client as any + if (c._decoratedSlash === undefined) c._decoratedSlash = [] const item = (client as { [name: string]: any })[prop] if (typeof item !== 'function') { throw new Error('@slash decorator requires a function') } else - client._decoratedSlash.push({ + c._decoratedSlash.push({ name: name ?? prop, guild, handler: item @@ -416,12 +424,14 @@ export function slash(name?: string, guild?: string) { /** Decorator to create a Sub-Slash Command handler */ export function subslash(parent: string, name?: string, guild?: string) { return function (client: Client | SlashModule | SlashClient, prop: string) { - if (client._decoratedSlash === undefined) client._decoratedSlash = [] + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + const c = client as any + if (c._decoratedSlash === undefined) c._decoratedSlash = [] const item = (client as { [name: string]: any })[prop] if (typeof item !== 'function') { throw new Error('@subslash decorator requires a function') } else - client._decoratedSlash.push({ + c._decoratedSlash.push({ parent, name: name ?? prop, guild, @@ -438,12 +448,14 @@ export function groupslash( guild?: string ) { return function (client: Client | SlashModule | SlashClient, prop: string) { - if (client._decoratedSlash === undefined) client._decoratedSlash = [] + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + const c = client as any + if (c._decoratedSlash === undefined) c._decoratedSlash = [] const item = (client as { [name: string]: any })[prop] if (typeof item !== 'function') { throw new Error('@groupslash decorator requires a function') } else - client._decoratedSlash.push({ + c._decoratedSlash.push({ group, parent, name: name ?? prop, diff --git a/src/managers/_util.ts b/src/managers/_util.ts new file mode 100644 index 0000000..8b5b670 --- /dev/null +++ b/src/managers/_util.ts @@ -0,0 +1,19 @@ +import { + MessageComponentData, + MessageComponentPayload +} from '../types/messageComponents.ts' + +export function transformComponent( + d: MessageComponentData[] +): MessageComponentPayload[] { + return d.map((e: any) => { + if (e.customID !== undefined) { + e.custom_id = e.customID + delete e.customID + } + if (e.components !== undefined) { + e.components = transformComponent(e.components) + } + return e + }) +} diff --git a/src/managers/channels.ts b/src/managers/channels.ts index 2caca4d..6810ecb 100644 --- a/src/managers/channels.ts +++ b/src/managers/channels.ts @@ -11,6 +11,7 @@ import type { import { CHANNEL } from '../types/endpoint.ts' import getChannelByType from '../utils/channel.ts' import { BaseManager } from './base.ts' +import { transformComponent } from './_util.ts' export type AllMessageOptions = MessageOptions | Embed @@ -100,7 +101,10 @@ export class ChannelsManager extends BaseManager { content: content, embed: option?.embed, file: option?.file, - components: option?.components, + components: + option?.components !== undefined + ? transformComponent(option.components) + : undefined, files: option?.files, tts: option?.tts, allowed_mentions: option?.allowedMentions, @@ -168,6 +172,10 @@ export class ChannelsManager extends BaseManager { embed: option?.embed !== undefined ? option.embed.toJSON() : undefined, // Cannot upload new files with Message // file: option?.file, + components: + option?.components !== undefined + ? transformComponent(option.components) + : undefined, tts: option?.tts, allowed_mentions: option?.allowedMentions }) diff --git a/src/rest/error.ts b/src/rest/error.ts index 3f4d4ed..1063f43 100644 --- a/src/rest/error.ts +++ b/src/rest/error.ts @@ -13,7 +13,7 @@ export class DiscordAPIError extends Error { this.message = typeof error === 'string' ? `${error} ` - : `\n${error.method.toUpperCase()} ${error.url.slice(7)} returned ${ + : `\n${error.method.toUpperCase()} ${error.url} returned ${ error.status }\n(${error.code ?? 'unknown'}) ${error.message}${ fmt.length === 0 diff --git a/src/structures/interactions.ts b/src/structures/interactions.ts index dad5e0c..f848012 100644 --- a/src/structures/interactions.ts +++ b/src/structures/interactions.ts @@ -1,4 +1,5 @@ import type { Client } from '../client/client.ts' +import { transformComponent } from '../managers/_util.ts' import { AllowedMentionsPayload, ChannelTypes, @@ -13,11 +14,14 @@ import { InteractionResponseType, InteractionType } from '../types/interactions.ts' +import { + InteractionMessageComponentData, + MessageComponentData +} from '../types/messageComponents.ts' import { InteractionApplicationCommandData, InteractionChannelPayload } from '../types/slashCommands.ts' -import { Dict } from '../utils/dict.ts' import { Permissions } from '../utils/permissions.ts' import { SnowflakeBase } from './base.ts' import { Channel } from './channel.ts' @@ -26,7 +30,6 @@ import { Guild } from './guild.ts' import { GuildTextChannel } from './guildTextChannel.ts' import { Member } from './member.ts' import { Message } from './message.ts' -import { Role } from './role.ts' import { TextChannel } from './textChannel.ts' import { User } from './user.ts' @@ -47,6 +50,7 @@ export interface InteractionMessageOptions { allowedMentions?: AllowedMentionsPayload /** Whether the Message Response should be Ephemeral (only visible to User) or not */ ephemeral?: boolean + components?: MessageComponentData[] } export interface InteractionResponse extends InteractionMessageOptions { @@ -76,13 +80,6 @@ export class InteractionChannel extends SnowflakeBase { } } -export interface InteractionApplicationCommandResolved { - users: Dict - members: Dict - channels: Dict - roles: Dict -} - export class InteractionUser extends User { member?: Member } @@ -110,7 +107,8 @@ export class Interaction extends SnowflakeBase { _httpResponded?: boolean applicationID: string /** Data sent with Interaction. Only applies to Application Command */ - data?: InteractionApplicationCommandData + data?: InteractionApplicationCommandData | InteractionMessageComponentData + message?: Message constructor( client: Client, @@ -120,6 +118,7 @@ export class Interaction extends SnowflakeBase { guild?: Guild member?: Member user: User + message?: Message } ) { super(client) @@ -132,6 +131,7 @@ export class Interaction extends SnowflakeBase { this.data = data.data this.guild = others.guild this.channel = others.channel + this.message = others.message } /** Respond to an Interaction */ @@ -154,7 +154,11 @@ export class Interaction extends SnowflakeBase { embeds: data.embeds, tts: data.tts ?? false, flags, - allowed_mentions: data.allowedMentions ?? undefined + allowed_mentions: data.allowedMentions ?? undefined, + components: + data.components === undefined + ? undefined + : transformComponent(data.components) } : undefined } @@ -227,6 +231,7 @@ export class Interaction extends SnowflakeBase { embeds?: Array flags?: number | number[] allowedMentions?: AllowedMentionsPayload + components?: MessageComponentData[] }): Promise { const url = WEBHOOK_MESSAGE(this.applicationID, this.token, '@original') await this.client.rest.patch(url, { @@ -236,7 +241,11 @@ export class Interaction extends SnowflakeBase { typeof data.flags === 'object' ? data.flags.reduce((p, a) => p | a, 0) : data.flags, - allowed_mentions: data.allowedMentions + allowed_mentions: data.allowedMentions, + components: + data.components === undefined + ? undefined + : transformComponent(data.components) }) return this } diff --git a/src/structures/messageComponents.ts b/src/structures/messageComponents.ts new file mode 100644 index 0000000..1e47bd3 --- /dev/null +++ b/src/structures/messageComponents.ts @@ -0,0 +1,42 @@ +import { + InteractionMessageComponentData, + MessageComponentType +} from '../types/messageComponents.ts' +import { Interaction } from './interactions.ts' +import type { Client } from '../client/mod.ts' +import { InteractionPayload } from '../types/interactions.ts' +import type { Guild } from './guild.ts' +import type { GuildTextChannel } from './guildTextChannel.ts' +import type { Member } from './member.ts' +import type { TextChannel } from './textChannel.ts' +import { User } from './user.ts' +import { Message } from './message.ts' + +export class MessageComponentInteraction extends Interaction { + data: InteractionMessageComponentData + declare message: Message + + constructor( + client: Client, + data: InteractionPayload, + others: { + channel?: TextChannel | GuildTextChannel + guild?: Guild + member?: Member + user: User + message?: Message + } + ) { + super(client, data, others) + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + this.data = data.data as InteractionMessageComponentData + } + + get customID(): string { + return this.data.custom_id + } + + get componentType(): MessageComponentType { + return this.data.component_type + } +} diff --git a/src/types/channel.ts b/src/types/channel.ts index a07e064..f6542b5 100644 --- a/src/types/channel.ts +++ b/src/types/channel.ts @@ -7,7 +7,10 @@ import type { EmojiPayload } from './emoji.ts' import type { MemberPayload } from './guild.ts' import type { InteractionType } from './interactions.ts' import type { UserPayload } from './user.ts' -import type { MessageComponentPayload } from './messageComponents.ts' +import type { + MessageComponentData, + MessageComponentPayload +} from './messageComponents.ts' export interface ChannelPayload { id: string @@ -189,6 +192,7 @@ export interface MessagePayload { flags?: number stickers?: MessageStickerPayload[] interaction?: MessageInteractionPayload + components?: MessageComponentPayload[] } export enum AllowedMentionType { @@ -211,7 +215,7 @@ export interface MessageOptions { files?: MessageAttachment[] allowedMentions?: AllowedMentionsPayload reply?: Message | MessageReference | string - components?: MessageComponentPayload[] + components?: MessageComponentData[] } export interface ChannelMention { @@ -395,6 +399,7 @@ export interface EditMessagePayload { embed?: EmbedPayload allowed_mentions?: AllowedMentionsPayload flags?: number + components?: MessageComponentPayload[] } export interface CreateMessagePayload extends EditMessagePayload { diff --git a/src/types/interactions.ts b/src/types/interactions.ts index 41962c0..01c12a4 100644 --- a/src/types/interactions.ts +++ b/src/types/interactions.ts @@ -1,5 +1,13 @@ -import { AllowedMentionsPayload, EmbedPayload } from './channel.ts' +import { + AllowedMentionsPayload, + EmbedPayload, + MessagePayload +} from './channel.ts' import type { MemberPayload } from './guild.ts' +import { + InteractionMessageComponentData, + MessageComponentData +} from './messageComponents.ts' import type { InteractionApplicationCommandData } from './slashCommands.ts' import type { UserPayload } from './user.ts' @@ -7,7 +15,9 @@ export enum InteractionType { /** Ping sent by the API (HTTP-only) */ PING = 1, /** Slash Command Interaction */ - APPLICATION_COMMAND = 2 + APPLICATION_COMMAND = 2, + /** Message Component Interaction */ + MESSAGE_COMPONENT = 3 } export interface InteractionMemberPayload extends MemberPayload { @@ -27,14 +37,17 @@ export interface InteractionPayload { /** ID of the Interaction */ id: string /** - * Data sent with the interaction. Undefined only when Interaction is not Slash Command.* + * Data sent with the interaction. Undefined only when Interaction is PING (http-only).* */ - data?: InteractionApplicationCommandData + data?: InteractionApplicationCommandData | InteractionMessageComponentData /** ID of the Guild in which Interaction was invoked */ guild_id?: string /** ID of the Channel in which Interaction was invoked */ channel_id?: string + /** Application ID of the Client who received interaction */ application_id: string + /** Message ID if the Interaction was of type MESSAGE_COMPONENT */ + message?: MessagePayload } export enum InteractionResponseType { @@ -62,6 +75,7 @@ export interface InteractionResponseDataPayload { /** Allowed Mentions object */ allowed_mentions?: AllowedMentionsPayload flags?: number + components?: MessageComponentData[] } export enum InteractionResponseFlags { diff --git a/src/types/messageComponents.ts b/src/types/messageComponents.ts index c666546..22d344f 100644 --- a/src/types/messageComponents.ts +++ b/src/types/messageComponents.ts @@ -17,4 +17,19 @@ export interface MessageComponentPayload { label?: string style?: ButtonStyle url?: string + custom_id?: string +} + +export interface MessageComponentData { + type: MessageComponentType + components?: MessageComponentData[] + label?: string + style?: ButtonStyle + url?: string + customID?: string +} + +export interface InteractionMessageComponentData { + custom_id: string + component_type: MessageComponentType } diff --git a/src/utils/err_fmt.ts b/src/utils/err_fmt.ts index 5bd8672..9bf1609 100644 --- a/src/utils/err_fmt.ts +++ b/src/utils/err_fmt.ts @@ -12,7 +12,7 @@ export function simplifyAPIError(errors: any): SimplifiedError { const arrayIndex = !isNaN(Number(obj[0])) if (arrayIndex) obj[0] = `[${obj[0]}]` if (acum !== '' && !arrayIndex) acum += '.' - fmt(obj[1], (acum += obj[0])) + fmt(obj[1], acum + obj[0]) }) } } diff --git a/src/utils/interactions.ts b/src/utils/interactions.ts new file mode 100644 index 0000000..0849e72 --- /dev/null +++ b/src/utils/interactions.ts @@ -0,0 +1,16 @@ +import { InteractionType } from '../../mod.ts' +import { Interaction } from '../structures/interactions.ts' +import { MessageComponentInteraction } from '../structures/messageComponents.ts' +import { SlashCommandInteraction } from '../structures/slash.ts' + +export function isSlashCommandInteraction( + d: Interaction +): d is SlashCommandInteraction { + return d.type === InteractionType.APPLICATION_COMMAND +} + +export function isMessageComponentInteraction( + d: Interaction +): d is MessageComponentInteraction { + return d.type === InteractionType.MESSAGE_COMPONENT +} diff --git a/test/components.ts b/test/components.ts new file mode 100644 index 0000000..61208f0 --- /dev/null +++ b/test/components.ts @@ -0,0 +1,184 @@ +import { + CommandClient, + Command, + CommandContext, + ButtonStyle, + MessageComponentType, + isMessageComponentInteraction, + MessageComponentInteraction, + Message +} from '../mod.ts' +import { TOKEN } from './config.ts' + +const client = new CommandClient({ + prefix: '.', + spacesAfterPrefix: true +}) + +enum Choice { + Rock, + Paper, + Scissor +} + +const games = new Map< + string, + { user: number; bot: number; msg: Message; txt: string } +>() +const components = [ + { + type: MessageComponentType.ActionRow, + components: [ + { + type: MessageComponentType.Button, + style: ButtonStyle.Primary, + label: 'Rock', + customID: 'rps::Rock' + }, + { + type: MessageComponentType.Button, + style: ButtonStyle.Primary, + label: 'Paper', + customID: 'rps::Paper' + }, + { + type: MessageComponentType.Button, + style: ButtonStyle.Primary, + label: 'Scissor', + customID: 'rps::Scissor' + } + ] + } +] + +client.once('ready', () => { + console.log('Ready!') +}) + +client.commands.add( + class extends Command { + name = 'button' + + execute(ctx: CommandContext): void { + ctx.channel.send('Test Buttons', { + components: [ + { + type: MessageComponentType.ActionRow, + components: [ + { + type: MessageComponentType.Button, + label: 'Primary', + style: ButtonStyle.Primary, + customID: '1' + }, + { + type: MessageComponentType.Button, + label: 'Secondary', + style: ButtonStyle.Secondary, + customID: '2' + }, + { + type: MessageComponentType.Button, + label: 'Destructive', + style: ButtonStyle.Destructive, + customID: '3' + }, + { + type: MessageComponentType.Button, + label: 'Success', + style: ButtonStyle.Success, + customID: '4' + }, + { + type: MessageComponentType.Button, + label: 'Link', + style: ButtonStyle.Link, + url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' + } + ] + } + ] + }) + } + } +) + +client.commands.add( + class extends Command { + name = 'play' + + execute(ctx: CommandContext): any { + if (games.has(ctx.author.id)) + return ctx.message.reply('You are already playing!') + ctx.channel + .send('Game starts now!', { + components + }) + .then((msg) => { + games.set(ctx.author.id, { + user: 0, + bot: 0, + msg, + txt: 'Game starts now!' + }) + }) + } + } +) + +// client.on('raw', (e, d) => { +// if (e === 'INTERACTION_CREATE') console.log(e, d) +// }) + +client.on('interactionCreate', (i) => { + if (isMessageComponentInteraction(i) === true) { + const d = i as MessageComponentInteraction + + if (d.customID.startsWith('rps::') === true) { + const game = games.get(d.user.id) + if (game === undefined) return + const choice = d.customID.split('::')[1] + const c: number = Number(Choice[choice as any]) + const rand = Math.floor(Math.random() * 2) + + game.txt += '\n\n' + game.txt += `You: ${choice}, Bot: ${Choice[rand]}` + let msg + if (rand === c) { + msg = 'Both chose ' + Choice[rand] + '!' + } else if ( + (rand === 0 && c === 2) || + (rand === 1 && c === 0) || + (rand === 2 && c === 1) + ) { + msg = 'Bot got one point!' + game.bot++ + } else { + msg = 'You got one point!' + game.user++ + } + game.txt += '\nInfo: ' + msg + + if (game.bot === 5 || game.user === 5) { + const won = game.bot === 5 ? 'Bot' : 'You' + game.msg.edit( + `${won} won!\n\n**Points:** You: ${game.user} | Bot: ${game.bot}`, + { + components: [] + } + ) + games.delete(d.user.id) + } else { + game.msg.edit( + `${game.txt}\n\n**Points:** You: ${game.user} | Bot: ${game.bot}`, + { + components + } + ) + } + } + } +}) + +console.log('Connecting...') +client.connect(TOKEN, ['GUILDS', 'GUILD_MESSAGES', 'DIRECT_MESSAGES']) diff --git a/test/music.ts b/test/music.ts index ea7a463..c25aa31 100644 --- a/test/music.ts +++ b/test/music.ts @@ -8,12 +8,12 @@ import { CommandContext, Extension, Collection, - GuildTextChannel -} from '../../mod.ts' + GuildTextChannel, + slash, + SlashCommandInteraction +} from '../mod.ts' import { LL_IP, LL_PASS, LL_PORT, TOKEN } from './config.ts' import { Manager, Player } from 'https://deno.land/x/lavadeno/mod.ts' -import { Interaction } from '../structures/slash.ts' -import { slash } from '../client/mod.ts' // import { SlashCommandOptionType } from '../types/slash.ts' export const nodes = [ @@ -58,12 +58,12 @@ class MyClient extends CommandClient { } @subslash('cmd', 'sub-cmd-no-grp') - subCmdNoGroup(d: Interaction): void { + subCmdNoGroup(d: SlashCommandInteraction): void { d.respond({ content: 'sub-cmd-no-group worked' }) } @groupslash('cmd', 'sub-cmd-group', 'sub-cmd') - subCmdGroup(d: Interaction): void { + subCmdGroup(d: SlashCommandInteraction): void { d.respond({ content: 'sub-cmd-group worked' }) } @@ -79,7 +79,7 @@ class MyClient extends CommandClient { } @slash() - run(d: Interaction): void { + run(d: SlashCommandInteraction): void { console.log(d.name) } From 67d81c1bf0bad40329ac3589ce9aa962698e87ab Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sat, 24 Apr 2021 14:33:15 +0530 Subject: [PATCH 03/40] x --- src/interactions/slashModule.ts | 5 ++--- src/types/messageComponents.ts | 8 ++++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/interactions/slashModule.ts b/src/interactions/slashModule.ts index 2ba0fe3..1c61c65 100644 --- a/src/interactions/slashModule.ts +++ b/src/interactions/slashModule.ts @@ -3,11 +3,10 @@ import type { SlashCommandHandler } from './slashClient.ts' export class SlashModule { name: string = '' commands: SlashCommandHandler[] = [] - _decoratedSlash?: SlashCommandHandler[] constructor() { - if (this._decoratedSlash !== undefined) { - this.commands = this._decoratedSlash + if ((this as any)._decoratedSlash !== undefined) { + ;(this as any).commands = (this as any)._decoratedSlash } } diff --git a/src/types/messageComponents.ts b/src/types/messageComponents.ts index 22d344f..45f3534 100644 --- a/src/types/messageComponents.ts +++ b/src/types/messageComponents.ts @@ -11,6 +11,12 @@ export enum ButtonStyle { Link = 5 } +export interface MessageComponentEmoji { + id?: string + name?: string + animated?: boolean +} + export interface MessageComponentPayload { type: MessageComponentType components?: MessageComponentPayload[] @@ -18,6 +24,7 @@ export interface MessageComponentPayload { style?: ButtonStyle url?: string custom_id?: string + emoji?: MessageComponentEmoji } export interface MessageComponentData { @@ -27,6 +34,7 @@ export interface MessageComponentData { style?: ButtonStyle url?: string customID?: string + emoji?: MessageComponentEmoji } export interface InteractionMessageComponentData { From 381cb13e6c5c8d863493796cd7f920b79e29f2a1 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sat, 24 Apr 2021 03:17:06 -0700 Subject: [PATCH 04/40] add disabled to component --- src/types/messageComponents.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/types/messageComponents.ts b/src/types/messageComponents.ts index 45f3534..3a0b21f 100644 --- a/src/types/messageComponents.ts +++ b/src/types/messageComponents.ts @@ -25,6 +25,7 @@ export interface MessageComponentPayload { url?: string custom_id?: string emoji?: MessageComponentEmoji + disabled?: boolean } export interface MessageComponentData { @@ -35,6 +36,7 @@ export interface MessageComponentData { url?: string customID?: string emoji?: MessageComponentEmoji + disabled?: boolean } export interface InteractionMessageComponentData { From 45bfbff3a334a7935d6d61348ee57ce1aed177ca Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sun, 25 Apr 2021 15:17:37 +0530 Subject: [PATCH 05/40] fix deploy --- deploy.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/deploy.ts b/deploy.ts index 7684b41..1c4df2b 100644 --- a/deploy.ts +++ b/deploy.ts @@ -1,10 +1,14 @@ +import { Interaction } from './mod.ts' import { SlashCommandsManager, SlashClient, SlashCommandHandlerCallback, SlashCommandHandler } from './src/interactions/mod.ts' -import { InteractionResponseType, InteractionType } from './src/types/slash.ts' +import { + InteractionResponseType, + InteractionType +} from './src/types/interactions.ts' export interface DeploySlashInitOptions { env?: boolean @@ -124,8 +128,18 @@ export function handle( client.handle(cmd, handler) } +export function interactions(cb: (i: Interaction) => any): void { + client.on('interaction', cb) +} + export { commands, client } -export * from './src/types/slash.ts' +export * from './src/types/slashCommands.ts' +export * from './src/types/interactions.ts' export * from './src/structures/slash.ts' export * from './src/interactions/mod.ts' export * from './src/types/channel.ts' +export * from './src/types/messageComponents.ts' +export * from './src/structures/interactions.ts' +export * from './src/structures/messageComponents.ts' +export * from './src/structures/message.ts' +export * from './src/structures/embed.ts' From a7c442d340165afa8e2c5a2917289d73b5b9517a Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sun, 25 Apr 2021 16:00:06 +0530 Subject: [PATCH 06/40] x --- src/interactions/slashClient.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/interactions/slashClient.ts b/src/interactions/slashClient.ts index 3c95c45..5f5473d 100644 --- a/src/interactions/slashClient.ts +++ b/src/interactions/slashClient.ts @@ -19,7 +19,9 @@ import { encodeText, decodeText } from '../utils/encoding.ts' import { SlashCommandsManager } from './slashCommand.ts' import { MessageComponentInteraction } from '../structures/messageComponents.ts' -export type SlashCommandHandlerCallback = (interaction: Interaction) => unknown +export type SlashCommandHandlerCallback = ( + interaction: SlashCommandInteraction +) => unknown export interface SlashCommandHandler { name: string guild?: string From 3fe3a849288f462fbce2fe09910a0517ada0473f Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sun, 25 Apr 2021 16:04:13 +0530 Subject: [PATCH 07/40] fix --- src/interactions/slashClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interactions/slashClient.ts b/src/interactions/slashClient.ts index 5f5473d..a34c0c8 100644 --- a/src/interactions/slashClient.ts +++ b/src/interactions/slashClient.ts @@ -226,7 +226,7 @@ export class SlashClient extends HarmonyEventEmitter { await this.emit('interaction', interaction) try { - await cmd.handler(interaction) + await cmd.handler(interaction as SlashCommandInteraction) } catch (e) { await this.emit('interactionError', e) } From 332f0f2742e5eb834f3b0e049561dbf40bed15df Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sun, 25 Apr 2021 16:27:34 +0530 Subject: [PATCH 08/40] add support got path --- deploy.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/deploy.ts b/deploy.ts index 1c4df2b..307ee33 100644 --- a/deploy.ts +++ b/deploy.ts @@ -14,6 +14,7 @@ export interface DeploySlashInitOptions { env?: boolean publicKey?: string token?: string + path?: string } /** Current Slash Client being used to handle commands */ @@ -64,6 +65,9 @@ export function init(options: DeploySlashInitOptions): void { respondWith: CallableFunction request: Request }): Promise => { + if (options.path !== undefined) { + if (new URL(evt.request.url).pathname !== options.path) return + } try { // we have to wrap because there are some weird scope errors const d = await client.verifyFetchEvent({ From 1beccb57bae8265759fc0853c6574a0c7fe2f8d0 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sun, 25 Apr 2021 16:28:48 +0530 Subject: [PATCH 09/40] fix --- deploy.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/deploy.ts b/deploy.ts index 307ee33..793ff82 100644 --- a/deploy.ts +++ b/deploy.ts @@ -42,8 +42,12 @@ let commands: SlashCommandsManager * * @param options Initialization options */ -export function init(options: { env: boolean }): void -export function init(options: { publicKey: string; token?: string }): void +export function init(options: { env: boolean; path?: string }): void +export function init(options: { + publicKey: string + token?: string + path?: string +}): void export function init(options: DeploySlashInitOptions): void { if (client !== undefined) throw new Error('Already initialized') if (options.env === true) { From d9925a4ac45449791c36fe2d9362aeceed3a8db7 Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Tue, 27 Apr 2021 23:04:27 +0200 Subject: [PATCH 10/40] add basic argument parsing (Still WIP) --- src/utils/command.ts | 86 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 src/utils/command.ts diff --git a/src/utils/command.ts b/src/utils/command.ts new file mode 100644 index 0000000..30c206e --- /dev/null +++ b/src/utils/command.ts @@ -0,0 +1,86 @@ +export enum MatchTypes { + 'flag', + 'mention', + 'content', + 'rest' +} + +export interface Args { + name: string + match: MatchTypes + type?: unknown // Still needs to be implemented + defaultValue?: unknown // Still needs to be implemented + flag?: string +} + +const mentionRegex = /([0-9]{18})/g + +export function parseArgs2( + commandArgs: Args[], + messageArgs: string[] +): Record { + const messageArgsNullableCopy: Array = [...messageArgs] + const args: Record = {} + for (const entry of commandArgs) { + switch (entry.match) { + case MatchTypes.flag: + parseFlags(args, entry, messageArgsNullableCopy) + break + case MatchTypes.mention: + parseMention(args, entry, messageArgsNullableCopy) + break + case MatchTypes.content: + parseContent(args, entry, messageArgs) + break + case MatchTypes.rest: + parseRest(args, entry, messageArgsNullableCopy) + break + } + } + return args +} + +function parseFlags( + args: Record, + entry: Args, + argsNullable: Array +): void { + for (let i = 0; i < argsNullable.length; i++) { + if (entry.flag === argsNullable[i]) { + argsNullable[i] = null + args[entry.name] = true + break + } else args[entry.name] = entry.defaultValue + } + return +} + +function parseMention( + args: Record, + entry: Args, + argsNullable: Array +): void { + const index = argsNullable.findIndex((x) => typeof x === 'string') + const mention = mentionRegex.exec(argsNullable[index]!)![0] + argsNullable[index] = null + args[entry.name] = mention + return +} + +function parseContent( + args: Record, + entry: Args, + argsNonNullable: Array +): void { + args[entry.name] = + argsNonNullable.length !== 0 ? argsNonNullable : entry.defaultValue + return +} + +function parseRest( + args: Record, + entry: Args, + argsNullable: Array +): void { + args[entry.name] = argsNullable.filter((x) => typeof x === 'string') +} From 7f96dfebc3ba1598838949939b4810bafff685f7 Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Wed, 28 Apr 2021 11:52:05 +0200 Subject: [PATCH 11/40] add args parser into command class (not tested) --- src/commands/client.ts | 3 ++- src/commands/command.ts | 8 ++++---- src/utils/command.ts | 7 ++++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/commands/client.ts b/src/commands/client.ts index 22b0117..9442f4f 100644 --- a/src/commands/client.ts +++ b/src/commands/client.ts @@ -9,6 +9,7 @@ import { CommandsManager, parseCommand } from './command.ts' +import { parseArgs } from '../utils/command.ts' import { Extension, ExtensionsManager } from './extension.ts' type PrefixReturnType = string | string[] | Promise @@ -239,7 +240,7 @@ export class CommandClient extends Client implements CommandClientOptions { client: this, name: parsed.name, prefix, - args: parsed.args, + args: parseArgs(command.args, parsed.args), argString: parsed.argString, message: msg, author: msg.author, diff --git a/src/commands/command.ts b/src/commands/command.ts index 987eaee..8601d70 100644 --- a/src/commands/command.ts +++ b/src/commands/command.ts @@ -6,7 +6,7 @@ import { Collection } from '../utils/collection.ts' import type { CommandClient } from './client.ts' import type { Extension } from './extension.ts' import { join, walk } from '../../deps.ts' - +import type { Args } from '../utils/command.ts' export interface CommandContext { /** The Client object */ client: CommandClient @@ -23,7 +23,7 @@ export interface CommandContext { /** Name of Command which was used */ name: string /** Array of Arguments used with Command */ - args: string[] + args: Record | null /** Complete Raw String of Arguments */ argString: string /** Guild which the command has called */ @@ -46,7 +46,7 @@ export interface CommandOptions { /** Usage Example of Command, only Arguments (without Prefix and Name) */ examples?: string | string[] /** Does the Command take Arguments? Maybe number of required arguments? Or list of arguments? */ - args?: number | boolean | string[] + args?: Args[] /** Permissions(s) required by both User and Bot in order to use Command */ permissions?: string | string[] /** Permission(s) required for using Command */ @@ -81,7 +81,7 @@ export class Command implements CommandOptions { extension?: Extension usage?: string | string[] examples?: string | string[] - args?: number | boolean | string[] + args?: Args[] permissions?: string | string[] userPermissions?: string | string[] botPermissions?: string | string[] diff --git a/src/utils/command.ts b/src/utils/command.ts index 30c206e..f8b34ac 100644 --- a/src/utils/command.ts +++ b/src/utils/command.ts @@ -15,10 +15,11 @@ export interface Args { const mentionRegex = /([0-9]{18})/g -export function parseArgs2( - commandArgs: Args[], +export function parseArgs( + commandArgs: Args[] | undefined, messageArgs: string[] -): Record { +): Record | null { + if (!commandArgs) return null const messageArgsNullableCopy: Array = [...messageArgs] const args: Record = {} for (const entry of commandArgs) { From ff655e0b7d83f45c71110e011789a936c6d2cdc9 Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Wed, 28 Apr 2021 12:43:01 +0200 Subject: [PATCH 12/40] export utils/command.ts --- mod.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/mod.ts b/mod.ts index f2a0842..57f6488 100644 --- a/mod.ts +++ b/mod.ts @@ -191,3 +191,4 @@ export { isVoiceChannel, default as getChannelByType } from './src/utils/channel.ts' +export * from "./src/utils/command.ts" \ No newline at end of file From 8ac716d6a93eae2fe58c7b27c3ba04103146dff3 Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Wed, 28 Apr 2021 12:43:16 +0200 Subject: [PATCH 13/40] change enum to type --- src/utils/command.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/utils/command.ts b/src/utils/command.ts index f8b34ac..7323838 100644 --- a/src/utils/command.ts +++ b/src/utils/command.ts @@ -1,13 +1,8 @@ -export enum MatchTypes { - 'flag', - 'mention', - 'content', - 'rest' -} +export type CommandArgumentMatchTypes = 'flag' | 'mention' | 'content' | 'rest' export interface Args { name: string - match: MatchTypes + match: CommandArgumentMatchTypes type?: unknown // Still needs to be implemented defaultValue?: unknown // Still needs to be implemented flag?: string @@ -24,16 +19,16 @@ export function parseArgs( const args: Record = {} for (const entry of commandArgs) { switch (entry.match) { - case MatchTypes.flag: + case "flag": parseFlags(args, entry, messageArgsNullableCopy) break - case MatchTypes.mention: + case 'mention': parseMention(args, entry, messageArgsNullableCopy) break - case MatchTypes.content: + case 'content': parseContent(args, entry, messageArgs) break - case MatchTypes.rest: + case 'rest': parseRest(args, entry, messageArgsNullableCopy) break } From 2cdb671fb142421c6cc24e5aa312dd05fa3991ba Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Wed, 28 Apr 2021 16:54:22 +0530 Subject: [PATCH 14/40] interactions api design fix --- mod.ts | 2 - src/gateway/handlers/interactionCreate.ts | 9 ----- src/gateway/handlers/mod.ts | 8 +--- src/interactions/slashClient.ts | 6 +-- src/managers/_util.ts | 19 ---------- src/managers/channels.ts | 9 ----- src/structures/interactions.ts | 21 ++--------- src/structures/messageComponents.ts | 42 --------------------- src/types/channel.ts | 8 ---- src/types/interactions.ts | 11 +----- src/types/messageComponents.ts | 45 ----------------------- src/utils/interactions.ts | 7 ---- 12 files changed, 7 insertions(+), 180 deletions(-) delete mode 100644 src/structures/messageComponents.ts delete mode 100644 src/types/messageComponents.ts diff --git a/mod.ts b/mod.ts index 5df0bbb..c7caa24 100644 --- a/mod.ts +++ b/mod.ts @@ -40,10 +40,8 @@ export { GuildManager } from './src/managers/guilds.ts' export * from './src/structures/base.ts' export * from './src/structures/slash.ts' export * from './src/structures/interactions.ts' -export * from './src/structures/messageComponents.ts' export * from './src/types/slashCommands.ts' export * from './src/types/interactions.ts' -export * from './src/types/messageComponents.ts' export { GuildEmojisManager } from './src/managers/guildEmojis.ts' export { MembersManager } from './src/managers/members.ts' export { MessageReactionsManager } from './src/managers/messageReactions.ts' diff --git a/src/gateway/handlers/interactionCreate.ts b/src/gateway/handlers/interactionCreate.ts index f99c7e1..e533a5e 100644 --- a/src/gateway/handlers/interactionCreate.ts +++ b/src/gateway/handlers/interactionCreate.ts @@ -22,7 +22,6 @@ import { RolePayload } from '../../types/role.ts' import { InteractionChannelPayload } from '../../types/slashCommands.ts' import { Message } from '../../structures/message.ts' import { TextChannel } from '../../structures/textChannel.ts' -import { MessageComponentInteraction } from '../../structures/messageComponents.ts' export const interactionCreate: GatewayEventHandler = async ( gateway: Gateway, @@ -156,14 +155,6 @@ export const interactionCreate: GatewayEventHandler = async ( user, resolved }) - } else if (d.type === InteractionType.MESSAGE_COMPONENT) { - interaction = new MessageComponentInteraction(gateway.client, d, { - member, - guild, - channel, - user, - message - }) } else { interaction = new Interaction(gateway.client, d, { member, diff --git a/src/gateway/handlers/mod.ts b/src/gateway/handlers/mod.ts index 561c898..dc649e8 100644 --- a/src/gateway/handlers/mod.ts +++ b/src/gateway/handlers/mod.ts @@ -69,7 +69,6 @@ import { applicationCommandCreate } from './applicationCommandCreate.ts' import { applicationCommandDelete } from './applicationCommandDelete.ts' import { applicationCommandUpdate } from './applicationCommandUpdate.ts' import type { SlashCommand } from '../../interactions/slashCommand.ts' -import { MessageComponentInteraction } from '../../structures/messageComponents.ts' export const gatewayHandlers: { [eventCode in GatewayEvents]: GatewayEventHandler | undefined @@ -365,12 +364,7 @@ export type ClientEvents = { * An Interaction was created * @param interaction Created interaction object */ - interactionCreate: [ - interaction: - | Interaction - | SlashCommandInteraction - | MessageComponentInteraction - ] + interactionCreate: [interaction: Interaction | SlashCommandInteraction] /** * When debug message was made diff --git a/src/interactions/slashClient.ts b/src/interactions/slashClient.ts index a34c0c8..4db556a 100644 --- a/src/interactions/slashClient.ts +++ b/src/interactions/slashClient.ts @@ -17,7 +17,6 @@ import { User } from '../structures/user.ts' import { HarmonyEventEmitter } from '../utils/events.ts' import { encodeText, decodeText } from '../utils/encoding.ts' import { SlashCommandsManager } from './slashCommand.ts' -import { MessageComponentInteraction } from '../structures/messageComponents.ts' export type SlashCommandHandlerCallback = ( interaction: SlashCommandInteraction @@ -203,10 +202,7 @@ export class SlashClient extends HarmonyEventEmitter { /** Process an incoming Interaction */ private async _process( - interaction: - | Interaction - | SlashCommandInteraction - | MessageComponentInteraction + interaction: Interaction | SlashCommandInteraction ): Promise { if (!this.enabled) return diff --git a/src/managers/_util.ts b/src/managers/_util.ts index 8b5b670..e69de29 100644 --- a/src/managers/_util.ts +++ b/src/managers/_util.ts @@ -1,19 +0,0 @@ -import { - MessageComponentData, - MessageComponentPayload -} from '../types/messageComponents.ts' - -export function transformComponent( - d: MessageComponentData[] -): MessageComponentPayload[] { - return d.map((e: any) => { - if (e.customID !== undefined) { - e.custom_id = e.customID - delete e.customID - } - if (e.components !== undefined) { - e.components = transformComponent(e.components) - } - return e - }) -} diff --git a/src/managers/channels.ts b/src/managers/channels.ts index 6810ecb..69efe6c 100644 --- a/src/managers/channels.ts +++ b/src/managers/channels.ts @@ -11,7 +11,6 @@ import type { import { CHANNEL } from '../types/endpoint.ts' import getChannelByType from '../utils/channel.ts' import { BaseManager } from './base.ts' -import { transformComponent } from './_util.ts' export type AllMessageOptions = MessageOptions | Embed @@ -101,10 +100,6 @@ export class ChannelsManager extends BaseManager { content: content, embed: option?.embed, file: option?.file, - components: - option?.components !== undefined - ? transformComponent(option.components) - : undefined, files: option?.files, tts: option?.tts, allowed_mentions: option?.allowedMentions, @@ -172,10 +167,6 @@ export class ChannelsManager extends BaseManager { embed: option?.embed !== undefined ? option.embed.toJSON() : undefined, // Cannot upload new files with Message // file: option?.file, - components: - option?.components !== undefined - ? transformComponent(option.components) - : undefined, tts: option?.tts, allowed_mentions: option?.allowedMentions }) diff --git a/src/structures/interactions.ts b/src/structures/interactions.ts index f848012..86001e9 100644 --- a/src/structures/interactions.ts +++ b/src/structures/interactions.ts @@ -1,5 +1,4 @@ import type { Client } from '../client/client.ts' -import { transformComponent } from '../managers/_util.ts' import { AllowedMentionsPayload, ChannelTypes, @@ -14,10 +13,6 @@ import { InteractionResponseType, InteractionType } from '../types/interactions.ts' -import { - InteractionMessageComponentData, - MessageComponentData -} from '../types/messageComponents.ts' import { InteractionApplicationCommandData, InteractionChannelPayload @@ -50,7 +45,6 @@ export interface InteractionMessageOptions { allowedMentions?: AllowedMentionsPayload /** Whether the Message Response should be Ephemeral (only visible to User) or not */ ephemeral?: boolean - components?: MessageComponentData[] } export interface InteractionResponse extends InteractionMessageOptions { @@ -107,7 +101,7 @@ export class Interaction extends SnowflakeBase { _httpResponded?: boolean applicationID: string /** Data sent with Interaction. Only applies to Application Command */ - data?: InteractionApplicationCommandData | InteractionMessageComponentData + data?: InteractionApplicationCommandData message?: Message constructor( @@ -154,11 +148,7 @@ export class Interaction extends SnowflakeBase { embeds: data.embeds, tts: data.tts ?? false, flags, - allowed_mentions: data.allowedMentions ?? undefined, - components: - data.components === undefined - ? undefined - : transformComponent(data.components) + allowed_mentions: data.allowedMentions ?? undefined } : undefined } @@ -231,7 +221,6 @@ export class Interaction extends SnowflakeBase { embeds?: Array flags?: number | number[] allowedMentions?: AllowedMentionsPayload - components?: MessageComponentData[] }): Promise { const url = WEBHOOK_MESSAGE(this.applicationID, this.token, '@original') await this.client.rest.patch(url, { @@ -241,11 +230,7 @@ export class Interaction extends SnowflakeBase { typeof data.flags === 'object' ? data.flags.reduce((p, a) => p | a, 0) : data.flags, - allowed_mentions: data.allowedMentions, - components: - data.components === undefined - ? undefined - : transformComponent(data.components) + allowed_mentions: data.allowedMentions }) return this } diff --git a/src/structures/messageComponents.ts b/src/structures/messageComponents.ts deleted file mode 100644 index 1e47bd3..0000000 --- a/src/structures/messageComponents.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { - InteractionMessageComponentData, - MessageComponentType -} from '../types/messageComponents.ts' -import { Interaction } from './interactions.ts' -import type { Client } from '../client/mod.ts' -import { InteractionPayload } from '../types/interactions.ts' -import type { Guild } from './guild.ts' -import type { GuildTextChannel } from './guildTextChannel.ts' -import type { Member } from './member.ts' -import type { TextChannel } from './textChannel.ts' -import { User } from './user.ts' -import { Message } from './message.ts' - -export class MessageComponentInteraction extends Interaction { - data: InteractionMessageComponentData - declare message: Message - - constructor( - client: Client, - data: InteractionPayload, - others: { - channel?: TextChannel | GuildTextChannel - guild?: Guild - member?: Member - user: User - message?: Message - } - ) { - super(client, data, others) - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - this.data = data.data as InteractionMessageComponentData - } - - get customID(): string { - return this.data.custom_id - } - - get componentType(): MessageComponentType { - return this.data.component_type - } -} diff --git a/src/types/channel.ts b/src/types/channel.ts index f6542b5..25eb484 100644 --- a/src/types/channel.ts +++ b/src/types/channel.ts @@ -7,10 +7,6 @@ import type { EmojiPayload } from './emoji.ts' import type { MemberPayload } from './guild.ts' import type { InteractionType } from './interactions.ts' import type { UserPayload } from './user.ts' -import type { - MessageComponentData, - MessageComponentPayload -} from './messageComponents.ts' export interface ChannelPayload { id: string @@ -192,7 +188,6 @@ export interface MessagePayload { flags?: number stickers?: MessageStickerPayload[] interaction?: MessageInteractionPayload - components?: MessageComponentPayload[] } export enum AllowedMentionType { @@ -215,7 +210,6 @@ export interface MessageOptions { files?: MessageAttachment[] allowedMentions?: AllowedMentionsPayload reply?: Message | MessageReference | string - components?: MessageComponentData[] } export interface ChannelMention { @@ -399,7 +393,6 @@ export interface EditMessagePayload { embed?: EmbedPayload allowed_mentions?: AllowedMentionsPayload flags?: number - components?: MessageComponentPayload[] } export interface CreateMessagePayload extends EditMessagePayload { @@ -408,7 +401,6 @@ export interface CreateMessagePayload extends EditMessagePayload { message_reference?: MessageReference file?: MessageAttachment files?: MessageAttachment[] - components?: MessageComponentPayload[] } export interface CreateWebhookMessageBasePayload { diff --git a/src/types/interactions.ts b/src/types/interactions.ts index 01c12a4..e4d4d94 100644 --- a/src/types/interactions.ts +++ b/src/types/interactions.ts @@ -4,10 +4,6 @@ import { MessagePayload } from './channel.ts' import type { MemberPayload } from './guild.ts' -import { - InteractionMessageComponentData, - MessageComponentData -} from './messageComponents.ts' import type { InteractionApplicationCommandData } from './slashCommands.ts' import type { UserPayload } from './user.ts' @@ -15,9 +11,7 @@ export enum InteractionType { /** Ping sent by the API (HTTP-only) */ PING = 1, /** Slash Command Interaction */ - APPLICATION_COMMAND = 2, - /** Message Component Interaction */ - MESSAGE_COMPONENT = 3 + APPLICATION_COMMAND = 2 } export interface InteractionMemberPayload extends MemberPayload { @@ -39,7 +33,7 @@ export interface InteractionPayload { /** * Data sent with the interaction. Undefined only when Interaction is PING (http-only).* */ - data?: InteractionApplicationCommandData | InteractionMessageComponentData + data?: InteractionApplicationCommandData /** ID of the Guild in which Interaction was invoked */ guild_id?: string /** ID of the Channel in which Interaction was invoked */ @@ -75,7 +69,6 @@ export interface InteractionResponseDataPayload { /** Allowed Mentions object */ allowed_mentions?: AllowedMentionsPayload flags?: number - components?: MessageComponentData[] } export enum InteractionResponseFlags { diff --git a/src/types/messageComponents.ts b/src/types/messageComponents.ts deleted file mode 100644 index 3a0b21f..0000000 --- a/src/types/messageComponents.ts +++ /dev/null @@ -1,45 +0,0 @@ -export enum MessageComponentType { - ActionRow = 1, - Button = 2 -} - -export enum ButtonStyle { - Primary = 1, - Secondary = 2, - Success = 3, - Destructive = 4, - Link = 5 -} - -export interface MessageComponentEmoji { - id?: string - name?: string - animated?: boolean -} - -export interface MessageComponentPayload { - type: MessageComponentType - components?: MessageComponentPayload[] - label?: string - style?: ButtonStyle - url?: string - custom_id?: string - emoji?: MessageComponentEmoji - disabled?: boolean -} - -export interface MessageComponentData { - type: MessageComponentType - components?: MessageComponentData[] - label?: string - style?: ButtonStyle - url?: string - customID?: string - emoji?: MessageComponentEmoji - disabled?: boolean -} - -export interface InteractionMessageComponentData { - custom_id: string - component_type: MessageComponentType -} diff --git a/src/utils/interactions.ts b/src/utils/interactions.ts index 0849e72..c5d4249 100644 --- a/src/utils/interactions.ts +++ b/src/utils/interactions.ts @@ -1,6 +1,5 @@ import { InteractionType } from '../../mod.ts' import { Interaction } from '../structures/interactions.ts' -import { MessageComponentInteraction } from '../structures/messageComponents.ts' import { SlashCommandInteraction } from '../structures/slash.ts' export function isSlashCommandInteraction( @@ -8,9 +7,3 @@ export function isSlashCommandInteraction( ): d is SlashCommandInteraction { return d.type === InteractionType.APPLICATION_COMMAND } - -export function isMessageComponentInteraction( - d: Interaction -): d is MessageComponentInteraction { - return d.type === InteractionType.MESSAGE_COMPONENT -} From c88e4a67b2b0ce0372438a1473900e516f9be75d Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Wed, 28 Apr 2021 16:57:57 +0530 Subject: [PATCH 15/40] oops wrong file commit --- test/components.ts | 184 --------------------------------------------- 1 file changed, 184 deletions(-) delete mode 100644 test/components.ts diff --git a/test/components.ts b/test/components.ts deleted file mode 100644 index 61208f0..0000000 --- a/test/components.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { - CommandClient, - Command, - CommandContext, - ButtonStyle, - MessageComponentType, - isMessageComponentInteraction, - MessageComponentInteraction, - Message -} from '../mod.ts' -import { TOKEN } from './config.ts' - -const client = new CommandClient({ - prefix: '.', - spacesAfterPrefix: true -}) - -enum Choice { - Rock, - Paper, - Scissor -} - -const games = new Map< - string, - { user: number; bot: number; msg: Message; txt: string } ->() -const components = [ - { - type: MessageComponentType.ActionRow, - components: [ - { - type: MessageComponentType.Button, - style: ButtonStyle.Primary, - label: 'Rock', - customID: 'rps::Rock' - }, - { - type: MessageComponentType.Button, - style: ButtonStyle.Primary, - label: 'Paper', - customID: 'rps::Paper' - }, - { - type: MessageComponentType.Button, - style: ButtonStyle.Primary, - label: 'Scissor', - customID: 'rps::Scissor' - } - ] - } -] - -client.once('ready', () => { - console.log('Ready!') -}) - -client.commands.add( - class extends Command { - name = 'button' - - execute(ctx: CommandContext): void { - ctx.channel.send('Test Buttons', { - components: [ - { - type: MessageComponentType.ActionRow, - components: [ - { - type: MessageComponentType.Button, - label: 'Primary', - style: ButtonStyle.Primary, - customID: '1' - }, - { - type: MessageComponentType.Button, - label: 'Secondary', - style: ButtonStyle.Secondary, - customID: '2' - }, - { - type: MessageComponentType.Button, - label: 'Destructive', - style: ButtonStyle.Destructive, - customID: '3' - }, - { - type: MessageComponentType.Button, - label: 'Success', - style: ButtonStyle.Success, - customID: '4' - }, - { - type: MessageComponentType.Button, - label: 'Link', - style: ButtonStyle.Link, - url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' - } - ] - } - ] - }) - } - } -) - -client.commands.add( - class extends Command { - name = 'play' - - execute(ctx: CommandContext): any { - if (games.has(ctx.author.id)) - return ctx.message.reply('You are already playing!') - ctx.channel - .send('Game starts now!', { - components - }) - .then((msg) => { - games.set(ctx.author.id, { - user: 0, - bot: 0, - msg, - txt: 'Game starts now!' - }) - }) - } - } -) - -// client.on('raw', (e, d) => { -// if (e === 'INTERACTION_CREATE') console.log(e, d) -// }) - -client.on('interactionCreate', (i) => { - if (isMessageComponentInteraction(i) === true) { - const d = i as MessageComponentInteraction - - if (d.customID.startsWith('rps::') === true) { - const game = games.get(d.user.id) - if (game === undefined) return - const choice = d.customID.split('::')[1] - const c: number = Number(Choice[choice as any]) - const rand = Math.floor(Math.random() * 2) - - game.txt += '\n\n' - game.txt += `You: ${choice}, Bot: ${Choice[rand]}` - let msg - if (rand === c) { - msg = 'Both chose ' + Choice[rand] + '!' - } else if ( - (rand === 0 && c === 2) || - (rand === 1 && c === 0) || - (rand === 2 && c === 1) - ) { - msg = 'Bot got one point!' - game.bot++ - } else { - msg = 'You got one point!' - game.user++ - } - game.txt += '\nInfo: ' + msg - - if (game.bot === 5 || game.user === 5) { - const won = game.bot === 5 ? 'Bot' : 'You' - game.msg.edit( - `${won} won!\n\n**Points:** You: ${game.user} | Bot: ${game.bot}`, - { - components: [] - } - ) - games.delete(d.user.id) - } else { - game.msg.edit( - `${game.txt}\n\n**Points:** You: ${game.user} | Bot: ${game.bot}`, - { - components - } - ) - } - } - } -}) - -console.log('Connecting...') -client.connect(TOKEN, ['GUILDS', 'GUILD_MESSAGES', 'DIRECT_MESSAGES']) From e7715b75cf7e72aa643e16a4ba4657a2086e6463 Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Wed, 28 Apr 2021 13:50:13 +0200 Subject: [PATCH 16/40] fix lint? --- src/utils/command.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/utils/command.ts b/src/utils/command.ts index 7323838..e604427 100644 --- a/src/utils/command.ts +++ b/src/utils/command.ts @@ -14,12 +14,12 @@ export function parseArgs( commandArgs: Args[] | undefined, messageArgs: string[] ): Record | null { - if (!commandArgs) return null + if (commandArgs === undefined) return null const messageArgsNullableCopy: Array = [...messageArgs] const args: Record = {} for (const entry of commandArgs) { switch (entry.match) { - case "flag": + case 'flag': parseFlags(args, entry, messageArgsNullableCopy) break case 'mention': @@ -48,7 +48,6 @@ function parseFlags( break } else args[entry.name] = entry.defaultValue } - return } function parseMention( @@ -60,7 +59,6 @@ function parseMention( const mention = mentionRegex.exec(argsNullable[index]!)![0] argsNullable[index] = null args[entry.name] = mention - return } function parseContent( @@ -70,7 +68,6 @@ function parseContent( ): void { args[entry.name] = argsNonNullable.length !== 0 ? argsNonNullable : entry.defaultValue - return } function parseRest( From 58ad6fcd3d59c388c7350d063c9b1ccc6087864a Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Wed, 28 Apr 2021 15:09:39 +0200 Subject: [PATCH 17/40] misc changes --- src/utils/command.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/utils/command.ts b/src/utils/command.ts index e604427..d4b699f 100644 --- a/src/utils/command.ts +++ b/src/utils/command.ts @@ -1,14 +1,15 @@ +import { MessageMentions } from "../structures/messageMentions.ts"; export type CommandArgumentMatchTypes = 'flag' | 'mention' | 'content' | 'rest' export interface Args { name: string match: CommandArgumentMatchTypes - type?: unknown // Still needs to be implemented - defaultValue?: unknown // Still needs to be implemented + // Still needs to be implemented + // type?: unknown + defaultValue?: string; flag?: string } -const mentionRegex = /([0-9]{18})/g export function parseArgs( commandArgs: Args[] | undefined, @@ -56,7 +57,7 @@ function parseMention( argsNullable: Array ): void { const index = argsNullable.findIndex((x) => typeof x === 'string') - const mention = mentionRegex.exec(argsNullable[index]!)![0] + const mention = MessageMentions.USER_MENTION.exec(argsNullable[index]!)![0] argsNullable[index] = null args[entry.name] = mention } From 1c25e2d3a15163375d410cfc4b7408cf602ed70a Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Wed, 28 Apr 2021 15:57:59 +0200 Subject: [PATCH 18/40] add test (fails at the moment) --- test/argsparser_test.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 test/argsparser_test.ts diff --git a/test/argsparser_test.ts b/test/argsparser_test.ts new file mode 100644 index 0000000..96c1a9a --- /dev/null +++ b/test/argsparser_test.ts @@ -0,0 +1,30 @@ +import { parseArgs, Args } from "../src/utils/command.ts"; +import { assertEquals } from "https://deno.land/std@0.95.0/testing/asserts.ts" + +const commandArgs: Args[] = [{ + name: "permaban", + match: "flag", + flag: "--permanent", + }, { + name: "user", + match: "mention", + }, { + name: "reason", + match: "rest", + }]; +const messageArgs: string[] = ["<@!708544768342229012>","--permanent","bye","bye","Skyler"]; +const expectedResult = { + permaban: true, + user: "708544768342229012", + reason: ["bye","bye","Skyler"] +} +Deno.test({ + name: "parse Args", + fn: () => { + const result = parseArgs(commandArgs,messageArgs); + assertEquals(result, expectedResult) + }, + sanitizeOps: true, + sanitizeResources: true, + sanitizeExit: true, +}) \ No newline at end of file From eac9c4bceb6296460bb303f607c81e2ca43f5a74 Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Wed, 28 Apr 2021 16:17:39 +0200 Subject: [PATCH 19/40] fix test --- src/utils/command.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/utils/command.ts b/src/utils/command.ts index d4b699f..6debad4 100644 --- a/src/utils/command.ts +++ b/src/utils/command.ts @@ -1,4 +1,4 @@ -import { MessageMentions } from "../structures/messageMentions.ts"; +import { MessageMentions } from '../structures/messageMentions.ts' export type CommandArgumentMatchTypes = 'flag' | 'mention' | 'content' | 'rest' export interface Args { @@ -6,11 +6,10 @@ export interface Args { match: CommandArgumentMatchTypes // Still needs to be implemented // type?: unknown - defaultValue?: string; + defaultValue?: string flag?: string } - export function parseArgs( commandArgs: Args[] | undefined, messageArgs: string[] @@ -57,9 +56,12 @@ function parseMention( argsNullable: Array ): void { const index = argsNullable.findIndex((x) => typeof x === 'string') - const mention = MessageMentions.USER_MENTION.exec(argsNullable[index]!)![0] + const regexMatches = MessageMentions.USER_MENTION.exec(argsNullable[index]!) + args[entry.name] = + regexMatches !== null + ? regexMatches[0].replace(MessageMentions.USER_MENTION, '$1') + : null argsNullable[index] = null - args[entry.name] = mention } function parseContent( From 97298f17f8470e2cb125e765e7c6e0c0ebfd8334 Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Wed, 28 Apr 2021 16:23:39 +0200 Subject: [PATCH 20/40] add default value --- src/utils/command.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/utils/command.ts b/src/utils/command.ts index 6debad4..3cfa939 100644 --- a/src/utils/command.ts +++ b/src/utils/command.ts @@ -43,10 +43,10 @@ function parseFlags( ): void { for (let i = 0; i < argsNullable.length; i++) { if (entry.flag === argsNullable[i]) { - argsNullable[i] = null args[entry.name] = true break - } else args[entry.name] = entry.defaultValue + } else args[entry.name] = entry.defaultValue ?? false; + argsNullable[i] = null } } @@ -60,7 +60,7 @@ function parseMention( args[entry.name] = regexMatches !== null ? regexMatches[0].replace(MessageMentions.USER_MENTION, '$1') - : null + : entry.defaultValue argsNullable[index] = null } @@ -78,5 +78,6 @@ function parseRest( entry: Args, argsNullable: Array ): void { - args[entry.name] = argsNullable.filter((x) => typeof x === 'string') + const restValues = argsNullable.filter((x) => typeof x === 'string') + args[entry.name] = restValues !== null ? restValues : entry.defaultValue; } From 9c18ec2d1f0ad8c9b3f8f0b4e20d29e87fc92708 Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Wed, 28 Apr 2021 16:56:30 +0200 Subject: [PATCH 21/40] change type to allow for proper defaults --- src/utils/command.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/command.ts b/src/utils/command.ts index 3cfa939..713857e 100644 --- a/src/utils/command.ts +++ b/src/utils/command.ts @@ -6,7 +6,7 @@ export interface Args { match: CommandArgumentMatchTypes // Still needs to be implemented // type?: unknown - defaultValue?: string + defaultValue?: unknown flag?: string } @@ -15,8 +15,10 @@ export function parseArgs( messageArgs: string[] ): Record | null { if (commandArgs === undefined) return null + const messageArgsNullableCopy: Array = [...messageArgs] const args: Record = {} + for (const entry of commandArgs) { switch (entry.match) { case 'flag': From e6c0c378decd7c5996e556d5d827919e3c2923de Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Wed, 28 Apr 2021 20:22:54 +0200 Subject: [PATCH 22/40] added more tests --- test/argsparser_test.ts | 59 +++++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/test/argsparser_test.ts b/test/argsparser_test.ts index 96c1a9a..1d3b07f 100644 --- a/test/argsparser_test.ts +++ b/test/argsparser_test.ts @@ -1,28 +1,71 @@ import { parseArgs, Args } from "../src/utils/command.ts"; -import { assertEquals } from "https://deno.land/std@0.95.0/testing/asserts.ts" +import { assertEquals, assertNotEquals } from "https://deno.land/std@0.95.0/testing/asserts.ts" -const commandArgs: Args[] = [{ +// debugger +const commandArgs: Args[] = [ + { name: "permaban", match: "flag", flag: "--permanent", + defaultValue: true, }, { name: "user", match: "mention", }, { name: "reason", match: "rest", - }]; -const messageArgs: string[] = ["<@!708544768342229012>","--permanent","bye","bye","Skyler"]; -const expectedResult = { + defaultValue: "ree" + } +]; + +const messageArgs1: string[] = ["<@!708544768342229012>","--permanent","bye","bye","Skyler"]; +const expectedResult1 = { permaban: true, user: "708544768342229012", reason: ["bye","bye","Skyler"] } + Deno.test({ - name: "parse Args", + name: "parse command arguments 1 (assertEquals)", fn: () => { - const result = parseArgs(commandArgs,messageArgs); - assertEquals(result, expectedResult) + const result = parseArgs(commandArgs,messageArgs1); + assertEquals(result, expectedResult1) + }, + sanitizeOps: true, + sanitizeResources: true, + sanitizeExit: true, +}) + +const messageArgs2: string[] = ["<@!708544768342229012>","bye","bye","Skyler"]; +const expectedResult2 = { + permaban: true, + user: "708544768342229012", + reason: ["bye","bye","Skyler"] +} + +Deno.test({ + name: "parse command arguments 2 (assertEquals)", + fn: () => { + const result = parseArgs(commandArgs, messageArgs2); + assertEquals(result, expectedResult2) + }, + sanitizeOps: true, + sanitizeResources: true, + sanitizeExit: true, +}) + +const messageArgs3: string[] = ["<@!708544768342229012>","bye","bye","Skyler"]; +const expectedResult3 = { + permaban: false, + user: "708544768342229012", + reason: ["bye","bye","Skyler"] +} + +Deno.test({ + name: "parse command arguments 3 (assertNotEquals)", + fn: () => { + const result = parseArgs(commandArgs, messageArgs3); + assertNotEquals(result, expectedResult3) }, sanitizeOps: true, sanitizeResources: true, From 0a871023c8e545d8f4d011d6216afe7e5dc3bfd3 Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Wed, 28 Apr 2021 20:23:06 +0200 Subject: [PATCH 23/40] fixed 2 bugs related to parsing --- src/utils/command.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/command.ts b/src/utils/command.ts index 713857e..06dcc1e 100644 --- a/src/utils/command.ts +++ b/src/utils/command.ts @@ -45,10 +45,10 @@ function parseFlags( ): void { for (let i = 0; i < argsNullable.length; i++) { if (entry.flag === argsNullable[i]) { + argsNullable[i] = null args[entry.name] = true break } else args[entry.name] = entry.defaultValue ?? false; - argsNullable[i] = null } } @@ -72,7 +72,7 @@ function parseContent( argsNonNullable: Array ): void { args[entry.name] = - argsNonNullable.length !== 0 ? argsNonNullable : entry.defaultValue + argsNonNullable.length > 0 ? argsNonNullable : entry.defaultValue } function parseRest( From dcbf635860b3e1cb91e6558df5353be61ef51d51 Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Wed, 28 Apr 2021 20:28:19 +0200 Subject: [PATCH 24/40] formatting --- test/argsparser_test.ts | 87 +++++++++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 33 deletions(-) diff --git a/test/argsparser_test.ts b/test/argsparser_test.ts index 1d3b07f..c51f749 100644 --- a/test/argsparser_test.ts +++ b/test/argsparser_test.ts @@ -1,73 +1,94 @@ -import { parseArgs, Args } from "../src/utils/command.ts"; -import { assertEquals, assertNotEquals } from "https://deno.land/std@0.95.0/testing/asserts.ts" +import { parseArgs, Args } from '../src/utils/command.ts' +import { + assertEquals, + assertNotEquals +} from 'https://deno.land/std@0.95.0/testing/asserts.ts' // debugger const commandArgs: Args[] = [ { - name: "permaban", - match: "flag", - flag: "--permanent", - defaultValue: true, - }, { - name: "user", - match: "mention", - }, { - name: "reason", - match: "rest", - defaultValue: "ree" + name: 'permaban', + match: 'flag', + flag: '--permanent', + defaultValue: true + }, + { + name: 'user', + match: 'mention' + }, + { + name: 'reason', + match: 'rest', + defaultValue: 'ree' } -]; +] -const messageArgs1: string[] = ["<@!708544768342229012>","--permanent","bye","bye","Skyler"]; +const messageArgs1: string[] = [ + '<@!708544768342229012>', + '--permanent', + 'bye', + 'bye', + 'Skyler' +] const expectedResult1 = { permaban: true, - user: "708544768342229012", - reason: ["bye","bye","Skyler"] + user: '708544768342229012', + reason: ['bye', 'bye', 'Skyler'] } Deno.test({ - name: "parse command arguments 1 (assertEquals)", + name: 'parse command arguments 1 (assertEquals)', fn: () => { - const result = parseArgs(commandArgs,messageArgs1); + const result = parseArgs(commandArgs, messageArgs1) assertEquals(result, expectedResult1) }, sanitizeOps: true, sanitizeResources: true, - sanitizeExit: true, + sanitizeExit: true }) -const messageArgs2: string[] = ["<@!708544768342229012>","bye","bye","Skyler"]; +const messageArgs2: string[] = [ + '<@!708544768342229012>', + 'bye', + 'bye', + 'Skyler' +] const expectedResult2 = { permaban: true, - user: "708544768342229012", - reason: ["bye","bye","Skyler"] + user: '708544768342229012', + reason: ['bye', 'bye', 'Skyler'] } Deno.test({ - name: "parse command arguments 2 (assertEquals)", + name: 'parse command arguments 2 (assertEquals)', fn: () => { - const result = parseArgs(commandArgs, messageArgs2); + const result = parseArgs(commandArgs, messageArgs2) assertEquals(result, expectedResult2) }, sanitizeOps: true, sanitizeResources: true, - sanitizeExit: true, + sanitizeExit: true }) -const messageArgs3: string[] = ["<@!708544768342229012>","bye","bye","Skyler"]; +const messageArgs3: string[] = [ + '<@!708544768342229012>', + 'bye', + 'bye', + 'Skyler' +] const expectedResult3 = { permaban: false, - user: "708544768342229012", - reason: ["bye","bye","Skyler"] + user: '708544768342229012', + reason: ['bye', 'bye', 'Skyler'] } Deno.test({ - name: "parse command arguments 3 (assertNotEquals)", + name: 'parse command arguments 3 (assertNotEquals)', fn: () => { - const result = parseArgs(commandArgs, messageArgs3); + const result = parseArgs(commandArgs, messageArgs3) assertNotEquals(result, expectedResult3) }, sanitizeOps: true, sanitizeResources: true, - sanitizeExit: true, -}) \ No newline at end of file + sanitizeExit: true +}) From bffead6680557b45a764a8f2e16eb4a60acb7691 Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Wed, 28 Apr 2021 20:29:27 +0200 Subject: [PATCH 25/40] fmt --- src/utils/command.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/command.ts b/src/utils/command.ts index 06dcc1e..24ab22c 100644 --- a/src/utils/command.ts +++ b/src/utils/command.ts @@ -15,7 +15,7 @@ export function parseArgs( messageArgs: string[] ): Record | null { if (commandArgs === undefined) return null - + const messageArgsNullableCopy: Array = [...messageArgs] const args: Record = {} @@ -48,7 +48,7 @@ function parseFlags( argsNullable[i] = null args[entry.name] = true break - } else args[entry.name] = entry.defaultValue ?? false; + } else args[entry.name] = entry.defaultValue ?? false } } @@ -81,5 +81,5 @@ function parseRest( argsNullable: Array ): void { const restValues = argsNullable.filter((x) => typeof x === 'string') - args[entry.name] = restValues !== null ? restValues : entry.defaultValue; + args[entry.name] = restValues !== null ? restValues : entry.defaultValue } From 1c9d17263b050653ae25b62488c4b13fd9ae2edf Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Wed, 28 Apr 2021 21:28:09 +0200 Subject: [PATCH 26/40] extend test --- test/argsparser_test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/argsparser_test.ts b/test/argsparser_test.ts index c51f749..d78ce62 100644 --- a/test/argsparser_test.ts +++ b/test/argsparser_test.ts @@ -6,6 +6,10 @@ import { // debugger const commandArgs: Args[] = [ + { + name: 'originalMessage', + match: 'content' + }, { name: 'permaban', match: 'flag', @@ -31,12 +35,20 @@ const messageArgs1: string[] = [ 'Skyler' ] const expectedResult1 = { + originalMessage: [ + '<@!708544768342229012>', + '--permanent', + 'bye', + 'bye', + 'Skyler' + ], permaban: true, user: '708544768342229012', reason: ['bye', 'bye', 'Skyler'] } Deno.test({ + only: false, name: 'parse command arguments 1 (assertEquals)', fn: () => { const result = parseArgs(commandArgs, messageArgs1) @@ -54,6 +66,7 @@ const messageArgs2: string[] = [ 'Skyler' ] const expectedResult2 = { + originalMessage: ['<@!708544768342229012>', 'bye', 'bye', 'Skyler'], permaban: true, user: '708544768342229012', reason: ['bye', 'bye', 'Skyler'] From 9d6feaff8d1375d018bffd176f38407aed91caff Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Wed, 28 Apr 2021 21:28:30 +0200 Subject: [PATCH 27/40] add generic type (I give up on generated types for now) --- src/utils/command.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/utils/command.ts b/src/utils/command.ts index 24ab22c..4e963ee 100644 --- a/src/utils/command.ts +++ b/src/utils/command.ts @@ -1,12 +1,10 @@ import { MessageMentions } from '../structures/messageMentions.ts' export type CommandArgumentMatchTypes = 'flag' | 'mention' | 'content' | 'rest' -export interface Args { +export interface Args { name: string match: CommandArgumentMatchTypes - // Still needs to be implemented - // type?: unknown - defaultValue?: unknown + defaultValue?: T flag?: string } From b3220fa1550e7b9b61479a690c329291c2608fdb Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Thu, 29 Apr 2021 11:05:31 +0200 Subject: [PATCH 28/40] don't assume that the first string found is a mention --- src/utils/command.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/command.ts b/src/utils/command.ts index 4e963ee..11fe653 100644 --- a/src/utils/command.ts +++ b/src/utils/command.ts @@ -55,7 +55,9 @@ function parseMention( entry: Args, argsNullable: Array ): void { - const index = argsNullable.findIndex((x) => typeof x === 'string') + const index = argsNullable.findIndex( + (x) => typeof x === 'string' && x.includes('<@!') + ) const regexMatches = MessageMentions.USER_MENTION.exec(argsNullable[index]!) args[entry.name] = regexMatches !== null From 222f1d0fa8f9ac4f5be6ea1a23d5fbafb971bd29 Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Thu, 29 Apr 2021 12:10:50 +0200 Subject: [PATCH 29/40] parseMention now parses channel & roles mentions --- src/utils/command.ts | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/utils/command.ts b/src/utils/command.ts index 11fe653..6ae5901 100644 --- a/src/utils/command.ts +++ b/src/utils/command.ts @@ -1,5 +1,23 @@ -import { MessageMentions } from '../structures/messageMentions.ts' -export type CommandArgumentMatchTypes = 'flag' | 'mention' | 'content' | 'rest' +interface MentionToRegex { + [key: string]: RegExp + mentionUser: RegExp + mentionRole: RegExp + mentionChannel: RegExp +} + +const mentionToRegex: MentionToRegex = { + mentionUser: /<@!?(\d{17,19})>/g, + mentionRole: /<@&(\d{17,19})>/g, + mentionChannel: /<#(\d{17,19})>/g +} + +export type CommandArgumentMatchTypes = + | 'flag' + | 'mentionUser' + | 'mentionRole' + | 'mentionChannel' + | 'content' + | 'rest' export interface Args { name: string @@ -22,7 +40,9 @@ export function parseArgs( case 'flag': parseFlags(args, entry, messageArgsNullableCopy) break - case 'mention': + case 'mentionUser': + case 'mentionRole': + case 'mentionChannel': parseMention(args, entry, messageArgsNullableCopy) break case 'content': @@ -55,13 +75,14 @@ function parseMention( entry: Args, argsNullable: Array ): void { + const regex = mentionToRegex[entry.match] const index = argsNullable.findIndex( - (x) => typeof x === 'string' && x.includes('<@!') + (x) => typeof x === 'string' && regex.test(x) ) - const regexMatches = MessageMentions.USER_MENTION.exec(argsNullable[index]!) + const regexMatches = regex.exec(argsNullable[index]!) args[entry.name] = regexMatches !== null - ? regexMatches[0].replace(MessageMentions.USER_MENTION, '$1') + ? regexMatches[0].replace(regex, '$1') : entry.defaultValue argsNullable[index] = null } From de4e207d85c32b4246f216ca749df582c0288822 Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Thu, 29 Apr 2021 12:43:03 +0200 Subject: [PATCH 30/40] remove global to fix bug --- src/utils/command.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/command.ts b/src/utils/command.ts index 6ae5901..5b26af5 100644 --- a/src/utils/command.ts +++ b/src/utils/command.ts @@ -6,9 +6,9 @@ interface MentionToRegex { } const mentionToRegex: MentionToRegex = { - mentionUser: /<@!?(\d{17,19})>/g, - mentionRole: /<@&(\d{17,19})>/g, - mentionChannel: /<#(\d{17,19})>/g + mentionUser: /<@!?(\d{17,19})>/, + mentionRole: /<@&(\d{17,19})>/, + mentionChannel: /<#(\d{17,19})>/ } export type CommandArgumentMatchTypes = From 057b3ede539d7824f9b72bb7b0ce7d52f3fca198 Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Thu, 29 Apr 2021 12:43:21 +0200 Subject: [PATCH 31/40] fix test for new behavior --- test/argsparser_test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/argsparser_test.ts b/test/argsparser_test.ts index d78ce62..cc877c5 100644 --- a/test/argsparser_test.ts +++ b/test/argsparser_test.ts @@ -1,10 +1,9 @@ -import { parseArgs, Args } from '../src/utils/command.ts' +import { Args, parseArgs } from '../src/utils/command.ts' import { assertEquals, assertNotEquals } from 'https://deno.land/std@0.95.0/testing/asserts.ts' -// debugger const commandArgs: Args[] = [ { name: 'originalMessage', @@ -18,7 +17,7 @@ const commandArgs: Args[] = [ }, { name: 'user', - match: 'mention' + match: 'mentionUser' }, { name: 'reason', From a8bb3cc49b84e0fbf7ecb29de1274fc5a2587920 Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Thu, 29 Apr 2021 19:34:16 +0200 Subject: [PATCH 32/40] add mention tests --- test/argsparser_test.ts | 49 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/test/argsparser_test.ts b/test/argsparser_test.ts index cc877c5..bd613f9 100644 --- a/test/argsparser_test.ts +++ b/test/argsparser_test.ts @@ -95,7 +95,7 @@ const expectedResult3 = { } Deno.test({ - name: 'parse command arguments 3 (assertNotEquals)', + name: 'parse command arguments default value (assertNotEquals)', fn: () => { const result = parseArgs(commandArgs, messageArgs3) assertNotEquals(result, expectedResult3) @@ -104,3 +104,50 @@ Deno.test({ sanitizeResources: true, sanitizeExit: true }) + + + +const commandArgs2: Args[] = [ + { + name: 'user', + match: 'mentionUser' + }, + { + name: 'channel', + match: 'mentionChannel' + }, + { + name: 'role', + match: 'mentionRole', + }, + { + name: 'reason', + match: 'rest', + defaultValue: 'ree' + } + +] + +const messageArgs4: string[] = [ + '<@!708544768342229012>', + 'bye', + '<#783319033730564098>', + '<@&836715188690092032>' +] +const expectedResult4 = { + channel: "783319033730564098", + role: "836715188690092032", + user: "708544768342229012", + reason: ["bye"] +} + +Deno.test({ + name: 'parse command arguments mentions (assertEquals)', + fn: () => { + const result = parseArgs(commandArgs2, messageArgs4) + assertEquals(result, expectedResult4) + }, + sanitizeOps: true, + sanitizeResources: true, + sanitizeExit: true +}) From 52ec39a24c2630b2292a02ff5a6d0371d399aa19 Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Fri, 30 Apr 2021 08:13:45 +0200 Subject: [PATCH 33/40] `rest` string[] => string --- src/utils/command.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/command.ts b/src/utils/command.ts index 5b26af5..4eda5bc 100644 --- a/src/utils/command.ts +++ b/src/utils/command.ts @@ -102,5 +102,6 @@ function parseRest( argsNullable: Array ): void { const restValues = argsNullable.filter((x) => typeof x === 'string') - args[entry.name] = restValues !== null ? restValues : entry.defaultValue + args[entry.name] = + restValues !== null ? restValues?.join(' ') : entry.defaultValue } From 6e084af4a3c53f86846621de48d57dbbbd5feb6a Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Fri, 30 Apr 2021 08:15:13 +0200 Subject: [PATCH 34/40] add tests aswell --- test/argsparser_test.ts | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/test/argsparser_test.ts b/test/argsparser_test.ts index bd613f9..f428246 100644 --- a/test/argsparser_test.ts +++ b/test/argsparser_test.ts @@ -43,7 +43,7 @@ const expectedResult1 = { ], permaban: true, user: '708544768342229012', - reason: ['bye', 'bye', 'Skyler'] + reason: 'bye bye Skyler' } Deno.test({ @@ -68,7 +68,7 @@ const expectedResult2 = { originalMessage: ['<@!708544768342229012>', 'bye', 'bye', 'Skyler'], permaban: true, user: '708544768342229012', - reason: ['bye', 'bye', 'Skyler'] + reason: 'bye bye Skyler' } Deno.test({ @@ -91,7 +91,7 @@ const messageArgs3: string[] = [ const expectedResult3 = { permaban: false, user: '708544768342229012', - reason: ['bye', 'bye', 'Skyler'] + reason: 'bye bye Skyler' } Deno.test({ @@ -105,10 +105,8 @@ Deno.test({ sanitizeExit: true }) - - const commandArgs2: Args[] = [ - { + { name: 'user', match: 'mentionUser' }, @@ -118,14 +116,13 @@ const commandArgs2: Args[] = [ }, { name: 'role', - match: 'mentionRole', + match: 'mentionRole' }, { name: 'reason', match: 'rest', defaultValue: 'ree' } - ] const messageArgs4: string[] = [ @@ -135,10 +132,10 @@ const messageArgs4: string[] = [ '<@&836715188690092032>' ] const expectedResult4 = { - channel: "783319033730564098", - role: "836715188690092032", - user: "708544768342229012", - reason: ["bye"] + channel: '783319033730564098', + role: '836715188690092032', + user: '708544768342229012', + reason: 'bye' } Deno.test({ From b22563ffc382142655318c17821962b5893fa8b6 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sat, 1 May 2021 10:33:08 +0530 Subject: [PATCH 35/40] requested changes --- src/gateway/handlers/interactionCreate.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/gateway/handlers/interactionCreate.ts b/src/gateway/handlers/interactionCreate.ts index e533a5e..43a80bb 100644 --- a/src/gateway/handlers/interactionCreate.ts +++ b/src/gateway/handlers/interactionCreate.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */ import { Guild } from '../../structures/guild.ts' import { Member } from '../../structures/member.ts' import { @@ -19,7 +20,10 @@ import type { Gateway, GatewayEventHandler } from '../mod.ts' import { User } from '../../structures/user.ts' import { Role } from '../../structures/role.ts' import { RolePayload } from '../../types/role.ts' -import { InteractionChannelPayload } from '../../types/slashCommands.ts' +import { + InteractionApplicationCommandData, + InteractionChannelPayload +} from '../../types/slashCommands.ts' import { Message } from '../../structures/message.ts' import { TextChannel } from '../../structures/textChannel.ts' @@ -71,7 +75,7 @@ export const interactionCreate: GatewayEventHandler = async ( roles: {} } - if ((d.data as any)?.resolved !== undefined) { + if ((d.data as InteractionApplicationCommandData)?.resolved !== undefined) { for (const [id, data] of Object.entries( (d.data as any)?.resolved.users ?? {} )) { @@ -84,7 +88,7 @@ export const interactionCreate: GatewayEventHandler = async ( } for (const [id, data] of Object.entries( - (d.data as any)?.resolved.members ?? {} + (d.data as InteractionApplicationCommandData)?.resolved?.members ?? {} )) { const roles = await guild?.roles.array() let permissions = new Permissions(Permissions.DEFAULT) @@ -109,7 +113,7 @@ export const interactionCreate: GatewayEventHandler = async ( } for (const [id, data] of Object.entries( - (d.data as any).resolved.roles ?? {} + (d.data as InteractionApplicationCommandData).resolved?.roles ?? {} )) { if (guild !== undefined) { await guild.roles.set(id, data as RolePayload) @@ -124,7 +128,7 @@ export const interactionCreate: GatewayEventHandler = async ( } for (const [id, data] of Object.entries( - (d.data as any).resolved.channels ?? {} + (d.data as InteractionApplicationCommandData).resolved?.channels ?? {} )) { resolved.channels[id] = new InteractionChannel( gateway.client, From 53030ea9412ab47ada94d6cc03f7d289e22ad404 Mon Sep 17 00:00:00 2001 From: DjDeveloper <43033058+DjDeveloperr@users.noreply.github.com> Date: Sun, 2 May 2021 11:19:48 +0530 Subject: [PATCH 36/40] chore(readme): update invite --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c987f5c..2b4d26b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

An easy to use Discord API Library for Deno

- + Support

@@ -156,7 +156,7 @@ Documentation is available for `main` (branch) and `stable` (release). ## Found a bug or want support? Join our discord server! -[![Widget for the Discord Server](https://discord.com/api/guilds/783319033205751809/widget.png?style=banner1)](https://discord.gg/harmonyland) +[![Widget for the Discord Server](https://discord.com/api/guilds/783319033205751809/widget.png?style=banner1)](https://discord.gg/harmony) ## Maintainer From c08565cde9b8d80c1820195fbe21f2dda3ec6679 Mon Sep 17 00:00:00 2001 From: lamebox <78473984+lamebox@users.noreply.github.com> Date: Sun, 2 May 2021 10:14:56 -0700 Subject: [PATCH 37/40] Remove messageComponent from exports since that isn't merged --- deploy.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/deploy.ts b/deploy.ts index 793ff82..f70c81d 100644 --- a/deploy.ts +++ b/deploy.ts @@ -146,8 +146,6 @@ export * from './src/types/interactions.ts' export * from './src/structures/slash.ts' export * from './src/interactions/mod.ts' export * from './src/types/channel.ts' -export * from './src/types/messageComponents.ts' export * from './src/structures/interactions.ts' -export * from './src/structures/messageComponents.ts' export * from './src/structures/message.ts' export * from './src/structures/embed.ts' From a5fd1de25ec175b8d3f603104b28e31920b436f8 Mon Sep 17 00:00:00 2001 From: lamebox <78473984+lamebox@users.noreply.github.com> Date: Sun, 2 May 2021 16:05:24 -0700 Subject: [PATCH 38/40] Update deploy.ts --- deploy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy.ts b/deploy.ts index f70c81d..9e5ad9a 100644 --- a/deploy.ts +++ b/deploy.ts @@ -1,4 +1,4 @@ -import { Interaction } from './mod.ts' +import { Interaction } from './src/structures/interactions.ts ' import { SlashCommandsManager, SlashClient, From 5df70e5904bedbf5423390ff06eda88605498d51 Mon Sep 17 00:00:00 2001 From: mierenmanz Date: Mon, 3 May 2021 11:24:09 +0200 Subject: [PATCH 39/40] FIX(command) incorrect value for rest default value --- src/utils/command.ts | 2 +- test/argsparser_test.ts | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/utils/command.ts b/src/utils/command.ts index 4eda5bc..64bd821 100644 --- a/src/utils/command.ts +++ b/src/utils/command.ts @@ -103,5 +103,5 @@ function parseRest( ): void { const restValues = argsNullable.filter((x) => typeof x === 'string') args[entry.name] = - restValues !== null ? restValues?.join(' ') : entry.defaultValue + restValues.length > 0 ? restValues?.join(' ') : entry.defaultValue } diff --git a/test/argsparser_test.ts b/test/argsparser_test.ts index f428246..25777ed 100644 --- a/test/argsparser_test.ts +++ b/test/argsparser_test.ts @@ -148,3 +148,27 @@ Deno.test({ sanitizeResources: true, sanitizeExit: true }) + +const messageArgs5: string[] = ['<@!708544768342229012>'] +const expectedResult5 = { + user: '708544768342229012', + reason: 'No reason provided' +} +const commandArgs5: Args[] = [ + { + name: 'user', + match: 'mentionUser' + }, + { + name: 'reason', + match: 'rest', + defaultValue: 'No reason provided' + } +] +Deno.test({ + name: 'parse command arguments, rest match default', + fn: () => { + const result = parseArgs(commandArgs5, messageArgs5) + assertEquals(result, expectedResult5) + } +}) From d52d6366418897133dab0ca668352ca160cccab9 Mon Sep 17 00:00:00 2001 From: Helloyunho Date: Mon, 3 May 2021 20:54:39 +0900 Subject: [PATCH 40/40] :bug: Fix setting contentType even tho there's no body --- src/rest/request.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rest/request.ts b/src/rest/request.ts index 273eadb..ca26850 100644 --- a/src/rest/request.ts +++ b/src/rest/request.ts @@ -90,7 +90,7 @@ export class APIRequest { ) form.append('payload_json', JSON.stringify(body)) body = form - } else { + } else if (body !== undefined) { contentType = 'application/json' body = JSON.stringify(body) }