message component interactions

This commit is contained in:
DjDeveloperr 2021-04-23 11:07:55 +05:30
parent 17e74dce45
commit b97ec3c225
18 changed files with 461 additions and 86 deletions

4
mod.ts
View File

@ -39,8 +39,11 @@ export { GuildChannelsManager } from './src/managers/guildChannels.ts'
export { GuildManager } from './src/managers/guilds.ts' export { GuildManager } from './src/managers/guilds.ts'
export * from './src/structures/base.ts' export * from './src/structures/base.ts'
export * from './src/structures/slash.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/slashCommands.ts'
export * from './src/types/interactions.ts' export * from './src/types/interactions.ts'
export * from './src/types/messageComponents.ts'
export { GuildEmojisManager } from './src/managers/guildEmojis.ts' export { GuildEmojisManager } from './src/managers/guildEmojis.ts'
export { MembersManager } from './src/managers/members.ts' export { MembersManager } from './src/managers/members.ts'
export { MessageReactionsManager } from './src/managers/messageReactions.ts' export { MessageReactionsManager } from './src/managers/messageReactions.ts'
@ -192,3 +195,4 @@ export {
isVoiceChannel, isVoiceChannel,
default as getChannelByType default as getChannelByType
} from './src/utils/channel.ts' } from './src/utils/channel.ts'
export * from './src/utils/interactions.ts'

View File

@ -12,7 +12,6 @@ import { EmojisManager } from '../managers/emojis.ts'
import { ActivityGame, ClientActivity } from '../types/presence.ts' import { ActivityGame, ClientActivity } from '../types/presence.ts'
import type { Extension } from '../commands/extension.ts' import type { Extension } from '../commands/extension.ts'
import { SlashClient } from '../interactions/slashClient.ts' import { SlashClient } from '../interactions/slashClient.ts'
import type { Interaction } from '../structures/slash.ts'
import { ShardManager } from './shard.ts' import { ShardManager } from './shard.ts'
import { Application } from '../structures/application.ts' import { Application } from '../structures/application.ts'
import { Invite } from '../structures/invite.ts' import { Invite } from '../structures/invite.ts'
@ -113,17 +112,6 @@ export class Client extends HarmonyEventEmitter<ClientEvents> {
/** Client's presence. Startup one if set before connecting */ /** Client's presence. Startup one if set before connecting */
presence: ClientPresence = new ClientPresence() 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 _id?: string
@ -175,13 +163,13 @@ export class Client extends HarmonyEventEmitter<ClientEvents> {
this.fetchUncachedReactions = true this.fetchUncachedReactions = true
if ( if (
this._decoratedEvents !== undefined && (this as any)._decoratedEvents !== undefined &&
Object.keys(this._decoratedEvents).length !== 0 Object.keys((this as any)._decoratedEvents).length !== 0
) { ) {
Object.entries(this._decoratedEvents).forEach((entry) => { Object.entries((this as any)._decoratedEvents).forEach((entry) => {
this.on(entry[0] as keyof ClientEvents, entry[1].bind(this)) this.on(entry[0] as keyof ClientEvents, (entry as any)[1].bind(this))
}) })
this._decoratedEvents = undefined ;(this as any)._decoratedEvents = undefined
} }
this.clientProperties = this.clientProperties =
@ -422,19 +410,23 @@ export class Client extends HarmonyEventEmitter<ClientEvents> {
} }
/** Event decorator to create an Event handler from function */ /** 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) { export function event(name?: keyof ClientEvents) {
return function ( return function (
client: Client | Extension, client: Client | Extension,
prop: keyof ClientEvents | string 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 { const listener = ((client as unknown) as {
[name in keyof ClientEvents]: (...args: ClientEvents[name]) => any [name in keyof ClientEvents]: (...args: ClientEvents[name]) => any
})[(prop as unknown) as keyof ClientEvents] })[(prop as unknown) as keyof ClientEvents]
if (typeof listener !== 'function') if (typeof listener !== 'function')
throw new Error('@event decorator requires a 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 const key = name === undefined ? prop : name
client._decoratedEvents[key] = listener c._decoratedEvents[key] = listener
} }
} }

View File

@ -73,27 +73,25 @@ export class Extension {
/** Events registered by this Extension */ /** Events registered by this Extension */
events: { [name: string]: (...args: any[]) => {} } = {} events: { [name: string]: (...args: any[]) => {} } = {}
_decoratedCommands?: { [name: string]: Command }
_decoratedEvents?: { [name: string]: (...args: any[]) => any }
constructor(client: CommandClient) { constructor(client: CommandClient) {
this.client = client this.client = client
if (this._decoratedCommands !== undefined) { const self = this as any
Object.entries(this._decoratedCommands).forEach((entry) => { if (self._decoratedCommands !== undefined) {
Object.entries(self._decoratedCommands).forEach((entry: any) => {
entry[1].extension = this entry[1].extension = this
this.commands.add(entry[1]) this.commands.add(entry[1])
}) })
this._decoratedCommands = undefined self._decoratedCommands = undefined
} }
if ( if (
this._decoratedEvents !== undefined && self._decoratedEvents !== undefined &&
Object.keys(this._decoratedEvents).length !== 0 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.listen(entry[0] as keyof ClientEvents, entry[1].bind(this))
}) })
this._decoratedEvents = undefined self._decoratedEvents = undefined
} }
} }

View File

@ -18,6 +18,11 @@ import { Permissions } from '../../utils/permissions.ts'
import type { Gateway, GatewayEventHandler } from '../mod.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { User } from '../../structures/user.ts' import { User } from '../../structures/user.ts'
import { Role } from '../../structures/role.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 ( export const interactionCreate: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,
@ -32,13 +37,22 @@ export const interactionCreate: GatewayEventHandler = async (
const guild = const guild =
d.guild_id === undefined d.guild_id === undefined
? 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) if (d.member !== undefined)
await guild?.members.set(d.member.user.id, d.member) await guild?.members.set(d.member.user.id, d.member)
const member = const member =
d.member !== undefined 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 : undefined
if (d.user !== undefined) await gateway.client.users.set(d.user.id, d.user) if (d.user !== undefined) await gateway.client.users.set(d.user.id, d.user)
const dmUser = const dmUser =
@ -47,9 +61,9 @@ export const interactionCreate: GatewayEventHandler = async (
const user = member !== undefined ? member.user : dmUser const user = member !== undefined ? member.user : dmUser
if (user === undefined) return if (user === undefined) return
const channel = const channel = await gateway.client.channels.get<GuildTextBasedChannel>(
(await gateway.client.channels.get<GuildTextBasedChannel>(d.channel_id)) ?? d.channel_id
(await gateway.client.channels.fetch<GuildTextBasedChannel>(d.channel_id)) )
const resolved: InteractionApplicationCommandResolved = { const resolved: InteractionApplicationCommandResolved = {
users: {}, users: {},
@ -58,9 +72,11 @@ export const interactionCreate: GatewayEventHandler = async (
roles: {} roles: {}
} }
if (d.data?.resolved !== undefined) { if ((d.data as any)?.resolved !== undefined) {
for (const [id, data] of Object.entries(d.data.resolved.users ?? {})) { for (const [id, data] of Object.entries(
await gateway.client.users.set(id, data) (d.data as any)?.resolved.users ?? {}
)) {
await gateway.client.users.set(id, data as UserPayload)
resolved.users[id] = ((await gateway.client.users.get( resolved.users[id] = ((await gateway.client.users.get(
id id
)) as unknown) as User )) as unknown) as User
@ -68,43 +84,69 @@ export const interactionCreate: GatewayEventHandler = async (
resolved.users[id].member = resolved.members[id] 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() const roles = await guild?.roles.array()
let permissions = new Permissions(Permissions.DEFAULT) let permissions = new Permissions(Permissions.DEFAULT)
if (roles !== undefined) { if (roles !== undefined) {
const mRoles = roles.filter( 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)) 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( resolved.members[id] = new Member(
gateway.client, gateway.client,
data, data as any,
resolved.users[id], resolved.users[id],
guild as Guild, guild as Guild,
permissions 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) { 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 resolved.roles[id] = ((await guild.roles.get(id)) as unknown) as Role
} else { } else {
resolved.roles[id] = new Role( resolved.roles[id] = new Role(
gateway.client, gateway.client,
data, data as any,
(guild as unknown) as Guild (guild as unknown) as Guild
) )
} }
} }
for (const [id, data] of Object.entries(d.data.resolved.channels ?? {})) { for (const [id, data] of Object.entries(
resolved.channels[id] = new InteractionChannel(gateway.client, data) (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<TextChannel>(
d.message.channel_id
))!
message = new Message(
gateway.client,
d.message,
channel,
new User(gateway.client, d.message.author)
)
}
let interaction let interaction
if (d.type === InteractionType.APPLICATION_COMMAND) { if (d.type === InteractionType.APPLICATION_COMMAND) {
interaction = new SlashCommandInteraction(gateway.client, d, { interaction = new SlashCommandInteraction(gateway.client, d, {
@ -114,12 +156,21 @@ export const interactionCreate: GatewayEventHandler = async (
user, user,
resolved resolved
}) })
} else if (d.type === InteractionType.MESSAGE_COMPONENT) {
interaction = new MessageComponentInteraction(gateway.client, d, {
member,
guild,
channel,
user,
message
})
} else { } else {
interaction = new Interaction(gateway.client, d, { interaction = new Interaction(gateway.client, d, {
member, member,
guild, guild,
channel, channel,
user user,
message
}) })
} }

View File

@ -69,6 +69,7 @@ import { applicationCommandCreate } from './applicationCommandCreate.ts'
import { applicationCommandDelete } from './applicationCommandDelete.ts' import { applicationCommandDelete } from './applicationCommandDelete.ts'
import { applicationCommandUpdate } from './applicationCommandUpdate.ts' import { applicationCommandUpdate } from './applicationCommandUpdate.ts'
import type { SlashCommand } from '../../interactions/slashCommand.ts' import type { SlashCommand } from '../../interactions/slashCommand.ts'
import { MessageComponentInteraction } from '../../structures/messageComponents.ts'
export const gatewayHandlers: { export const gatewayHandlers: {
[eventCode in GatewayEvents]: GatewayEventHandler | undefined [eventCode in GatewayEvents]: GatewayEventHandler | undefined
@ -364,7 +365,12 @@ export type ClientEvents = {
* An Interaction was created * An Interaction was created
* @param interaction Created interaction object * @param interaction Created interaction object
*/ */
interactionCreate: [interaction: Interaction | SlashCommandInteraction] interactionCreate: [
interaction:
| Interaction
| SlashCommandInteraction
| MessageComponentInteraction
]
/** /**
* When debug message was made * When debug message was made

View File

@ -17,6 +17,7 @@ import { User } from '../structures/user.ts'
import { HarmonyEventEmitter } from '../utils/events.ts' import { HarmonyEventEmitter } from '../utils/events.ts'
import { encodeText, decodeText } from '../utils/encoding.ts' import { encodeText, decodeText } from '../utils/encoding.ts'
import { SlashCommandsManager } from './slashCommand.ts' import { SlashCommandsManager } from './slashCommand.ts'
import { MessageComponentInteraction } from '../structures/messageComponents.ts'
export type SlashCommandHandlerCallback = (interaction: Interaction) => unknown export type SlashCommandHandlerCallback = (interaction: Interaction) => unknown
export interface SlashCommandHandler { export interface SlashCommandHandler {
@ -77,8 +78,10 @@ export class SlashClient extends HarmonyEventEmitter<SlashClientEvents> {
this.enabled = options.enabled ?? true this.enabled = options.enabled ?? true
if (this.client?._decoratedSlash !== undefined) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
this.client._decoratedSlash.forEach((e) => { const client = this.client as any
if (client?._decoratedSlash !== undefined) {
client._decoratedSlash.forEach((e: any) => {
e.handler = e.handler.bind(this.client) e.handler = e.handler.bind(this.client)
this.handlers.push(e) this.handlers.push(e)
}) })
@ -205,7 +208,10 @@ export class SlashClient extends HarmonyEventEmitter<SlashClientEvents> {
/** Process an incoming Interaction */ /** Process an incoming Interaction */
private async _process( private async _process(
interaction: Interaction | SlashCommandInteraction interaction:
| Interaction
| SlashCommandInteraction
| MessageComponentInteraction
): Promise<void> { ): Promise<void> {
if (!this.enabled) return if (!this.enabled) return
@ -282,7 +288,7 @@ export class SlashClient extends HarmonyEventEmitter<SlashClientEvents> {
member: payload.member as any, member: payload.member as any,
guild: payload.guild_id as any, guild: payload.guild_id as any,
channel: payload.channel_id as any, channel: payload.channel_id as any,
resolved: ((payload.data resolved: (((payload.data as any)
?.resolved as unknown) as InteractionApplicationCommandResolved) ?? { ?.resolved as unknown) as InteractionApplicationCommandResolved) ?? {
users: {}, users: {},
members: {}, members: {},
@ -400,12 +406,14 @@ export class SlashClient extends HarmonyEventEmitter<SlashClientEvents> {
/** Decorator to create a Slash Command handler */ /** Decorator to create a Slash Command handler */
export function slash(name?: string, guild?: string) { export function slash(name?: string, guild?: string) {
return function (client: Client | SlashClient | SlashModule, prop: 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] const item = (client as { [name: string]: any })[prop]
if (typeof item !== 'function') { if (typeof item !== 'function') {
throw new Error('@slash decorator requires a function') throw new Error('@slash decorator requires a function')
} else } else
client._decoratedSlash.push({ c._decoratedSlash.push({
name: name ?? prop, name: name ?? prop,
guild, guild,
handler: item handler: item
@ -416,12 +424,14 @@ export function slash(name?: string, guild?: string) {
/** Decorator to create a Sub-Slash Command handler */ /** Decorator to create a Sub-Slash Command handler */
export function subslash(parent: string, name?: string, guild?: string) { export function subslash(parent: string, name?: string, guild?: string) {
return function (client: Client | SlashModule | SlashClient, prop: 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] const item = (client as { [name: string]: any })[prop]
if (typeof item !== 'function') { if (typeof item !== 'function') {
throw new Error('@subslash decorator requires a function') throw new Error('@subslash decorator requires a function')
} else } else
client._decoratedSlash.push({ c._decoratedSlash.push({
parent, parent,
name: name ?? prop, name: name ?? prop,
guild, guild,
@ -438,12 +448,14 @@ export function groupslash(
guild?: string guild?: string
) { ) {
return function (client: Client | SlashModule | SlashClient, prop: 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] const item = (client as { [name: string]: any })[prop]
if (typeof item !== 'function') { if (typeof item !== 'function') {
throw new Error('@groupslash decorator requires a function') throw new Error('@groupslash decorator requires a function')
} else } else
client._decoratedSlash.push({ c._decoratedSlash.push({
group, group,
parent, parent,
name: name ?? prop, name: name ?? prop,

19
src/managers/_util.ts Normal file
View File

@ -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
})
}

View File

@ -11,6 +11,7 @@ import type {
import { CHANNEL } from '../types/endpoint.ts' import { CHANNEL } from '../types/endpoint.ts'
import getChannelByType from '../utils/channel.ts' import getChannelByType from '../utils/channel.ts'
import { BaseManager } from './base.ts' import { BaseManager } from './base.ts'
import { transformComponent } from './_util.ts'
export type AllMessageOptions = MessageOptions | Embed export type AllMessageOptions = MessageOptions | Embed
@ -100,7 +101,10 @@ export class ChannelsManager extends BaseManager<ChannelPayload, Channel> {
content: content, content: content,
embed: option?.embed, embed: option?.embed,
file: option?.file, file: option?.file,
components: option?.components, components:
option?.components !== undefined
? transformComponent(option.components)
: undefined,
files: option?.files, files: option?.files,
tts: option?.tts, tts: option?.tts,
allowed_mentions: option?.allowedMentions, allowed_mentions: option?.allowedMentions,
@ -168,6 +172,10 @@ export class ChannelsManager extends BaseManager<ChannelPayload, Channel> {
embed: option?.embed !== undefined ? option.embed.toJSON() : undefined, embed: option?.embed !== undefined ? option.embed.toJSON() : undefined,
// Cannot upload new files with Message // Cannot upload new files with Message
// file: option?.file, // file: option?.file,
components:
option?.components !== undefined
? transformComponent(option.components)
: undefined,
tts: option?.tts, tts: option?.tts,
allowed_mentions: option?.allowedMentions allowed_mentions: option?.allowedMentions
}) })

View File

@ -13,7 +13,7 @@ export class DiscordAPIError extends Error {
this.message = this.message =
typeof error === 'string' typeof error === 'string'
? `${error} ` ? `${error} `
: `\n${error.method.toUpperCase()} ${error.url.slice(7)} returned ${ : `\n${error.method.toUpperCase()} ${error.url} returned ${
error.status error.status
}\n(${error.code ?? 'unknown'}) ${error.message}${ }\n(${error.code ?? 'unknown'}) ${error.message}${
fmt.length === 0 fmt.length === 0

View File

@ -1,4 +1,5 @@
import type { Client } from '../client/client.ts' import type { Client } from '../client/client.ts'
import { transformComponent } from '../managers/_util.ts'
import { import {
AllowedMentionsPayload, AllowedMentionsPayload,
ChannelTypes, ChannelTypes,
@ -13,11 +14,14 @@ import {
InteractionResponseType, InteractionResponseType,
InteractionType InteractionType
} from '../types/interactions.ts' } from '../types/interactions.ts'
import {
InteractionMessageComponentData,
MessageComponentData
} from '../types/messageComponents.ts'
import { import {
InteractionApplicationCommandData, InteractionApplicationCommandData,
InteractionChannelPayload InteractionChannelPayload
} from '../types/slashCommands.ts' } from '../types/slashCommands.ts'
import { Dict } from '../utils/dict.ts'
import { Permissions } from '../utils/permissions.ts' import { Permissions } from '../utils/permissions.ts'
import { SnowflakeBase } from './base.ts' import { SnowflakeBase } from './base.ts'
import { Channel } from './channel.ts' import { Channel } from './channel.ts'
@ -26,7 +30,6 @@ import { Guild } from './guild.ts'
import { GuildTextChannel } from './guildTextChannel.ts' import { GuildTextChannel } from './guildTextChannel.ts'
import { Member } from './member.ts' import { Member } from './member.ts'
import { Message } from './message.ts' import { Message } from './message.ts'
import { Role } from './role.ts'
import { TextChannel } from './textChannel.ts' import { TextChannel } from './textChannel.ts'
import { User } from './user.ts' import { User } from './user.ts'
@ -47,6 +50,7 @@ export interface InteractionMessageOptions {
allowedMentions?: AllowedMentionsPayload allowedMentions?: AllowedMentionsPayload
/** Whether the Message Response should be Ephemeral (only visible to User) or not */ /** Whether the Message Response should be Ephemeral (only visible to User) or not */
ephemeral?: boolean ephemeral?: boolean
components?: MessageComponentData[]
} }
export interface InteractionResponse extends InteractionMessageOptions { export interface InteractionResponse extends InteractionMessageOptions {
@ -76,13 +80,6 @@ export class InteractionChannel extends SnowflakeBase {
} }
} }
export interface InteractionApplicationCommandResolved {
users: Dict<InteractionUser>
members: Dict<Member>
channels: Dict<InteractionChannel>
roles: Dict<Role>
}
export class InteractionUser extends User { export class InteractionUser extends User {
member?: Member member?: Member
} }
@ -110,7 +107,8 @@ export class Interaction extends SnowflakeBase {
_httpResponded?: boolean _httpResponded?: boolean
applicationID: string applicationID: string
/** Data sent with Interaction. Only applies to Application Command */ /** Data sent with Interaction. Only applies to Application Command */
data?: InteractionApplicationCommandData data?: InteractionApplicationCommandData | InteractionMessageComponentData
message?: Message
constructor( constructor(
client: Client, client: Client,
@ -120,6 +118,7 @@ export class Interaction extends SnowflakeBase {
guild?: Guild guild?: Guild
member?: Member member?: Member
user: User user: User
message?: Message
} }
) { ) {
super(client) super(client)
@ -132,6 +131,7 @@ export class Interaction extends SnowflakeBase {
this.data = data.data this.data = data.data
this.guild = others.guild this.guild = others.guild
this.channel = others.channel this.channel = others.channel
this.message = others.message
} }
/** Respond to an Interaction */ /** Respond to an Interaction */
@ -154,7 +154,11 @@ export class Interaction extends SnowflakeBase {
embeds: data.embeds, embeds: data.embeds,
tts: data.tts ?? false, tts: data.tts ?? false,
flags, flags,
allowed_mentions: data.allowedMentions ?? undefined allowed_mentions: data.allowedMentions ?? undefined,
components:
data.components === undefined
? undefined
: transformComponent(data.components)
} }
: undefined : undefined
} }
@ -227,6 +231,7 @@ export class Interaction extends SnowflakeBase {
embeds?: Array<Embed | EmbedPayload> embeds?: Array<Embed | EmbedPayload>
flags?: number | number[] flags?: number | number[]
allowedMentions?: AllowedMentionsPayload allowedMentions?: AllowedMentionsPayload
components?: MessageComponentData[]
}): Promise<Interaction> { }): Promise<Interaction> {
const url = WEBHOOK_MESSAGE(this.applicationID, this.token, '@original') const url = WEBHOOK_MESSAGE(this.applicationID, this.token, '@original')
await this.client.rest.patch(url, { await this.client.rest.patch(url, {
@ -236,7 +241,11 @@ export class Interaction extends SnowflakeBase {
typeof data.flags === 'object' typeof data.flags === 'object'
? data.flags.reduce((p, a) => p | a, 0) ? data.flags.reduce((p, a) => p | a, 0)
: data.flags, : data.flags,
allowed_mentions: data.allowedMentions allowed_mentions: data.allowedMentions,
components:
data.components === undefined
? undefined
: transformComponent(data.components)
}) })
return this return this
} }

View File

@ -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
}
}

View File

@ -7,7 +7,10 @@ import type { EmojiPayload } from './emoji.ts'
import type { MemberPayload } from './guild.ts' import type { MemberPayload } from './guild.ts'
import type { InteractionType } from './interactions.ts' import type { InteractionType } from './interactions.ts'
import type { UserPayload } from './user.ts' import type { UserPayload } from './user.ts'
import type { MessageComponentPayload } from './messageComponents.ts' import type {
MessageComponentData,
MessageComponentPayload
} from './messageComponents.ts'
export interface ChannelPayload { export interface ChannelPayload {
id: string id: string
@ -189,6 +192,7 @@ export interface MessagePayload {
flags?: number flags?: number
stickers?: MessageStickerPayload[] stickers?: MessageStickerPayload[]
interaction?: MessageInteractionPayload interaction?: MessageInteractionPayload
components?: MessageComponentPayload[]
} }
export enum AllowedMentionType { export enum AllowedMentionType {
@ -211,7 +215,7 @@ export interface MessageOptions {
files?: MessageAttachment[] files?: MessageAttachment[]
allowedMentions?: AllowedMentionsPayload allowedMentions?: AllowedMentionsPayload
reply?: Message | MessageReference | string reply?: Message | MessageReference | string
components?: MessageComponentPayload[] components?: MessageComponentData[]
} }
export interface ChannelMention { export interface ChannelMention {
@ -395,6 +399,7 @@ export interface EditMessagePayload {
embed?: EmbedPayload embed?: EmbedPayload
allowed_mentions?: AllowedMentionsPayload allowed_mentions?: AllowedMentionsPayload
flags?: number flags?: number
components?: MessageComponentPayload[]
} }
export interface CreateMessagePayload extends EditMessagePayload { export interface CreateMessagePayload extends EditMessagePayload {

View File

@ -1,5 +1,13 @@
import { AllowedMentionsPayload, EmbedPayload } from './channel.ts' import {
AllowedMentionsPayload,
EmbedPayload,
MessagePayload
} from './channel.ts'
import type { MemberPayload } from './guild.ts' import type { MemberPayload } from './guild.ts'
import {
InteractionMessageComponentData,
MessageComponentData
} from './messageComponents.ts'
import type { InteractionApplicationCommandData } from './slashCommands.ts' import type { InteractionApplicationCommandData } from './slashCommands.ts'
import type { UserPayload } from './user.ts' import type { UserPayload } from './user.ts'
@ -7,7 +15,9 @@ export enum InteractionType {
/** Ping sent by the API (HTTP-only) */ /** Ping sent by the API (HTTP-only) */
PING = 1, PING = 1,
/** Slash Command Interaction */ /** Slash Command Interaction */
APPLICATION_COMMAND = 2 APPLICATION_COMMAND = 2,
/** Message Component Interaction */
MESSAGE_COMPONENT = 3
} }
export interface InteractionMemberPayload extends MemberPayload { export interface InteractionMemberPayload extends MemberPayload {
@ -27,14 +37,17 @@ export interface InteractionPayload {
/** ID of the Interaction */ /** ID of the Interaction */
id: string 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 */ /** ID of the Guild in which Interaction was invoked */
guild_id?: string guild_id?: string
/** ID of the Channel in which Interaction was invoked */ /** ID of the Channel in which Interaction was invoked */
channel_id?: string channel_id?: string
/** Application ID of the Client who received interaction */
application_id: string application_id: string
/** Message ID if the Interaction was of type MESSAGE_COMPONENT */
message?: MessagePayload
} }
export enum InteractionResponseType { export enum InteractionResponseType {
@ -62,6 +75,7 @@ export interface InteractionResponseDataPayload {
/** Allowed Mentions object */ /** Allowed Mentions object */
allowed_mentions?: AllowedMentionsPayload allowed_mentions?: AllowedMentionsPayload
flags?: number flags?: number
components?: MessageComponentData[]
} }
export enum InteractionResponseFlags { export enum InteractionResponseFlags {

View File

@ -17,4 +17,19 @@ export interface MessageComponentPayload {
label?: string label?: string
style?: ButtonStyle style?: ButtonStyle
url?: string 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
} }

View File

@ -12,7 +12,7 @@ export function simplifyAPIError(errors: any): SimplifiedError {
const arrayIndex = !isNaN(Number(obj[0])) const arrayIndex = !isNaN(Number(obj[0]))
if (arrayIndex) obj[0] = `[${obj[0]}]` if (arrayIndex) obj[0] = `[${obj[0]}]`
if (acum !== '' && !arrayIndex) acum += '.' if (acum !== '' && !arrayIndex) acum += '.'
fmt(obj[1], (acum += obj[0])) fmt(obj[1], acum + obj[0])
}) })
} }
} }

16
src/utils/interactions.ts Normal file
View File

@ -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
}

184
test/components.ts Normal file
View File

@ -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'])

View File

@ -8,12 +8,12 @@ import {
CommandContext, CommandContext,
Extension, Extension,
Collection, Collection,
GuildTextChannel GuildTextChannel,
} from '../../mod.ts' slash,
SlashCommandInteraction
} from '../mod.ts'
import { LL_IP, LL_PASS, LL_PORT, TOKEN } from './config.ts' import { LL_IP, LL_PASS, LL_PORT, TOKEN } from './config.ts'
import { Manager, Player } from 'https://deno.land/x/lavadeno/mod.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' // import { SlashCommandOptionType } from '../types/slash.ts'
export const nodes = [ export const nodes = [
@ -58,12 +58,12 @@ class MyClient extends CommandClient {
} }
@subslash('cmd', 'sub-cmd-no-grp') @subslash('cmd', 'sub-cmd-no-grp')
subCmdNoGroup(d: Interaction): void { subCmdNoGroup(d: SlashCommandInteraction): void {
d.respond({ content: 'sub-cmd-no-group worked' }) d.respond({ content: 'sub-cmd-no-group worked' })
} }
@groupslash('cmd', 'sub-cmd-group', 'sub-cmd') @groupslash('cmd', 'sub-cmd-group', 'sub-cmd')
subCmdGroup(d: Interaction): void { subCmdGroup(d: SlashCommandInteraction): void {
d.respond({ content: 'sub-cmd-group worked' }) d.respond({ content: 'sub-cmd-group worked' })
} }
@ -79,7 +79,7 @@ class MyClient extends CommandClient {
} }
@slash() @slash()
run(d: Interaction): void { run(d: SlashCommandInteraction): void {
console.log(d.name) console.log(d.name)
} }