diff --git a/deploy.ts b/deploy.ts new file mode 100644 index 0000000..536e17b --- /dev/null +++ b/deploy.ts @@ -0,0 +1,87 @@ +import { + SlashCommandsManager, + SlashClient, + SlashCommandHandlerCallback +} from './src/models/slashClient.ts' +import { InteractionResponseType, InteractionType } from './src/types/slash.ts' + +export interface DeploySlashInitOptions { + env?: boolean + publicKey?: string + token?: string + id?: string +} + +let client: SlashClient +let commands: SlashCommandsManager + +export function init(options: DeploySlashInitOptions): void { + if (client !== undefined) throw new Error('Already initialized') + if (options.env === true) { + options.publicKey = Deno.env.get('PUBLIC_KEY') + options.token = Deno.env.get('TOKEN') + options.id = Deno.env.get('ID') + } + + if (options.publicKey === undefined) + throw new Error('Public Key not provided') + + client = new SlashClient({ + id: options.id, + token: options.token, + publicKey: options.publicKey + }) + + commands = client.commands + + const cb = async (evt: { + respondWith: CallableFunction + request: Request + }): Promise => { + try { + const d = await client.verifyFetchEvent(evt) + if (d === false) { + await evt.respondWith( + new Response(null, { + status: 400 + }) + ) + return + } + + if (d.type === InteractionType.PING) { + await d.respond({ type: InteractionResponseType.PONG }) + return + } + + await (client as any)._process(d) + } catch (e) { + await client.emit('interactionError', e) + } + } + + addEventListener('fetch', cb as any) +} + +export function handle( + cmd: + | string + | { + name: string + parent?: string + group?: string + guild?: string + }, + handler: SlashCommandHandlerCallback +): void { + client.handle({ + name: typeof cmd === 'string' ? cmd : cmd.name, + handler, + ...(typeof cmd === 'string' ? {} : cmd) + }) +} + +export { commands, client } +export * from './src/types/slash.ts' +export * from './src/structures/slash.ts' +export * from './src/models/slashClient.ts' diff --git a/src/models/client.ts b/src/models/client.ts index 83846cd..dc40e24 100644 --- a/src/models/client.ts +++ b/src/models/client.ts @@ -13,7 +13,6 @@ import { ActivityGame, ClientActivity } from '../types/presence.ts' import { Extension } from './extensions.ts' import { SlashClient } from './slashClient.ts' import { Interaction } from '../structures/slash.ts' -import { SlashModule } from './slashModule.ts' import { ShardManager } from './shard.ts' import { Application } from '../structures/application.ts' import { Invite } from '../structures/invite.ts' @@ -190,10 +189,10 @@ export class Client extends HarmonyEventEmitter { this.clientProperties = options.clientProperties === undefined ? { - os: Deno.build.os, - browser: 'harmony', - device: 'harmony' - } + os: Deno.build.os, + browser: 'harmony', + device: 'harmony' + } : options.clientProperties if (options.shard !== undefined) this.shard = options.shard @@ -208,7 +207,7 @@ export class Client extends HarmonyEventEmitter { this.token = token this.debug('Info', 'Found token in ENV') } - } catch (e) {} + } catch (e) { } } const restOptions: RESTOptions = { @@ -436,59 +435,3 @@ export function event(name?: keyof ClientEvents) { client._decoratedEvents[key] = listener } } - -/** 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 = [] - const item = (client as { [name: string]: any })[prop] - if (typeof item !== 'function') { - throw new Error('@slash decorator requires a function') - } else - client._decoratedSlash.push({ - name: name ?? prop, - guild, - handler: item - }) - } -} - -/** 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 = [] - const item = (client as { [name: string]: any })[prop] - if (typeof item !== 'function') { - throw new Error('@subslash decorator requires a function') - } else - client._decoratedSlash.push({ - parent, - name: name ?? prop, - guild, - handler: item - }) - } -} - -/** Decorator to create a Grouped Slash Command handler */ -export function groupslash( - parent: string, - group: string, - name?: string, - guild?: string -) { - return function (client: Client | SlashModule | SlashClient, prop: string) { - if (client._decoratedSlash === undefined) client._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({ - group, - parent, - name: name ?? prop, - guild, - handler: item - }) - } -} diff --git a/src/models/slashClient.ts b/src/models/slashClient.ts index 0dac52c..f4dbec1 100644 --- a/src/models/slashClient.ts +++ b/src/models/slashClient.ts @@ -1,4 +1,4 @@ -import { Guild } from '../structures/guild.ts' +import type { Guild } from '../structures/guild.ts' import { Interaction, InteractionApplicationCommandResolved @@ -14,11 +14,12 @@ import { SlashCommandPayload } from '../types/slash.ts' import { Collection } from '../utils/collection.ts' -import { Client } from './client.ts' +import type { Client } from './client.ts' import { RESTManager } from './rest.ts' import { SlashModule } from './slashModule.ts' import { verify as edverify } from 'https://deno.land/x/ed25519@1.0.1/mod.ts' import { User } from '../structures/user.ts' +import { HarmonyEventEmitter } from "../utils/events.ts" export class SlashCommand { slash: SlashCommandsManager @@ -380,8 +381,14 @@ export interface SlashOptions { const encoder = new TextEncoder() const decoder = new TextDecoder('utf-8') +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +export type SlashClientEvents = { + interaction: [Interaction] + interactionError: [Error] +} + /** Slash Client represents an Interactions Client which can be used without Harmony Client. */ -export class SlashClient { +export class SlashClient extends HarmonyEventEmitter { id: string | (() => string) client?: Client token?: string @@ -401,6 +408,7 @@ export class SlashClient { }> constructor(options: SlashOptions) { + super() let id = options.id if (options.token !== undefined) id = atob(options.token?.split('.')[0]) if (id === undefined) @@ -435,8 +443,8 @@ export class SlashClient { : options.rest : options.client.rest - this.client?.on('interactionCreate', (interaction) => - this._process(interaction) + this.client?.on('interactionCreate', async (interaction) => + await this._process(interaction) ) this.commands = new SlashCommandsManager(this) @@ -506,7 +514,7 @@ export class SlashClient { } /** Process an incoming Interaction */ - private _process(interaction: Interaction): void { + private async _process(interaction: Interaction): Promise { if (!this.enabled) return if ( @@ -523,7 +531,10 @@ export class SlashClient { if (cmd === undefined) return - cmd.handler(interaction) + await this.emit('interaction', interaction) + try { await cmd.handler(interaction) } catch (e) { + await this.emit('interactionError', e) + } } /** Verify HTTP based Interaction */ @@ -672,3 +683,59 @@ export class SlashClient { return true } } + +/** 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 = [] + const item = (client as { [name: string]: any })[prop] + if (typeof item !== 'function') { + throw new Error('@slash decorator requires a function') + } else + client._decoratedSlash.push({ + name: name ?? prop, + guild, + handler: item + }) + } +} + +/** 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 = [] + const item = (client as { [name: string]: any })[prop] + if (typeof item !== 'function') { + throw new Error('@subslash decorator requires a function') + } else + client._decoratedSlash.push({ + parent, + name: name ?? prop, + guild, + handler: item + }) + } +} + +/** Decorator to create a Grouped Slash Command handler */ +export function groupslash( + parent: string, + group: string, + name?: string, + guild?: string +) { + return function (client: Client | SlashModule | SlashClient, prop: string) { + if (client._decoratedSlash === undefined) client._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({ + group, + parent, + name: name ?? prop, + guild, + handler: item + }) + } +}