feat: middlewares and http-based slash util

This commit is contained in:
DjDeveloperr 2020-12-22 12:28:45 +05:30
parent da0bfc12c7
commit 03ea5df551
3 changed files with 154 additions and 22 deletions

View file

@ -10,15 +10,12 @@
<br>
- Lightweight and easy to use.
- Built-in Command Framework,
- Easily build Commands on the fly.
- Completely Customizable.
- Complete Object-Oriented approach.
- 100% Discord API Coverage.
- Customizable caching.
- Built in support for Redis.
- Write Custom Cache Adapters.
- Complete TypeScript support.
- Slash Commands supported.
- Built-in Commands framework.
- Customizable Caching, with Redis support.
- Use `@decorators` to easily make things!
- Made with ❤️ TypeScript.
## Table of Contents
@ -102,13 +99,14 @@ client.connect('super secret token comes here', Intents.All)
```
Or with Decorators!
```ts
import {
Client,
event,
Intents,
command,
CommandContext,
CommandContext
} from 'https://deno.land/x/harmony/mod.ts'
class MyClient extends CommandClient {
@ -141,6 +139,7 @@ Documentation is available for `main` (branch) and `stable` (release).
- [Main](https://doc.deno.land/https/raw.githubusercontent.com/harmony-org/harmony/main/mod.ts)
- [Stable](https://doc.deno.land/https/deno.land/x/harmony/mod.ts)
- [Guide](https://harmony-org.github.io)
## Found a bug or want support? Join our discord server!

View file

@ -288,7 +288,7 @@ export function groupslash(
name?: string,
guild?: string
) {
return function (client: Client | SlashModule, prop: 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') {

View file

@ -11,6 +11,14 @@ import {
import { Collection } from '../utils/collection.ts'
import { Client } from './client.ts'
import { RESTManager } from './rest.ts'
import { SlashModule } from './slashModule.ts'
import { verify as edverify } from 'https://deno.land/x/ed25519/mod.ts'
import { Buffer } from 'https://deno.land/std@0.80.0/node/buffer.ts'
import {
Request as ORequest,
Response as OResponse
} from 'https://deno.land/x/opine@1.0.0/src/types.ts'
import { Context } from 'https://deno.land/x/oak@v6.4.0/mod.ts'
export class SlashCommand {
slash: SlashCommandsManager
@ -37,6 +45,21 @@ export class SlashCommand {
async edit(data: SlashCommandPartial): Promise<void> {
await this.slash.edit(this.id, data, this._guild)
}
/** 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
}
}
export interface CreateOptions {
@ -58,7 +81,7 @@ function createSlashOption(
? undefined
: data.description ?? 'No description.',
options: data.options?.map((e) =>
typeof e === 'function' ? e(SlashOptionCallableBuilder) : e
typeof e === 'function' ? e(SlashOption) : e
),
choices:
data.choices === undefined
@ -70,7 +93,7 @@ function createSlashOption(
}
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class SlashOptionCallableBuilder {
export class SlashOption {
static string(data: CreateOptions): SlashCommandOption {
return createSlashOption(SlashCommandOptionType.STRING, data)
}
@ -104,9 +127,7 @@ export class SlashOptionCallableBuilder {
}
}
export type SlashOptionCallable = (
o: typeof SlashOptionCallableBuilder
) => SlashCommandOption
export type SlashOptionCallable = (o: typeof SlashOption) => SlashCommandOption
export type SlashBuilderOptionsData =
| Array<SlashCommandOption | SlashOptionCallable>
@ -125,12 +146,10 @@ function buildOptionsArray(
options: SlashBuilderOptionsData
): SlashCommandOption[] {
return Array.isArray(options)
? options.map((op) =>
typeof op === 'function' ? op(SlashOptionCallableBuilder) : op
)
? options.map((op) => (typeof op === 'function' ? op(SlashOption) : op))
: Object.entries(options).map((entry) =>
typeof entry[1] === 'function'
? entry[1](SlashOptionCallableBuilder)
? entry[1](SlashOption)
: Object.assign(entry[1], { name: entry[0] })
)
}
@ -163,7 +182,7 @@ export class SlashBuilder {
option(option: SlashOptionCallable | SlashCommandOption): SlashBuilder {
if (this.data.options === undefined) this.data.options = []
this.data.options.push(
typeof option === 'function' ? option(SlashOptionCallableBuilder) : option
typeof option === 'function' ? option(SlashOption) : option
)
return this
}
@ -312,6 +331,7 @@ export interface SlashOptions {
enabled?: boolean
token?: string
rest?: RESTManager
publicKey?: string
}
export class SlashClient {
@ -322,6 +342,18 @@ export class SlashClient {
commands: SlashCommandsManager
handlers: SlashCommandHandler[] = []
rest: RESTManager
modules: SlashModule[] = []
publicKey?: string
_decoratedSlash?: Array<{
name: string
guild?: string
parent?: string
group?: string
handler: (interaction: Interaction) => any
}>
_decoratedSlashModules?: SlashModule[]
constructor(options: SlashOptions) {
let id = options.id
@ -332,6 +364,7 @@ export class SlashClient {
this.client = options.client
this.token = options.token
this.commands = new SlashCommandsManager(this)
this.publicKey = options.publicKey
if (options !== undefined) {
this.enabled = options.enabled ?? true
@ -343,6 +376,24 @@ export class SlashClient {
})
}
if (this.client?._decoratedSlashModules !== undefined) {
this.client._decoratedSlashModules.forEach((e) => {
this.modules.push(e)
})
}
if (this._decoratedSlash !== undefined) {
this._decoratedSlash.forEach((e) => {
this.handlers.push(e)
})
}
if (this._decoratedSlashModules !== undefined) {
this._decoratedSlashModules.forEach((e) => {
this.modules.push(e)
})
}
this.rest =
options.client === undefined
? options.rest === undefined
@ -367,8 +418,16 @@ export class SlashClient {
return this
}
getHandlers(): SlashCommandHandler[] {
let res = this.handlers
for (const mod of this.modules) {
res = [...res, ...mod.commands]
}
return res
}
private _getCommand(i: Interaction): SlashCommandHandler | undefined {
return this.handlers.find((e) => {
return this.getHandlers().find((e) => {
const hasGroupOrParent = e.group !== undefined || e.parent !== undefined
const groupMatched =
e.group !== undefined && e.parent !== undefined
@ -401,4 +460,78 @@ export class SlashClient {
cmd.handler(interaction)
}
async verifyKey(
rawBody: string | Uint8Array | Buffer,
signature: string,
timestamp: string
): Promise<boolean> {
if (this.publicKey === undefined)
throw new Error('Public Key is not present')
return edverify(
signature,
Buffer.concat([
Buffer.from(timestamp, 'utf-8'),
Buffer.from(
rawBody instanceof Uint8Array
? new TextDecoder().decode(rawBody)
: rawBody
)
]),
this.publicKey
).catch(() => false)
}
async verifyOpineRequest(req: ORequest): Promise<boolean> {
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(
req: ORequest,
res: OResponse,
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". */
async verifyOakRequest(ctx: Context): Promise<any> {
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
const verified = await this.verifyKey(body as any, signature, timestamp)
if (!verified) return false
return true
}
}