Merge pull request #67 from DjDeveloperr/slash
feat: HTTP based Slash Commands util
This commit is contained in:
		
						commit
						d46cd0cbe5
					
				
					 10 changed files with 461 additions and 65 deletions
				
			
		
							
								
								
									
										19
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										19
									
								
								README.md
									
										
									
									
									
								
							|  | @ -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. | ||||
| - Complete Object-Oriented approach. | ||||
| - 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! | ||||
| 
 | ||||
|  |  | |||
|  | @ -247,6 +247,7 @@ export function event(name?: string) { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| /** Decorator to create a Slash Command handler */ | ||||
| export function slash(name?: string, guild?: string) { | ||||
|   return function (client: Client | SlashModule, prop: string) { | ||||
|     if (client._decoratedSlash === undefined) client._decoratedSlash = [] | ||||
|  | @ -262,6 +263,7 @@ export function slash(name?: string, guild?: string) { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| /** Decorator to create a Sub-Slash Command handler */ | ||||
| export function subslash(parent: string, name?: string, guild?: string) { | ||||
|   return function (client: Client | SlashModule, prop: string) { | ||||
|     if (client._decoratedSlash === undefined) client._decoratedSlash = [] | ||||
|  | @ -279,13 +281,14 @@ export function subslash(parent: string, name?: string, guild?: string) { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| /** Decorator to create a Grouped Slash Command handler */ | ||||
| export function groupslash( | ||||
|   parent: string, | ||||
|   group: string, | ||||
|   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') { | ||||
|  | @ -303,6 +306,7 @@ export function groupslash( | |||
|   } | ||||
| } | ||||
| 
 | ||||
| /** Decorator to add a Slash Module to Client */ | ||||
| export function slashModule() { | ||||
|   return function (client: Client, prop: string) { | ||||
|     if (client._decoratedSlashModules === undefined) | ||||
|  |  | |||
|  | @ -97,6 +97,7 @@ export interface RESTOptions { | |||
|   token?: string | ||||
|   headers?: { [name: string]: string | undefined } | ||||
|   canary?: boolean | ||||
|   version?: 6 | 7 | 8 | ||||
| } | ||||
| 
 | ||||
| export class RESTManager { | ||||
|  | @ -111,6 +112,7 @@ export class RESTManager { | |||
|   constructor(client?: RESTOptions) { | ||||
|     this.client = client | ||||
|     this.api = builder(this) | ||||
|     if (client?.version !== undefined) this.version = client.version | ||||
|     // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||
|     this.handleRateLimits() | ||||
|   } | ||||
|  | @ -408,6 +410,7 @@ export class RESTManager { | |||
|           const query = | ||||
|             method === 'get' && body !== undefined | ||||
|               ? Object.entries(body as any) | ||||
|                   .filter(([k, v]) => v !== undefined) | ||||
|                   .map( | ||||
|                     ([key, value]) => | ||||
|                       `${encodeURIComponent(key)}=${encodeURIComponent( | ||||
|  |  | |||
|  | @ -1,20 +1,24 @@ | |||
| import { Guild } from '../structures/guild.ts' | ||||
| import { Interaction } from '../structures/slash.ts' | ||||
| import { | ||||
|   APPLICATION_COMMAND, | ||||
|   APPLICATION_COMMANDS, | ||||
|   APPLICATION_GUILD_COMMAND, | ||||
|   APPLICATION_GUILD_COMMANDS | ||||
| } from '../types/endpoint.ts' | ||||
| import { | ||||
|   InteractionType, | ||||
|   SlashCommandChoice, | ||||
|   SlashCommandOption, | ||||
|   SlashCommandOptionType, | ||||
|   SlashCommandPartial, | ||||
|   SlashCommandPayload | ||||
| } from '../types/slash.ts' | ||||
| 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 | ||||
|  | @ -41,6 +45,158 @@ 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 { | ||||
|   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) => | ||||
|       typeof e === 'function' ? e(SlashOption) : e | ||||
|     ), | ||||
|     choices: | ||||
|       data.choices === undefined | ||||
|         ? undefined | ||||
|         : data.choices.map((e) => | ||||
|             typeof e === 'string' ? { name: e, value: e } : e | ||||
|           ) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // eslint-disable-next-line @typescript-eslint/no-extraneous-class
 | ||||
| export class SlashOption { | ||||
|   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) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export type SlashOptionCallable = (o: typeof SlashOption) => SlashCommandOption | ||||
| 
 | ||||
| export type SlashBuilderOptionsData = | ||||
|   | Array<SlashCommandOption | SlashOptionCallable> | ||||
|   | { | ||||
|       [name: string]: | ||||
|         | { | ||||
|             description: string | ||||
|             type: SlashCommandOptionType | ||||
|             options?: SlashCommandOption[] | ||||
|             choices?: SlashCommandChoice[] | ||||
|           } | ||||
|         | SlashOptionCallable | ||||
|     } | ||||
| 
 | ||||
| function buildOptionsArray( | ||||
|   options: SlashBuilderOptionsData | ||||
| ): SlashCommandOption[] { | ||||
|   return Array.isArray(options) | ||||
|     ? options.map((op) => (typeof op === 'function' ? op(SlashOption) : op)) | ||||
|     : Object.entries(options).map((entry) => | ||||
|         typeof entry[1] === 'function' | ||||
|           ? entry[1](SlashOption) | ||||
|           : Object.assign(entry[1], { name: entry[0] }) | ||||
|       ) | ||||
| } | ||||
| 
 | ||||
| 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( | ||||
|       typeof option === 'function' ? option(SlashOption) : option | ||||
|     ) | ||||
|     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 | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class SlashCommandsManager { | ||||
|  | @ -58,9 +214,9 @@ export class SlashCommandsManager { | |||
|   async all(): Promise<Collection<string, SlashCommand>> { | ||||
|     const col = new Collection<string, SlashCommand>() | ||||
| 
 | ||||
|     const res = (await this.rest.get( | ||||
|       APPLICATION_COMMANDS(this.slash.getID()) | ||||
|     )) as SlashCommandPayload[] | ||||
|     const res = (await this.rest.api.applications[ | ||||
|       this.slash.getID() | ||||
|     ].commands.get()) as SlashCommandPayload[] | ||||
|     if (!Array.isArray(res)) return col | ||||
| 
 | ||||
|     for (const raw of res) { | ||||
|  | @ -77,12 +233,9 @@ export class SlashCommandsManager { | |||
|   ): Promise<Collection<string, SlashCommand>> { | ||||
|     const col = new Collection<string, SlashCommand>() | ||||
| 
 | ||||
|     const res = (await this.rest.get( | ||||
|       APPLICATION_GUILD_COMMANDS( | ||||
|         this.slash.getID(), | ||||
|         typeof guild === 'string' ? guild : guild.id | ||||
|       ) | ||||
|     )) as SlashCommandPayload[] | ||||
|     const res = (await this.rest.api.applications[this.slash.getID()].guilds[ | ||||
|       typeof guild === 'string' ? guild : guild.id | ||||
|     ].commands.get()) as SlashCommandPayload[] | ||||
|     if (!Array.isArray(res)) return col | ||||
| 
 | ||||
|     for (const raw of res) { | ||||
|  | @ -99,15 +252,14 @@ export class SlashCommandsManager { | |||
|     data: SlashCommandPartial, | ||||
|     guild?: Guild | string | ||||
|   ): Promise<SlashCommand> { | ||||
|     const payload = await this.rest.post( | ||||
|     const route = | ||||
|       guild === undefined | ||||
|         ? APPLICATION_COMMANDS(this.slash.getID()) | ||||
|         : APPLICATION_GUILD_COMMANDS( | ||||
|             this.slash.getID(), | ||||
|         ? this.rest.api.applications[this.slash.getID()].commands | ||||
|         : this.rest.api.applications[this.slash.getID()].guilds[ | ||||
|             typeof guild === 'string' ? guild : guild.id | ||||
|           ), | ||||
|       data | ||||
|     ) | ||||
|           ].commands | ||||
| 
 | ||||
|     const payload = await route.post(data) | ||||
| 
 | ||||
|     const cmd = new SlashCommand(this, payload) | ||||
|     cmd._guild = | ||||
|  | @ -122,16 +274,14 @@ export class SlashCommandsManager { | |||
|     data: SlashCommandPartial, | ||||
|     guild?: Guild | string | ||||
|   ): Promise<SlashCommandsManager> { | ||||
|     await this.rest.patch( | ||||
|     const route = | ||||
|       guild === undefined | ||||
|         ? APPLICATION_COMMAND(this.slash.getID(), id) | ||||
|         : APPLICATION_GUILD_COMMAND( | ||||
|             this.slash.getID(), | ||||
|             typeof guild === 'string' ? guild : guild.id, | ||||
|             id | ||||
|           ), | ||||
|       data | ||||
|     ) | ||||
|         ? this.rest.api.applications[this.slash.getID()].commands[id] | ||||
|         : this.rest.api.applications[this.slash.getID()].guilds[ | ||||
|             typeof guild === 'string' ? guild : guild.id | ||||
|           ].commands[id] | ||||
| 
 | ||||
|     await route.patch(data) | ||||
|     return this | ||||
|   } | ||||
| 
 | ||||
|  | @ -140,29 +290,28 @@ export class SlashCommandsManager { | |||
|     id: string, | ||||
|     guild?: Guild | string | ||||
|   ): Promise<SlashCommandsManager> { | ||||
|     await this.rest.delete( | ||||
|     const route = | ||||
|       guild === undefined | ||||
|         ? APPLICATION_COMMAND(this.slash.getID(), id) | ||||
|         : APPLICATION_GUILD_COMMAND( | ||||
|             this.slash.getID(), | ||||
|             typeof guild === 'string' ? guild : guild.id, | ||||
|             id | ||||
|           ) | ||||
|     ) | ||||
|         ? this.rest.api.applications[this.slash.getID()].commands[id] | ||||
|         : this.rest.api.applications[this.slash.getID()].guilds[ | ||||
|             typeof guild === 'string' ? guild : guild.id | ||||
|           ].commands[id] | ||||
| 
 | ||||
|     await route.delete() | ||||
|     return this | ||||
|   } | ||||
| 
 | ||||
|   /** Get a Slash Command (global or Guild) */ | ||||
|   async get(id: string, guild?: Guild | string): Promise<SlashCommand> { | ||||
|     const data = await this.rest.get( | ||||
|     const route = | ||||
|       guild === undefined | ||||
|         ? APPLICATION_COMMAND(this.slash.getID(), id) | ||||
|         : APPLICATION_GUILD_COMMAND( | ||||
|             this.slash.getID(), | ||||
|             typeof guild === 'string' ? guild : guild.id, | ||||
|             id | ||||
|           ) | ||||
|     ) | ||||
|         ? this.rest.api.applications[this.slash.getID()].commands[id] | ||||
|         : this.rest.api.applications[this.slash.getID()].guilds[ | ||||
|             typeof guild === 'string' ? guild : guild.id | ||||
|           ].commands[id] | ||||
| 
 | ||||
|     const data = await route.get() | ||||
| 
 | ||||
|     return new SlashCommand(this, data) | ||||
|   } | ||||
| } | ||||
|  | @ -182,6 +331,7 @@ export interface SlashOptions { | |||
|   enabled?: boolean | ||||
|   token?: string | ||||
|   rest?: RESTManager | ||||
|   publicKey?: string | ||||
| } | ||||
| 
 | ||||
| export class SlashClient { | ||||
|  | @ -192,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 | ||||
|  | @ -202,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 | ||||
|  | @ -213,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 | ||||
|  | @ -237,8 +418,28 @@ export class SlashClient { | |||
|     return this | ||||
|   } | ||||
| 
 | ||||
|   loadModule(module: SlashModule): SlashClient { | ||||
|     this.modules.push(module) | ||||
|     return this | ||||
|   } | ||||
| 
 | ||||
|   getHandlers(): SlashCommandHandler[] { | ||||
|     let res = this.handlers | ||||
|     for (const mod of this.modules) { | ||||
|       if (mod === undefined) continue | ||||
|       res = [ | ||||
|         ...res, | ||||
|         ...mod.commands.map((cmd) => { | ||||
|           cmd.handler = cmd.handler.bind(mod) | ||||
|           return cmd | ||||
|         }) | ||||
|       ] | ||||
|     } | ||||
|     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 | ||||
|  | @ -271,4 +472,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 | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -46,6 +46,10 @@ export class Message extends Base { | |||
|   flags?: number | ||||
|   stickers?: MessageSticker[] | ||||
| 
 | ||||
|   get createdAt(): Date { | ||||
|     return new Date(this.timestamp) | ||||
|   } | ||||
| 
 | ||||
|   constructor( | ||||
|     client: Client, | ||||
|     data: MessagePayload, | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ import { Client } from '../models/client.ts' | |||
| import { | ||||
|   GuildTextChannelPayload, | ||||
|   MessageOption, | ||||
|   MessagePayload, | ||||
|   MessageReference, | ||||
|   ModifyGuildTextChannelOption, | ||||
|   ModifyGuildTextChannelPayload, | ||||
|  | @ -14,6 +15,7 @@ import { | |||
|   CHANNEL_MESSAGE, | ||||
|   CHANNEL_MESSAGES | ||||
| } from '../types/endpoint.ts' | ||||
| import { Collection } from '../utils/collection.ts' | ||||
| import { Channel } from './channel.ts' | ||||
| import { Embed } from './embed.ts' | ||||
| import { Guild } from './guild.ts' | ||||
|  | @ -125,6 +127,48 @@ export class TextChannel extends Channel { | |||
|     await res.mentions.fromPayload(newMsg) | ||||
|     return res | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Fetch Messages of a Channel | ||||
|    * @param options Options to configure fetching Messages | ||||
|    */ | ||||
|   async fetchMessages(options?: { | ||||
|     limit?: number | ||||
|     around?: Message | string | ||||
|     before?: Message | string | ||||
|     after?: Message | string | ||||
|   }): Promise<Collection<string, Message>> { | ||||
|     const res = new Collection<string, Message>() | ||||
|     const raws = (await this.client.rest.api.channels[this.id].messages.get({ | ||||
|       limit: options?.limit ?? 50, | ||||
|       around: | ||||
|         options?.around === undefined | ||||
|           ? undefined | ||||
|           : typeof options.around === 'string' | ||||
|           ? options.around | ||||
|           : options.around.id, | ||||
|       before: | ||||
|         options?.before === undefined | ||||
|           ? undefined | ||||
|           : typeof options.before === 'string' | ||||
|           ? options.before | ||||
|           : options.before.id, | ||||
|       after: | ||||
|         options?.after === undefined | ||||
|           ? undefined | ||||
|           : typeof options.after === 'string' | ||||
|           ? options.after | ||||
|           : options.after.id | ||||
|     })) as MessagePayload[] | ||||
| 
 | ||||
|     for (const raw of raws) { | ||||
|       await this.messages.set(raw.id, raw) | ||||
|       const msg = ((await this.messages.get(raw.id)) as unknown) as Message | ||||
|       res.set(msg.id, msg) | ||||
|     } | ||||
| 
 | ||||
|     return res | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class GuildTextChannel extends TextChannel { | ||||
|  | @ -186,4 +230,40 @@ export class GuildTextChannel extends TextChannel { | |||
| 
 | ||||
|     return new GuildTextChannel(this.client, resp, this.guild) | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Bulk Delete Messages in a Guild Text Channel | ||||
|    * @param messages Messages to delete. Can be a number, or Array of Message or IDs | ||||
|    */ | ||||
|   async bulkDelete( | ||||
|     messages: Array<Message | string> | number | ||||
|   ): Promise<GuildTextChannel> { | ||||
|     let ids: string[] = [] | ||||
| 
 | ||||
|     if (Array.isArray(messages)) | ||||
|       ids = messages.map((e) => (typeof e === 'string' ? e : e.id)) | ||||
|     else { | ||||
|       let list = await this.messages.array() | ||||
|       if (list.length < messages) list = (await this.fetchMessages()).array() | ||||
|       ids = list | ||||
|         .sort((b, a) => a.createdAt.getTime() - b.createdAt.getTime()) | ||||
|         .filter((e, i) => i < messages) | ||||
|         .filter( | ||||
|           (e) => | ||||
|             new Date().getTime() - e.createdAt.getTime() <= | ||||
|             1000 * 60 * 60 * 24 * 14 | ||||
|         ) | ||||
|         .map((e) => e.id) | ||||
|     } | ||||
| 
 | ||||
|     ids = [...new Set(ids)] | ||||
|     if (ids.length < 2 || ids.length > 100) | ||||
|       throw new Error('bulkDelete can only delete messages in range 2-100') | ||||
| 
 | ||||
|     await this.client.rest.api.channels[this.id].messages['bulk-delete'].post({ | ||||
|       messages: ids | ||||
|     }) | ||||
| 
 | ||||
|     return this | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -7,7 +7,8 @@ import { | |||
|   groupslash, | ||||
|   CommandContext, | ||||
|   Extension, | ||||
|   Collection | ||||
|   Collection, | ||||
|   GuildTextChannel | ||||
| } from '../../mod.ts' | ||||
| import { LL_IP, LL_PASS, LL_PORT, TOKEN } from './config.ts' | ||||
| import { | ||||
|  | @ -69,6 +70,17 @@ class MyClient extends CommandClient { | |||
|     d.respond({ content: 'sub-cmd-group worked' }) | ||||
|   } | ||||
| 
 | ||||
|   @command() | ||||
|   rmrf(ctx: CommandContext): any { | ||||
|     if (ctx.author.id !== '422957901716652033') return | ||||
|     ;((ctx.channel as any) as GuildTextChannel) | ||||
|       .bulkDelete(3) | ||||
|       .then((chan) => { | ||||
|         ctx.channel.send(`Bulk deleted 2 in ${chan}`) | ||||
|       }) | ||||
|       .catch((e) => ctx.channel.send(`${e.message}`)) | ||||
|   } | ||||
| 
 | ||||
|   @slash() | ||||
|   run(d: Interaction): void { | ||||
|     console.log(d.name) | ||||
|  | @ -205,6 +217,10 @@ class VCExtension extends Extension { | |||
| 
 | ||||
| const client = new MyClient() | ||||
| 
 | ||||
| client.on('raw', (e, d) => { | ||||
|   if (e === 'GUILD_MEMBER_ADD' || e === 'GUILD_MEMBER_UPDATE') console.log(e, d) | ||||
| }) | ||||
| 
 | ||||
| client.extensions.load(VCExtension) | ||||
| 
 | ||||
| client.connect(TOKEN, Intents.None) | ||||
| client.connect(TOKEN, Intents.All) | ||||
|  |  | |||
|  | @ -1,6 +1,16 @@ | |||
| import { SlashClient } from '../models/slashClient.ts' | ||||
| import { SlashCommandPartial } from '../types/slash.ts' | ||||
| import { TOKEN } from './config.ts' | ||||
| 
 | ||||
| const slash = new SlashClient({ token: TOKEN }) | ||||
| export const slash = new SlashClient({ token: TOKEN }) | ||||
| 
 | ||||
| slash.commands.all().then(console.log) | ||||
| // Cmd objects come here
 | ||||
| const commands: SlashCommandPartial[] = [] | ||||
| 
 | ||||
| console.log('Creating...') | ||||
| commands.forEach((cmd) => { | ||||
|   slash.commands | ||||
|     .create(cmd, '!! Your testing guild ID comes here !!') | ||||
|     .then((c) => console.log(`Created command ${c.name}!`)) | ||||
|     .catch((e) => `Failed to create ${cmd.name} - ${e.message}`) | ||||
| }) | ||||
|  |  | |||
|  | @ -62,6 +62,7 @@ export interface MemberPayload { | |||
|   premium_since?: string | ||||
|   deaf: boolean | ||||
|   mute: boolean | ||||
|   pending?: boolean | ||||
| } | ||||
| 
 | ||||
| export enum MessageNotification { | ||||
|  | @ -113,6 +114,9 @@ export type GuildFeatures = | |||
|   | 'FEATURABLE' | ||||
|   | 'ANIMATED_ICON' | ||||
|   | 'BANNER' | ||||
|   | 'WELCOME_SCREEN_ENABLED' | ||||
|   | 'MEMBER_VERIFICATION_GATE_ENABLED' | ||||
|   | 'PREVIEW_ENABLED' | ||||
| 
 | ||||
| export enum IntegrationExpireBehavior { | ||||
|   REMOVE_ROLE = 0, | ||||
|  |  | |||
|  | @ -50,7 +50,7 @@ export interface SlashCommandChoice { | |||
|   /** (Display) name of the Choice */ | ||||
|   name: string | ||||
|   /** Actual value to be sent in Interaction */ | ||||
|   value: string | ||||
|   value: any | ||||
| } | ||||
| 
 | ||||
| export enum SlashCommandOptionType { | ||||
|  | @ -66,7 +66,8 @@ export enum SlashCommandOptionType { | |||
| 
 | ||||
| export interface SlashCommandOption { | ||||
|   name: string | ||||
|   description: string | ||||
|   /** Description not required in Sub-Command or Sub-Command-Group */ | ||||
|   description?: string | ||||
|   type: SlashCommandOptionType | ||||
|   required?: boolean | ||||
|   default?: boolean | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue