This commit is contained in:
DjDeveloperr 2021-05-06 08:15:49 +05:30
commit 7570dfffbe
26 changed files with 922 additions and 482 deletions

View File

@ -3,7 +3,7 @@
<p align=center><i><b>An easy to use Discord API Library for Deno</b></i></p>
<p align=center>
<img src="https://img.shields.io/badge/standard--readme-OK-green.svg?style=for-the-badge"/>
<a href=https://discord.gg/WVN2JF2FRv>
<a href=https://discord.gg/harmony>
<img src="https://img.shields.io/discord/783319033205751809.svg?label=Discord&logo=Discord&colorB=7289da&style=for-the-badge" alt="Support">
</a>
</p>
@ -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

View File

@ -1,15 +1,20 @@
import { Interaction } from './src/structures/interactions.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
publicKey?: string
token?: string
path?: string
}
/** Current Slash Client being used to handle commands */
@ -37,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) {
@ -60,6 +69,9 @@ export function init(options: DeploySlashInitOptions): void {
respondWith: CallableFunction
request: Request
}): Promise<void> => {
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({
@ -124,8 +136,16 @@ 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/structures/interactions.ts'
export * from './src/structures/message.ts'
export * from './src/structures/embed.ts'

6
mod.ts
View File

@ -39,7 +39,9 @@ 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/structures/interactions.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'
@ -191,3 +193,5 @@ export {
isVoiceChannel,
default as getChannelByType
} from './src/utils/channel.ts'
export * from './src/utils/interactions.ts'
export * from "./src/utils/command.ts"

View File

@ -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<string | string[]>
@ -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,

View File

@ -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<string, unknown> | 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[]

View File

@ -1,17 +1,31 @@
/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
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'
import { User } from '../../structures/user.ts'
import { Role } from '../../structures/role.ts'
import { RolePayload } from '../../types/role.ts'
import {
InteractionApplicationCommandData,
InteractionChannelPayload
} from '../../types/slashCommands.ts'
import { Message } from '../../structures/message.ts'
import { TextChannel } from '../../structures/textChannel.ts'
export const interactionCreate: GatewayEventHandler = async (
gateway: Gateway,
@ -26,13 +40,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 =
@ -41,9 +64,9 @@ export const interactionCreate: GatewayEventHandler = async (
const user = member !== undefined ? member.user : dmUser
if (user === undefined) return
const channel =
(await gateway.client.channels.get<GuildTextBasedChannel>(d.channel_id)) ??
(await gateway.client.channels.fetch<GuildTextBasedChannel>(d.channel_id))
const channel = await gateway.client.channels.get<GuildTextBasedChannel>(
d.channel_id
)
const resolved: InteractionApplicationCommandResolved = {
users: {},
@ -52,9 +75,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 InteractionApplicationCommandData)?.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
@ -62,49 +87,87 @@ 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 InteractionApplicationCommandData)?.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 InteractionApplicationCommandData).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 InteractionApplicationCommandData).resolved?.channels ?? {}
)) {
resolved.channels[id] = new InteractionChannel(
gateway.client,
data as InteractionChannelPayload
)
}
}
const interaction = new Interaction(gateway.client, d, {
member,
guild,
channel,
user,
resolved
})
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
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,
message
})
}
gateway.client.emit('interactionCreate', interaction)
}

View File

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

View File

@ -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'
@ -17,7 +18,9 @@ import { HarmonyEventEmitter } from '../utils/events.ts'
import { encodeText, decodeText } from '../utils/encoding.ts'
import { SlashCommandsManager } from './slashCommand.ts'
export type SlashCommandHandlerCallback = (interaction: Interaction) => unknown
export type SlashCommandHandlerCallback = (
interaction: SlashCommandInteraction
) => unknown
export interface SlashCommandHandler {
name: string
guild?: string
@ -185,7 +188,9 @@ export class SlashClient extends HarmonyEventEmitter<SlashClientEvents> {
}
/** 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 =
@ -216,28 +221,28 @@ export class SlashClient extends HarmonyEventEmitter<SlashClientEvents> {
}
/** Process an incoming Interaction */
private async _process(interaction: Interaction): Promise<void> {
private async _process(
interaction: Interaction | SlashCommandInteraction
): Promise<void> {
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
await this.emit('interaction', interaction)
try {
await cmd.handler(interaction)
await cmd.handler(interaction as SlashCommandInteraction)
} catch (e) {
await this.emit('interactionError', e)
}
@ -286,20 +291,31 @@ export class SlashClient extends HarmonyEventEmitter<SlashClientEvents> {
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 as any)
?.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,

View File

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

View File

@ -1 +0,0 @@
export {}

View File

@ -12,8 +12,6 @@ import type {
import { CHANNEL } from '../types/endpoint.ts'
import getChannelByType from '../utils/channel.ts'
import { BaseManager } from './base.ts'
// Deno is bugged
import {} from './_util.ts'
export type AllMessageOptions = MessageOptions | Embed

View File

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

View File

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

View File

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

View File

@ -0,0 +1,348 @@
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 { 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 { TextChannel } from './textChannel.ts'
import { User } from './user.ts'
interface WebhookMessageOptions extends MessageOptions {
embeds?: Array<Embed | EmbedPayload>
name?: string
avatar?: string
}
type AllWebhookMessageOptions = string | WebhookMessageOptions
/** Interaction Message related Options */
export interface InteractionMessageOptions {
content?: string
embeds?: Array<Embed | EmbedPayload>
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<T = Channel>(): Promise<T | undefined> {
return this.client.channels.get<T>(this.id)
}
}
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
message?: Message
constructor(
client: Client,
data: InteractionPayload,
others: {
channel?: TextChannel | GuildTextChannel
guild?: Guild
member?: Member
user: User
message?: Message
}
) {
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
this.message = others.message
}
/** Respond to an Interaction */
async respond(data: InteractionResponse): Promise<Interaction> {
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<Interaction> {
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<Interaction>
async reply(options: InteractionMessageOptions): Promise<Interaction>
async reply(
content: string,
options: InteractionMessageOptions
): Promise<Interaction>
async reply(
content: string | InteractionMessageOptions,
messageOptions?: InteractionMessageOptions
): Promise<Interaction> {
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<Embed | EmbedPayload>
flags?: number | number[]
allowedMentions?: AllowedMentionsPayload
}): Promise<Interaction> {
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<Interaction> {
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<Message> {
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<Embed | EmbedPayload>
file?: any
allowed_mentions?: {
parse?: string
roles?: string[]
users?: string[]
everyone?: boolean
}
}
): Promise<Interaction> {
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<Interaction> {
await this.client.rest.delete(
WEBHOOK_MESSAGE(
this.applicationID,
this.token ?? this.client.token,
typeof msg === 'string' ? msg : msg.id
)
)
return this
}
}

View File

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

View File

@ -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<Embed | EmbedPayload>
name?: string
avatar?: string
}
type AllWebhookMessageOptions = string | WebhookMessageOptions
/** Interaction Message related Options */
export interface InteractionMessageOptions {
content?: string
embeds?: Array<Embed | EmbedPayload>
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<T = Channel>(): Promise<T | undefined> {
return this.client.channels.get<T>(this.id)
}
}
import {
InteractionUser,
InteractionChannel,
Interaction
} from './interactions.ts'
export interface InteractionApplicationCommandResolved {
users: Dict<InteractionUser>
@ -83,36 +25,11 @@ export interface InteractionApplicationCommandResolved {
roles: Dict<Role>
}
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<Interaction> {
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<Interaction> {
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<Interaction>
async reply(options: InteractionMessageOptions): Promise<Interaction>
async reply(
content: string,
options: InteractionMessageOptions
): Promise<Interaction>
async reply(
content: string | InteractionMessageOptions,
messageOptions?: InteractionMessageOptions
): Promise<Interaction> {
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<Embed | EmbedPayload>
flags?: number | number[]
allowedMentions?: AllowedMentionsPayload
}): Promise<Interaction> {
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<Interaction> {
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<Message> {
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<Embed | EmbedPayload>
file?: any
allowed_mentions?: {
parse?: string
roles?: string[]
users?: string[]
everyone?: boolean
}
}
): Promise<Interaction> {
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<Interaction> {
await this.client.rest.delete(
WEBHOOK_MESSAGE(
this.applicationID,
this.token ?? this.client.token,
typeof msg === 'string' ? msg : msg.id
)
)
return this
}
}

View File

@ -5,7 +5,7 @@ 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'
export interface ChannelPayload {

View File

@ -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'
/**

77
src/types/interactions.ts Normal file
View File

@ -0,0 +1,77 @@
import {
AllowedMentionsPayload,
EmbedPayload,
MessagePayload
} 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 PING (http-only).*
*/
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 of the Client who received interaction */
application_id: string
/** Message ID if the Interaction was of type MESSAGE_COMPONENT */
message?: MessagePayload
}
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
}

View File

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

107
src/utils/command.ts Normal file
View File

@ -0,0 +1,107 @@
interface MentionToRegex {
[key: string]: RegExp
mentionUser: RegExp
mentionRole: RegExp
mentionChannel: RegExp
}
const mentionToRegex: MentionToRegex = {
mentionUser: /<@!?(\d{17,19})>/,
mentionRole: /<@&(\d{17,19})>/,
mentionChannel: /<#(\d{17,19})>/
}
export type CommandArgumentMatchTypes =
| 'flag'
| 'mentionUser'
| 'mentionRole'
| 'mentionChannel'
| 'content'
| 'rest'
export interface Args<T = unknown> {
name: string
match: CommandArgumentMatchTypes
defaultValue?: T
flag?: string
}
export function parseArgs(
commandArgs: Args[] | undefined,
messageArgs: string[]
): Record<string, unknown> | null {
if (commandArgs === undefined) return null
const messageArgsNullableCopy: Array<string | null> = [...messageArgs]
const args: Record<string, unknown> = {}
for (const entry of commandArgs) {
switch (entry.match) {
case 'flag':
parseFlags(args, entry, messageArgsNullableCopy)
break
case 'mentionUser':
case 'mentionRole':
case 'mentionChannel':
parseMention(args, entry, messageArgsNullableCopy)
break
case 'content':
parseContent(args, entry, messageArgs)
break
case 'rest':
parseRest(args, entry, messageArgsNullableCopy)
break
}
}
return args
}
function parseFlags(
args: Record<string, unknown>,
entry: Args,
argsNullable: Array<string | null>
): 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
}
}
function parseMention(
args: Record<string, unknown>,
entry: Args,
argsNullable: Array<string | null>
): void {
const regex = mentionToRegex[entry.match]
const index = argsNullable.findIndex(
(x) => typeof x === 'string' && regex.test(x)
)
const regexMatches = regex.exec(argsNullable[index]!)
args[entry.name] =
regexMatches !== null
? regexMatches[0].replace(regex, '$1')
: entry.defaultValue
argsNullable[index] = null
}
function parseContent(
args: Record<string, unknown>,
entry: Args,
argsNonNullable: Array<string | null>
): void {
args[entry.name] =
argsNonNullable.length > 0 ? argsNonNullable : entry.defaultValue
}
function parseRest(
args: Record<string, unknown>,
entry: Args,
argsNullable: Array<string | null>
): void {
const restValues = argsNullable.filter((x) => typeof x === 'string')
args[entry.name] =
restValues.length > 0 ? restValues?.join(' ') : entry.defaultValue
}

View File

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

View File

@ -0,0 +1,9 @@
import { InteractionType } from '../../mod.ts'
import { Interaction } from '../structures/interactions.ts'
import { SlashCommandInteraction } from '../structures/slash.ts'
export function isSlashCommandInteraction(
d: Interaction
): d is SlashCommandInteraction {
return d.type === InteractionType.APPLICATION_COMMAND
}

174
test/argsparser_test.ts Normal file
View File

@ -0,0 +1,174 @@
import { Args, parseArgs } from '../src/utils/command.ts'
import {
assertEquals,
assertNotEquals
} from 'https://deno.land/std@0.95.0/testing/asserts.ts'
const commandArgs: Args[] = [
{
name: 'originalMessage',
match: 'content'
},
{
name: 'permaban',
match: 'flag',
flag: '--permanent',
defaultValue: true
},
{
name: 'user',
match: 'mentionUser'
},
{
name: 'reason',
match: 'rest',
defaultValue: 'ree'
}
]
const messageArgs1: string[] = [
'<@!708544768342229012>',
'--permanent',
'bye',
'bye',
'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)
assertEquals(result, expectedResult1)
},
sanitizeOps: true,
sanitizeResources: true,
sanitizeExit: true
})
const messageArgs2: string[] = [
'<@!708544768342229012>',
'bye',
'bye',
'Skyler'
]
const expectedResult2 = {
originalMessage: ['<@!708544768342229012>', 'bye', 'bye', 'Skyler'],
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 default value (assertNotEquals)',
fn: () => {
const result = parseArgs(commandArgs, messageArgs3)
assertNotEquals(result, expectedResult3)
},
sanitizeOps: true,
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
})
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)
}
})

View File

@ -9,8 +9,8 @@ import {
Extension,
Collection,
GuildTextChannel,
Interaction,
slash
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'
@ -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)
}