2021-03-30 09:51:29 +00:00
import type { Guild } from '../structures/guild.ts'
2021-03-14 10:16:44 +00:00
import {
Interaction ,
InteractionApplicationCommandResolved
} from '../structures/slash.ts'
2020-12-10 06:55:52 +00:00
import {
2021-03-14 09:42:05 +00:00
InteractionPayload ,
2021-03-14 10:16:44 +00:00
InteractionResponsePayload ,
2020-12-12 12:27:35 +00:00
InteractionType ,
2020-12-21 13:48:46 +00:00
SlashCommandChoice ,
2020-12-10 06:55:52 +00:00
SlashCommandOption ,
2020-12-21 13:48:46 +00:00
SlashCommandOptionType ,
2020-12-10 06:55:52 +00:00
SlashCommandPartial ,
SlashCommandPayload
} from '../types/slash.ts'
import { Collection } from '../utils/collection.ts'
2021-03-30 09:51:29 +00:00
import type { Client } from './client.ts'
2020-12-20 09:45:49 +00:00
import { RESTManager } from './rest.ts'
2020-12-22 06:58:45 +00:00
import { SlashModule } from './slashModule.ts'
2021-03-14 08:50:15 +00:00
import { verify as edverify } from 'https://deno.land/x/ed25519@1.0.1/mod.ts'
2021-03-14 10:16:44 +00:00
import { User } from '../structures/user.ts'
2021-03-30 09:51:29 +00:00
import { HarmonyEventEmitter } from "../utils/events.ts"
2020-12-10 06:55:52 +00:00
export class SlashCommand {
2020-12-10 09:10:00 +00:00
slash : SlashCommandsManager
2020-12-10 06:55:52 +00:00
id : string
applicationID : string
name : string
description : string
options : SlashCommandOption [ ]
2021-01-26 20:15:47 +00:00
guild? : Guild
2020-12-10 09:10:00 +00:00
_guild? : string
2020-12-10 06:55:52 +00:00
2021-01-26 20:15:47 +00:00
constructor (
manager : SlashCommandsManager ,
data : SlashCommandPayload ,
guild? : Guild
) {
2020-12-10 09:10:00 +00:00
this . slash = manager
2020-12-10 06:55:52 +00:00
this . id = data . id
this . applicationID = data . application_id
this . name = data . name
this . description = data . description
2020-12-16 10:42:52 +00:00
this . options = data . options ? ? [ ]
2021-01-26 20:15:47 +00:00
this . guild = guild
2020-12-10 06:55:52 +00:00
}
2020-12-10 09:10:00 +00:00
async delete ( ) : Promise < void > {
await this . slash . delete ( this . id , this . _guild )
}
async edit ( data : SlashCommandPartial ) : Promise < void > {
await this . slash . edit ( this . id , data , this . _guild )
}
2020-12-22 06:58:45 +00:00
/** Create a handler for this Slash Command */
handle (
func : SlashCommandHandlerCallback ,
options ? : { parent? : string ; group? : string }
) : SlashCommand {
this . slash . slash . handle ( {
name : this.name ,
parent : options?.parent ,
group : options?.group ,
guild : this._guild ,
handler : func
} )
return this
}
2020-12-10 06:55:52 +00:00
}
2020-12-21 13:48:46 +00:00
export interface CreateOptions {
name : string
description? : string
options? : Array < SlashCommandOption | SlashOptionCallable >
choices? : Array < SlashCommandChoice | string >
}
function createSlashOption (
type : SlashCommandOptionType ,
data : CreateOptions
) : SlashCommandOption {
return {
name : data.name ,
type ,
description :
type === 0 || type === 1
? undefined
: data . description ? ? 'No description.' ,
options : data.options?.map ( ( e ) = >
2020-12-22 06:58:45 +00:00
typeof e === 'function' ? e ( SlashOption ) : e
2020-12-21 13:48:46 +00:00
) ,
choices :
data . choices === undefined
? undefined
: data . choices . map ( ( e ) = >
2021-03-29 12:30:40 +00:00
typeof e === 'string' ? { name : e , value : e } : e
)
2020-12-21 13:48:46 +00:00
}
}
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
2020-12-22 06:58:45 +00:00
export class SlashOption {
2020-12-21 13:48:46 +00:00
static string ( data : CreateOptions ) : SlashCommandOption {
return createSlashOption ( SlashCommandOptionType . STRING , data )
}
static bool ( data : CreateOptions ) : SlashCommandOption {
return createSlashOption ( SlashCommandOptionType . BOOLEAN , data )
}
static subCommand ( data : CreateOptions ) : SlashCommandOption {
return createSlashOption ( SlashCommandOptionType . SUB_COMMAND , data )
}
static subCommandGroup ( data : CreateOptions ) : SlashCommandOption {
return createSlashOption ( SlashCommandOptionType . SUB_COMMAND_GROUP , data )
}
static role ( data : CreateOptions ) : SlashCommandOption {
return createSlashOption ( SlashCommandOptionType . ROLE , data )
}
static channel ( data : CreateOptions ) : SlashCommandOption {
return createSlashOption ( SlashCommandOptionType . CHANNEL , data )
}
static user ( data : CreateOptions ) : SlashCommandOption {
return createSlashOption ( SlashCommandOptionType . USER , data )
}
static number ( data : CreateOptions ) : SlashCommandOption {
return createSlashOption ( SlashCommandOptionType . INTEGER , data )
}
}
2020-12-22 06:58:45 +00:00
export type SlashOptionCallable = ( o : typeof SlashOption ) = > SlashCommandOption
2020-12-21 13:48:46 +00:00
export type SlashBuilderOptionsData =
| Array < SlashCommandOption | SlashOptionCallable >
| {
2021-03-29 12:30:40 +00:00
[ name : string ] :
| {
description : string
type : SlashCommandOptionType
options? : SlashCommandOption [ ]
choices? : SlashCommandChoice [ ]
2020-12-21 13:48:46 +00:00
}
2021-03-29 12:30:40 +00:00
| SlashOptionCallable
}
2020-12-21 13:48:46 +00:00
function buildOptionsArray (
options : SlashBuilderOptionsData
) : SlashCommandOption [ ] {
return Array . isArray ( options )
2020-12-22 06:58:45 +00:00
? options . map ( ( op ) = > ( typeof op === 'function' ? op ( SlashOption ) : op ) )
2020-12-21 13:48:46 +00:00
: Object . entries ( options ) . map ( ( entry ) = >
2021-03-29 12:30:40 +00:00
typeof entry [ 1 ] === 'function'
? entry [ 1 ] ( SlashOption )
: Object . assign ( entry [ 1 ] , { name : entry [ 0 ] } )
)
2020-12-21 13:48:46 +00:00
}
2021-02-10 12:29:21 +00:00
/** Slash Command Builder */
2020-12-21 13:48:46 +00:00
export class SlashBuilder {
data : SlashCommandPartial
constructor (
name? : string ,
description? : string ,
options? : SlashBuilderOptionsData
) {
this . data = {
name : name ? ? '' ,
description : description ? ? 'No description.' ,
options : options === undefined ? [ ] : buildOptionsArray ( options )
}
}
name ( name : string ) : SlashBuilder {
this . data . name = name
return this
}
description ( desc : string ) : SlashBuilder {
this . data . description = desc
return this
}
option ( option : SlashOptionCallable | SlashCommandOption ) : SlashBuilder {
if ( this . data . options === undefined ) this . data . options = [ ]
this . data . options . push (
2020-12-22 06:58:45 +00:00
typeof option === 'function' ? option ( SlashOption ) : option
2020-12-21 13:48:46 +00:00
)
return this
}
options ( options : SlashBuilderOptionsData ) : SlashBuilder {
this . data . options = buildOptionsArray ( options )
return this
}
export ( ) : SlashCommandPartial {
if ( this . data . name === '' )
throw new Error ( 'Name was not provided in Slash Builder' )
return this . data
}
}
2021-02-10 12:29:21 +00:00
/** Manages Slash Commands, allows fetching/modifying/deleting/creating Slash Commands. */
2020-12-10 09:10:00 +00:00
export class SlashCommandsManager {
2020-12-10 06:55:52 +00:00
slash : SlashClient
2021-01-24 18:36:19 +00:00
rest : RESTManager
2020-12-20 09:45:49 +00:00
constructor ( client : SlashClient ) {
this . slash = client
2021-01-24 18:36:19 +00:00
this . rest = client . rest
2020-12-10 06:55:52 +00:00
}
/** Get all Global Slash Commands */
async all ( ) : Promise < Collection < string , SlashCommand > > {
const col = new Collection < string , SlashCommand > ( )
2020-12-21 13:48:46 +00:00
const res = ( await this . rest . api . applications [
this . slash . getID ( )
] . commands . get ( ) ) as SlashCommandPayload [ ]
2020-12-10 06:55:52 +00:00
if ( ! Array . isArray ( res ) ) return col
for ( const raw of res ) {
2020-12-10 09:10:00 +00:00
const cmd = new SlashCommand ( this , raw )
col . set ( raw . id , cmd )
2020-12-10 06:55:52 +00:00
}
return col
}
/** Get a Guild's Slash Commands */
async guild (
guild : Guild | string
) : Promise < Collection < string , SlashCommand > > {
const col = new Collection < string , SlashCommand > ( )
2020-12-21 13:48:46 +00:00
const res = ( await this . rest . api . applications [ this . slash . getID ( ) ] . guilds [
typeof guild === 'string' ? guild : guild.id
] . commands . get ( ) ) as SlashCommandPayload [ ]
2020-12-10 06:55:52 +00:00
if ( ! Array . isArray ( res ) ) return col
2021-01-26 20:15:47 +00:00
const _guild =
typeof guild === 'object'
? guild
: await this . slash . client ? . guilds . get ( guild )
2020-12-10 06:55:52 +00:00
for ( const raw of res ) {
2021-01-26 20:15:47 +00:00
const cmd = new SlashCommand ( this , raw , _guild )
2020-12-10 09:10:00 +00:00
cmd . _guild = typeof guild === 'string' ? guild : guild.id
col . set ( raw . id , cmd )
2020-12-10 06:55:52 +00:00
}
return col
}
/** Create a Slash Command (global or Guild) */
async create (
data : SlashCommandPartial ,
guild? : Guild | string
) : Promise < SlashCommand > {
2020-12-21 13:48:46 +00:00
const route =
2020-12-10 06:55:52 +00:00
guild === undefined
2020-12-21 13:48:46 +00:00
? this . rest . api . applications [ this . slash . getID ( ) ] . commands
: this . rest . api . applications [ this . slash . getID ( ) ] . guilds [
2021-03-29 12:30:40 +00:00
typeof guild === 'string' ? guild : guild.id
] . commands
2020-12-21 13:48:46 +00:00
const payload = await route . post ( data )
2020-12-10 06:55:52 +00:00
2021-01-26 20:15:47 +00:00
const _guild =
typeof guild === 'object'
? guild
: guild === undefined
2021-03-29 12:30:40 +00:00
? undefined
: await this . slash . client ? . guilds . get ( guild )
2021-01-26 20:15:47 +00:00
const cmd = new SlashCommand ( this , payload , _guild )
2020-12-10 09:10:00 +00:00
cmd . _guild =
typeof guild === 'string' || guild === undefined ? guild : guild.id
return cmd
2020-12-10 06:55:52 +00:00
}
2020-12-10 09:10:00 +00:00
/** Edit a Slash Command (global or Guild) */
2020-12-10 06:55:52 +00:00
async edit (
id : string ,
2020-12-10 09:10:00 +00:00
data : SlashCommandPartial ,
guild? : Guild | string
) : Promise < SlashCommandsManager > {
2020-12-21 13:48:46 +00:00
const route =
2020-12-10 06:55:52 +00:00
guild === undefined
2020-12-21 13:48:46 +00:00
? this . rest . api . applications [ this . slash . getID ( ) ] . commands [ id ]
: this . rest . api . applications [ this . slash . getID ( ) ] . guilds [
2021-03-29 12:30:40 +00:00
typeof guild === 'string' ? guild : guild.id
] . commands [ id ]
2020-12-21 13:48:46 +00:00
await route . patch ( data )
2020-12-10 06:55:52 +00:00
return this
}
2020-12-10 09:10:00 +00:00
/** Delete a Slash Command (global or Guild) */
async delete (
id : string ,
guild? : Guild | string
) : Promise < SlashCommandsManager > {
2020-12-21 13:48:46 +00:00
const route =
2020-12-10 09:10:00 +00:00
guild === undefined
2020-12-21 13:48:46 +00:00
? this . rest . api . applications [ this . slash . getID ( ) ] . commands [ id ]
: this . rest . api . applications [ this . slash . getID ( ) ] . guilds [
2021-03-29 12:30:40 +00:00
typeof guild === 'string' ? guild : guild.id
] . commands [ id ]
2020-12-21 13:48:46 +00:00
await route . delete ( )
2020-12-10 09:10:00 +00:00
return this
}
2020-12-20 09:45:49 +00:00
/** Get a Slash Command (global or Guild) */
async get ( id : string , guild? : Guild | string ) : Promise < SlashCommand > {
2020-12-21 13:48:46 +00:00
const route =
2020-12-20 09:45:49 +00:00
guild === undefined
2020-12-21 13:48:46 +00:00
? this . rest . api . applications [ this . slash . getID ( ) ] . commands [ id ]
: this . rest . api . applications [ this . slash . getID ( ) ] . guilds [
2021-03-29 12:30:40 +00:00
typeof guild === 'string' ? guild : guild.id
] . commands [ id ]
2020-12-21 13:48:46 +00:00
const data = await route . get ( )
2021-01-26 20:15:47 +00:00
const _guild =
typeof guild === 'object'
? guild
: guild === undefined
2021-03-29 12:30:40 +00:00
? undefined
: await this . slash . client ? . guilds . get ( guild )
2021-01-26 20:15:47 +00:00
return new SlashCommand ( this , data , _guild )
2020-12-20 09:45:49 +00:00
}
2021-01-24 14:20:49 +00:00
/** Bulk Edit Global or Guild Slash Commands */
async bulkEdit (
cmds : Array < SlashCommandPartial | SlashCommandPayload > ,
guild? : Guild | string
) : Promise < SlashCommandsManager > {
const route =
guild === undefined
? this . rest . api . applications [ this . slash . getID ( ) ] . commands
: this . rest . api . applications [ this . slash . getID ( ) ] . guilds [
2021-03-29 12:30:40 +00:00
typeof guild === 'string' ? guild : guild.id
] . commands
2021-01-24 14:20:49 +00:00
await route . put ( cmds )
return this
}
2020-12-10 09:10:00 +00:00
}
2021-02-10 12:29:21 +00:00
export type SlashCommandHandlerCallback = ( interaction : Interaction ) = > unknown
2020-12-10 09:10:00 +00:00
export interface SlashCommandHandler {
name : string
guild? : string
2020-12-16 10:00:13 +00:00
parent? : string
2020-12-16 13:05:26 +00:00
group? : string
2020-12-10 09:10:00 +00:00
handler : SlashCommandHandlerCallback
2020-12-10 06:55:52 +00:00
}
2021-02-10 12:29:21 +00:00
/** Options for SlashClient */
2020-12-20 09:45:49 +00:00
export interface SlashOptions {
id? : string | ( ( ) = > string )
client? : Client
enabled? : boolean
token? : string
rest? : RESTManager
2020-12-22 06:58:45 +00:00
publicKey? : string
2020-12-20 09:45:49 +00:00
}
2021-03-14 10:16:44 +00:00
const encoder = new TextEncoder ( )
const decoder = new TextDecoder ( 'utf-8' )
2021-03-30 09:51:29 +00:00
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export type SlashClientEvents = {
interaction : [ Interaction ]
interactionError : [ Error ]
2021-03-30 10:07:13 +00:00
ping : [ ]
2021-03-30 09:51:29 +00:00
}
2021-02-10 12:29:21 +00:00
/** Slash Client represents an Interactions Client which can be used without Harmony Client. */
2021-03-30 09:51:29 +00:00
export class SlashClient extends HarmonyEventEmitter < SlashClientEvents > {
2020-12-20 09:45:49 +00:00
id : string | ( ( ) = > string )
client? : Client
token? : string
2020-12-10 06:55:52 +00:00
enabled : boolean = true
2020-12-10 09:10:00 +00:00
commands : SlashCommandsManager
handlers : SlashCommandHandler [ ] = [ ]
2020-12-20 09:45:49 +00:00
rest : RESTManager
2020-12-22 06:58:45 +00:00
modules : SlashModule [ ] = [ ]
publicKey? : string
_decoratedSlash? : Array < {
name : string
guild? : string
parent? : string
group? : string
handler : ( interaction : Interaction ) = > any
} >
2020-12-20 09:45:49 +00:00
constructor ( options : SlashOptions ) {
2021-03-30 09:51:29 +00:00
super ( )
2020-12-20 09:45:49 +00:00
let id = options . id
if ( options . token !== undefined ) id = atob ( options . token ? . split ( '.' ) [ 0 ] )
if ( id === undefined )
throw new Error ( 'ID could not be found. Pass at least client or token' )
this . id = id
this . client = options . client
this . token = options . token
2020-12-22 06:58:45 +00:00
this . publicKey = options . publicKey
2020-12-10 06:55:52 +00:00
2021-01-24 18:49:08 +00:00
this . enabled = options . enabled ? ? true
2020-12-10 06:55:52 +00:00
2020-12-20 09:45:49 +00:00
if ( this . client ? . _decoratedSlash !== undefined ) {
2020-12-10 09:10:00 +00:00
this . client . _decoratedSlash . forEach ( ( e ) = > {
2021-01-24 18:36:19 +00:00
e . handler = e . handler . bind ( this . client )
2020-12-10 09:10:00 +00:00
this . handlers . push ( e )
} )
}
2020-12-22 06:58:45 +00:00
if ( this . _decoratedSlash !== undefined ) {
this . _decoratedSlash . forEach ( ( e ) = > {
2021-01-24 18:36:19 +00:00
e . handler = e . handler . bind ( this . client )
2020-12-22 06:58:45 +00:00
this . handlers . push ( e )
} )
}
2020-12-20 09:45:49 +00:00
this . rest =
options . client === undefined
? options . rest === undefined
? new RESTManager ( {
2021-03-29 12:30:40 +00:00
token : this.token
} )
2020-12-20 09:45:49 +00:00
: options . rest
: options . client . rest
2021-03-30 09:51:29 +00:00
this . client ? . on ( 'interactionCreate' , async ( interaction ) = >
await this . _process ( interaction )
2020-12-10 06:55:52 +00:00
)
2021-01-24 18:36:19 +00:00
this . commands = new SlashCommandsManager ( this )
2020-12-10 06:55:52 +00:00
}
2020-12-20 09:45:49 +00:00
getID ( ) : string {
return typeof this . id === 'string' ? this . id : this.id ( )
}
2020-12-10 09:10:00 +00:00
/** Adds a new Slash Command Handler */
2020-12-16 13:11:01 +00:00
handle ( handler : SlashCommandHandler ) : SlashClient {
this . handlers . push ( handler )
2020-12-10 06:55:52 +00:00
return this
}
2020-12-10 09:10:00 +00:00
2021-01-01 05:55:23 +00:00
/** Load a Slash Module */
2020-12-23 09:56:02 +00:00
loadModule ( module : SlashModule ) : SlashClient {
this . modules . push ( module )
return this
}
2021-01-01 05:55:23 +00:00
/** Get all Handlers. Including Slash Modules */
2020-12-22 06:58:45 +00:00
getHandlers ( ) : SlashCommandHandler [ ] {
let res = this . handlers
for ( const mod of this . modules ) {
2020-12-23 09:56:02 +00:00
if ( mod === undefined ) continue
res = [
. . . res ,
. . . mod . commands . map ( ( cmd ) = > {
cmd . handler = cmd . handler . bind ( mod )
return cmd
} )
]
2020-12-22 06:58:45 +00:00
}
return res
}
2021-01-01 05:55:23 +00:00
/** Get Handler for an Interaction. Supports nested sub commands and sub command groups. */
2020-12-16 13:05:26 +00:00
private _getCommand ( i : Interaction ) : SlashCommandHandler | undefined {
2020-12-22 06:58:45 +00:00
return this . getHandlers ( ) . find ( ( e ) = > {
2020-12-16 13:05:26 +00:00
const hasGroupOrParent = e . group !== undefined || e . parent !== undefined
const groupMatched =
e . group !== undefined && e . parent !== undefined
? i . options
2021-03-29 12:30:40 +00:00
. find (
( o ) = >
o . name === e . group &&
o . type === SlashCommandOptionType . SUB_COMMAND_GROUP
)
? . options ? . find ( ( o ) = > o . name === e . name ) !== undefined
2020-12-16 13:05:26 +00:00
: true
const subMatched =
e . group === undefined && e . parent !== undefined
2021-02-10 12:29:21 +00:00
? i . options . find (
2021-03-29 12:30:40 +00:00
( o ) = >
o . name === e . name &&
o . type === SlashCommandOptionType . SUB_COMMAND
) !== undefined
2020-12-16 13:05:26 +00:00
: true
const nameMatched1 = e . name === i . name
const parentMatched = hasGroupOrParent ? e . parent === i.name : true
const nameMatched = hasGroupOrParent ? parentMatched : nameMatched1
const matched = groupMatched && subMatched && nameMatched
return matched
} )
}
2021-02-10 12:29:21 +00:00
/** Process an incoming Interaction */
2021-03-30 09:51:29 +00:00
private async _process ( interaction : Interaction ) : Promise < void > {
2020-12-10 09:10:00 +00:00
if ( ! this . enabled ) return
2021-02-10 12:29:21 +00:00
if (
interaction . type !== InteractionType . APPLICATION_COMMAND ||
interaction . data === undefined
)
return
2020-12-12 12:27:35 +00:00
2020-12-16 13:05:26 +00:00
const cmd = this . _getCommand ( interaction )
2021-01-01 05:55:23 +00:00
if ( cmd ? . group !== undefined )
interaction . data . options = interaction . data . options [ 0 ] . options ? ? [ ]
if ( cmd ? . parent !== undefined )
interaction . data . options = interaction . data . options [ 0 ] . options ? ? [ ]
2020-12-10 09:10:00 +00:00
if ( cmd === undefined ) return
2021-03-30 09:51:29 +00:00
await this . emit ( 'interaction' , interaction )
try { await cmd . handler ( interaction ) } catch ( e ) {
await this . emit ( 'interactionError' , e )
}
2020-12-10 09:10:00 +00:00
}
2020-12-22 06:58:45 +00:00
2021-03-14 09:42:05 +00:00
/** Verify HTTP based Interaction */
2020-12-22 06:58:45 +00:00
async verifyKey (
2021-03-14 08:50:15 +00:00
rawBody : string | Uint8Array ,
signature : string | Uint8Array ,
timestamp : string | Uint8Array
2020-12-22 06:58:45 +00:00
) : Promise < boolean > {
if ( this . publicKey === undefined )
throw new Error ( 'Public Key is not present' )
2021-03-14 08:50:15 +00:00
const fullBody = new Uint8Array ( [
. . . ( typeof timestamp === 'string'
? encoder . encode ( timestamp )
: timestamp ) ,
. . . ( typeof rawBody === 'string' ? encoder . encode ( rawBody ) : rawBody )
] )
return edverify ( signature , fullBody , this . publicKey ) . catch ( ( ) = > false )
2020-12-22 06:58:45 +00:00
}
2021-03-14 10:16:44 +00:00
/** Verify [Deno Std HTTP Server Request](https://deno.land/std/http/server.ts) and return Interaction. **Data present in Interaction returned by this method is very different from actual typings as there is no real `Client` behind the scenes to cache things.** */
2021-03-14 09:42:05 +00:00
async verifyServerRequest ( req : {
headers : Headers
method : string
2021-03-29 12:30:40 +00:00
body : Deno.Reader | Uint8Array ,
2021-03-14 09:42:05 +00:00
respond : ( options : {
status? : number
2021-03-14 10:16:44 +00:00
headers? : Headers
2021-03-29 12:30:40 +00:00
body? : string | Uint8Array | FormData
2021-03-14 09:42:05 +00:00
} ) = > Promise < void >
2021-03-14 10:16:44 +00:00
} ) : Promise < false | Interaction > {
2021-03-14 09:42:05 +00:00
if ( req . method . toLowerCase ( ) !== 'post' ) return false
const signature = req . headers . get ( 'x-signature-ed25519' )
const timestamp = req . headers . get ( 'x-signature-timestamp' )
if ( signature === null || timestamp === null ) return false
2021-03-29 12:30:40 +00:00
const rawbody = req . body instanceof Uint8Array ? req.body : await Deno . readAll ( req . body )
2021-03-14 09:42:05 +00:00
const verify = await this . verifyKey ( rawbody , signature , timestamp )
if ( ! verify ) return false
try {
const payload : InteractionPayload = JSON . parse ( decoder . decode ( rawbody ) )
2021-03-14 10:16:44 +00:00
// 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 : { }
}
} )
2021-03-29 12:30:40 +00:00
res . _httpRespond = async ( d : InteractionResponsePayload | FormData ) = >
2021-03-14 10:16:44 +00:00
await req . respond ( {
status : 200 ,
headers : new Headers ( {
2021-03-29 12:30:40 +00:00
'content-type' : d instanceof FormData ? 'multipart/form-data' : 'application/json'
2021-03-14 10:16:44 +00:00
} ) ,
2021-03-29 12:30:40 +00:00
body : d instanceof FormData ? d : JSON.stringify ( d )
2021-03-14 10:16:44 +00:00
} )
2021-03-14 09:42:05 +00:00
return res
} catch ( e ) {
return false
}
}
2021-03-29 12:30:40 +00:00
/** Verify FetchEvent (for Service Worker usage) and return Interaction if valid */
async verifyFetchEvent ( { request : req , respondWith } : { respondWith : CallableFunction , request : Request } ) : Promise < false | Interaction > {
if ( req . bodyUsed === true ) throw new Error ( 'Request Body already used' )
if ( req . body === null ) return false
const body = ( await req . body . getReader ( ) . read ( ) ) . value
if ( body === undefined ) return false
return await this . verifyServerRequest ( {
headers : req.headers ,
body ,
method : req.method ,
respond : async ( options ) = > {
await respondWith ( new Response ( options . body , {
headers : options.headers ,
status : options.status ,
} ) )
} ,
} )
}
2021-02-01 08:37:54 +00:00
async verifyOpineRequest ( req : any ) : Promise < boolean > {
2020-12-22 06:58:45 +00:00
const signature = req . headers . get ( 'x-signature-ed25519' )
const timestamp = req . headers . get ( 'x-signature-timestamp' )
const contentLength = req . headers . get ( 'content-length' )
if ( signature === null || timestamp === null || contentLength === null )
return false
const body = new Uint8Array ( parseInt ( contentLength ) )
await req . body . read ( body )
const verified = await this . verifyKey ( body , signature , timestamp )
if ( ! verified ) return false
return true
}
/** Middleware to verify request in Opine framework. */
async verifyOpineMiddleware (
2021-02-01 08:37:54 +00:00
req : any ,
res : any ,
2020-12-22 06:58:45 +00:00
next : CallableFunction
) : Promise < any > {
const verified = await this . verifyOpineRequest ( req )
if ( ! verified ) return res . setStatus ( 401 ) . end ( )
await next ( )
return true
}
// TODO: create verifyOakMiddleware too
/** Method to verify Request from Oak server "Context". */
2021-02-01 08:37:54 +00:00
async verifyOakRequest ( ctx : any ) : Promise < any > {
2020-12-22 06:58:45 +00:00
const signature = ctx . request . headers . get ( 'x-signature-ed25519' )
const timestamp = ctx . request . headers . get ( 'x-signature-timestamp' )
const contentLength = ctx . request . headers . get ( 'content-length' )
if (
signature === null ||
timestamp === null ||
contentLength === null ||
ctx . request . hasBody !== true
) {
return false
}
const body = await ctx . request . body ( ) . value
2021-02-01 08:37:54 +00:00
const verified = await this . verifyKey ( body , signature , timestamp )
2020-12-22 06:58:45 +00:00
if ( ! verified ) return false
return true
}
2020-12-10 06:55:52 +00:00
}
2021-03-30 09:51:29 +00:00
/** 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
} )
}
}