Github online editor FTW
This commit is contained in:
		
						commit
						a14104ad14
					
				
					 29 changed files with 759 additions and 159 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! | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										13
									
								
								deps.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								deps.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| export { EventEmitter } from 'https://deno.land/std@0.82.0/node/events.ts' | ||||
| export { unzlib } from 'https://deno.land/x/denoflate@1.1/mod.ts' | ||||
| export { fetchAuto } from 'https://raw.githubusercontent.com/DjDeveloperr/fetch-base64/main/mod.ts' | ||||
| export { parse } from 'https://deno.land/x/mutil@0.1.2/mod.ts' | ||||
| export { connect } from 'https://deno.land/x/redis@v0.14.1/mod.ts' | ||||
| export type { | ||||
|   Redis, | ||||
|   RedisConnectOptions | ||||
| } from 'https://deno.land/x/redis@v0.14.1/mod.ts' | ||||
| export { | ||||
|   Manager, | ||||
|   Player | ||||
| } from 'https://raw.githubusercontent.com/Lavaclient/lavadeno/master/mod.ts' | ||||
							
								
								
									
										3
									
								
								mod.ts
									
										
									
									
									
								
							
							
						
						
									
										3
									
								
								mod.ts
									
										
									
									
									
								
							|  | @ -1,5 +1,4 @@ | |||
| export { GatewayIntents } from './src/types/gateway.ts' | ||||
| export { default as EventEmitter } from 'https://deno.land/std@0.74.0/node/events.ts' | ||||
| export { Base } from './src/structures/base.ts' | ||||
| export { Gateway } from './src/gateway/index.ts' | ||||
| export type { ClientEvents } from './src/gateway/handlers/index.ts' | ||||
|  | @ -66,7 +65,7 @@ export { | |||
|   ActivityTypes | ||||
| } from './src/structures/presence.ts' | ||||
| export { Role } from './src/structures/role.ts' | ||||
| export { Snowflake } from './src/structures/snowflake.ts' | ||||
| export { Snowflake } from './src/utils/snowflake.ts' | ||||
| export { TextChannel, GuildTextChannel } from './src/structures/textChannel.ts' | ||||
| export { MessageReaction } from './src/structures/messageReaction.ts' | ||||
| export { User } from './src/structures/user.ts' | ||||
|  |  | |||
|  | @ -17,23 +17,29 @@ export const guildEmojiUpdate: GatewayEventHandler = async ( | |||
|     const _updated: EmojiPayload[] = [] | ||||
| 
 | ||||
|     for (const raw of d.emojis) { | ||||
|       const has = emojis.get(raw.id) | ||||
|       const emojiID = raw.id !== null ? raw.id : raw.name | ||||
|       const has = emojis.get(emojiID) | ||||
|       if (has === undefined) { | ||||
|         await guild.emojis.set(raw.id, raw) | ||||
|         const emoji = (await guild.emojis.get(raw.id)) as Emoji | ||||
|         await guild.emojis.set(emojiID, raw) | ||||
|         const emoji = (await guild.emojis.get(emojiID)) as Emoji | ||||
|         added.push(emoji) | ||||
|       } else _updated.push(raw) | ||||
|     } | ||||
| 
 | ||||
|     for (const emoji of emojis.values()) { | ||||
|       const find = _updated.find((e) => emoji.id === e.id) | ||||
|       const emojiID = emoji.id !== null ? emoji.id : emoji.name | ||||
|       const find = _updated.find((e) => { | ||||
|         const eID = e.id !== null ? e.id : e.name | ||||
|         return emojiID === eID | ||||
|       }) | ||||
|       if (find === undefined) { | ||||
|         await guild.emojis.delete(emoji.id) | ||||
|         await guild.emojis.delete(emojiID) | ||||
|         deleted.push(emoji) | ||||
|       } else { | ||||
|         const before = (await guild.emojis.get(find.id)) as Emoji | ||||
|         await guild.emojis.set(find.id, find) | ||||
|         const after = (await guild.emojis.get(find.id)) as Emoji | ||||
|         const foundID = find.id !== null ? find.id : find.name | ||||
|         const before = (await guild.emojis.get(foundID)) as Emoji | ||||
|         await guild.emojis.set(foundID, find) | ||||
|         const after = (await guild.emojis.get(foundID)) as Emoji | ||||
|         updated.push({ before, after }) | ||||
|       } | ||||
|     } | ||||
|  |  | |||
|  | @ -29,15 +29,16 @@ export const messageReactionAdd: GatewayEventHandler = async ( | |||
|     } else return | ||||
|   } | ||||
| 
 | ||||
|   let reaction = await message.reactions.get(d.emoji.id) | ||||
|   const emojiID = d.emoji.id !== null ? d.emoji.id : d.emoji.name | ||||
|   let reaction = await message.reactions.get(emojiID) | ||||
|   if (reaction === undefined) { | ||||
|     await message.reactions.set(d.emoji.id, { | ||||
|     await message.reactions.set(emojiID, { | ||||
|       count: 1, | ||||
|       emoji: d.emoji, | ||||
|       me: d.user_id === gateway.client.user?.id | ||||
|     }) | ||||
|     reaction = ((await message.reactions.get( | ||||
|       d.emoji.id | ||||
|       emojiID | ||||
|     )) as unknown) as MessageReaction | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -27,7 +27,8 @@ export const messageReactionRemove: GatewayEventHandler = async ( | |||
|     } else return | ||||
|   } | ||||
| 
 | ||||
|   const reaction = await message.reactions.get(d.emoji.id) | ||||
|   const emojiID = d.emoji.id !== null ? d.emoji.id : d.emoji.name | ||||
|   const reaction = await message.reactions.get(emojiID) | ||||
|   if (reaction === undefined) return | ||||
| 
 | ||||
|   reaction.users.delete(d.user_id) | ||||
|  |  | |||
|  | @ -19,7 +19,8 @@ export const messageReactionRemoveEmoji: GatewayEventHandler = async ( | |||
|     } else return | ||||
|   } | ||||
| 
 | ||||
|   const reaction = await message.reactions.get(d.emoji.id) | ||||
|   const emojiID = d.emoji.id !== null ? d.emoji.id : d.emoji.name | ||||
|   const reaction = await message.reactions.get(emojiID) | ||||
|   if (reaction === undefined) return | ||||
| 
 | ||||
|   await reaction.users.flush() | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { unzlib } from 'https://deno.land/x/denoflate@1.1/mod.ts' | ||||
| import { unzlib, EventEmitter } from '../../deps.ts' | ||||
| import { Client } from '../models/client.ts' | ||||
| import { | ||||
|   DISCORD_GATEWAY_URL, | ||||
|  | @ -18,7 +18,6 @@ import { GatewayCache } from '../managers/gatewayCache.ts' | |||
| import { delay } from '../utils/delay.ts' | ||||
| import { VoiceChannel } from '../structures/guildVoiceChannel.ts' | ||||
| import { Guild } from '../structures/guild.ts' | ||||
| import EventEmitter from 'https://deno.land/std@0.74.0/node/events.ts' | ||||
| 
 | ||||
| export interface RequestMembersOptions { | ||||
|   limit?: number | ||||
|  | @ -177,46 +176,51 @@ export class Gateway extends EventEmitter { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private async onclose(event: CloseEvent): Promise<void> { | ||||
|     if (event.reason === RECONNECT_REASON) return | ||||
|     this.emit('close', event.code, event.reason) | ||||
|     this.debug(`Connection Closed with code: ${event.code}`) | ||||
|   private async onclose({ reason, code }: CloseEvent): Promise<void> { | ||||
|     if (reason === RECONNECT_REASON) return | ||||
|     this.emit('close', code, reason) | ||||
|     this.debug(`Connection Closed with code: ${code}`) | ||||
| 
 | ||||
|     if (event.code === GatewayCloseCodes.UNKNOWN_ERROR) { | ||||
|     switch (code) { | ||||
|       case GatewayCloseCodes.UNKNOWN_ERROR: | ||||
|         this.debug('API has encountered Unknown Error. Reconnecting...') | ||||
|         // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||
|         this.reconnect() | ||||
|     } else if (event.code === GatewayCloseCodes.UNKNOWN_OPCODE) { | ||||
|         break | ||||
|       case GatewayCloseCodes.UNKNOWN_OPCODE: | ||||
|         throw new Error("Unknown OP Code was sent. This shouldn't happen!") | ||||
|     } else if (event.code === GatewayCloseCodes.DECODE_ERROR) { | ||||
|       case GatewayCloseCodes.DECODE_ERROR: | ||||
|         throw new Error("Invalid Payload was sent. This shouldn't happen!") | ||||
|     } else if (event.code === GatewayCloseCodes.NOT_AUTHENTICATED) { | ||||
|       case GatewayCloseCodes.NOT_AUTHENTICATED: | ||||
|         throw new Error('Not Authorized: Payload was sent before Identifying.') | ||||
|     } else if (event.code === GatewayCloseCodes.AUTHENTICATION_FAILED) { | ||||
|       case GatewayCloseCodes.AUTHENTICATION_FAILED: | ||||
|         throw new Error('Invalid Token provided!') | ||||
|     } else if (event.code === GatewayCloseCodes.INVALID_SEQ) { | ||||
|       case GatewayCloseCodes.INVALID_SEQ: | ||||
|         this.debug('Invalid Seq was sent. Reconnecting.') | ||||
|         // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||
|         this.reconnect() | ||||
|     } else if (event.code === GatewayCloseCodes.RATE_LIMITED) { | ||||
|         break | ||||
|       case GatewayCloseCodes.RATE_LIMITED: | ||||
|         throw new Error("You're ratelimited. Calm down.") | ||||
|     } else if (event.code === GatewayCloseCodes.SESSION_TIMED_OUT) { | ||||
|       case GatewayCloseCodes.SESSION_TIMED_OUT: | ||||
|         this.debug('Session Timeout. Reconnecting.') | ||||
|         // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||
|         this.reconnect(true) | ||||
|     } else if (event.code === GatewayCloseCodes.INVALID_SHARD) { | ||||
|         break | ||||
|       case GatewayCloseCodes.INVALID_SHARD: | ||||
|         this.debug('Invalid Shard was sent. Reconnecting.') | ||||
|         // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||
|         this.reconnect() | ||||
|     } else if (event.code === GatewayCloseCodes.SHARDING_REQUIRED) { | ||||
|         break | ||||
|       case GatewayCloseCodes.SHARDING_REQUIRED: | ||||
|         throw new Error("Couldn't connect. Sharding is required!") | ||||
|     } else if (event.code === GatewayCloseCodes.INVALID_API_VERSION) { | ||||
|       case GatewayCloseCodes.INVALID_API_VERSION: | ||||
|         throw new Error("Invalid API Version was used. This shouldn't happen!") | ||||
|     } else if (event.code === GatewayCloseCodes.INVALID_INTENTS) { | ||||
|       case GatewayCloseCodes.INVALID_INTENTS: | ||||
|         throw new Error('Invalid Intents') | ||||
|     } else if (event.code === GatewayCloseCodes.DISALLOWED_INTENTS) { | ||||
|       case GatewayCloseCodes.DISALLOWED_INTENTS: | ||||
|         throw new Error("Given Intents aren't allowed") | ||||
|     } else { | ||||
|       default: | ||||
|         this.debug( | ||||
|           'Unknown Close code, probably connection error. Reconnecting in 5s.' | ||||
|         ) | ||||
|  | @ -226,6 +230,7 @@ export class Gateway extends EventEmitter { | |||
|         } | ||||
|         await delay(5000) | ||||
|         await this.reconnect(true) | ||||
|         break | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ import { EmojiPayload } from '../types/emoji.ts' | |||
| import { CHANNEL, GUILD_EMOJI, GUILD_EMOJIS } from '../types/endpoint.ts' | ||||
| import { BaseChildManager } from './baseChild.ts' | ||||
| import { EmojisManager } from './emojis.ts' | ||||
| import { fetchAuto } from 'https://raw.githubusercontent.com/DjDeveloperr/fetch-base64/main/mod.ts' | ||||
| import { fetchAuto } from '../../deps.ts' | ||||
| 
 | ||||
| export class GuildEmojisManager extends BaseChildManager<EmojiPayload, Emoji> { | ||||
|   guild: Guild | ||||
|  | @ -87,7 +87,8 @@ export class GuildEmojisManager extends BaseChildManager<EmojiPayload, Emoji> { | |||
|   async flush(): Promise<boolean> { | ||||
|     const arr = await this.array() | ||||
|     for (const elem of arr) { | ||||
|       this.parent.delete(elem.id) | ||||
|       const emojiID = elem.id !== null ? elem.id : elem.name | ||||
|       this.parent.delete(emojiID) | ||||
|     } | ||||
|     return true | ||||
|   } | ||||
|  |  | |||
|  | @ -21,7 +21,9 @@ export class MessageReactionsManager extends BaseManager< | |||
|     const raw = await this._get(id) | ||||
|     if (raw === undefined) return | ||||
| 
 | ||||
|     let emoji = await this.client.emojis.get(raw.emoji.id) | ||||
|     const emojiID = raw.emoji.id !== null ? raw.emoji.id : raw.emoji.name | ||||
| 
 | ||||
|     let emoji = await this.client.emojis.get(emojiID) | ||||
|     if (emoji === undefined) emoji = new Emoji(this.client, raw.emoji) | ||||
| 
 | ||||
|     const reaction = new MessageReaction(this.client, raw, this.message, emoji) | ||||
|  | @ -43,7 +45,8 @@ export class MessageReactionsManager extends BaseManager< | |||
| 
 | ||||
|     return await Promise.all( | ||||
|       arr.map(async (raw) => { | ||||
|         let emoji = await this.client.emojis.get(raw.emoji.id) | ||||
|         const emojiID = raw.emoji.id !== null ? raw.emoji.id : raw.emoji.name | ||||
|         let emoji = await this.client.emojis.get(emojiID) | ||||
|         if (emoji === undefined) emoji = new Emoji(this.client, raw.emoji) | ||||
| 
 | ||||
|         return new MessageReaction(this.client, raw, this.message, emoji) | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ import { | |||
|   connect, | ||||
|   Redis, | ||||
|   RedisConnectOptions | ||||
| } from 'https://denopkg.com/keroxp/deno-redis/mod.ts' | ||||
| } from '../../deps.ts' | ||||
| 
 | ||||
| /** | ||||
|  * ICacheAdapter is the interface to be implemented by Cache Adapters for them to be usable with Harmony. | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ import { User } from '../structures/user.ts' | |||
| import { GatewayIntents } from '../types/gateway.ts' | ||||
| import { Gateway } from '../gateway/index.ts' | ||||
| import { RESTManager } from './rest.ts' | ||||
| import EventEmitter from 'https://deno.land/std@0.74.0/node/events.ts' | ||||
| import { EventEmitter } from '../../deps.ts' | ||||
| import { DefaultCacheAdapter, ICacheAdapter } from './cacheAdapter.ts' | ||||
| import { UsersManager } from '../managers/users.ts' | ||||
| import { GuildManager } from '../managers/guilds.ts' | ||||
|  | @ -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) | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ import { User } from '../structures/user.ts' | |||
| import { Collection } from '../utils/collection.ts' | ||||
| import { CommandClient } from './commandClient.ts' | ||||
| import { Extension } from './extensions.ts' | ||||
| import { parse } from 'https://deno.land/x/mutil@0.1.2/mod.ts' | ||||
| import { parse } from '../../deps.ts' | ||||
| 
 | ||||
| export interface CommandContext { | ||||
|   /** The Client object */ | ||||
|  |  | |||
|  | @ -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,6 +1,6 @@ | |||
| import { Collection } from '../utils/collection.ts' | ||||
| import { Client, ClientOptions } from './client.ts' | ||||
| import EventEmitter from 'https://deno.land/std@0.74.0/node/events.ts' | ||||
| import {EventEmitter} from '../../deps.ts' | ||||
| import { RESTManager } from './rest.ts' | ||||
| // import { GATEWAY_BOT } from '../types/endpoint.ts'
 | ||||
| // import { GatewayBotPayload } from '../types/gatewayBot.ts'
 | ||||
|  |  | |||
|  | @ -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(), | ||||
|     const res = (await this.rest.api.applications[this.slash.getID()].guilds[ | ||||
|       typeof guild === 'string' ? guild : guild.id | ||||
|       ) | ||||
|     )) as SlashCommandPayload[] | ||||
|     ].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 | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -18,10 +18,14 @@ export class Emoji extends Base { | |||
|   available?: boolean | ||||
| 
 | ||||
|   get getEmojiString(): string { | ||||
|     if (this.id === null) { | ||||
|       return this.name as string; | ||||
|     } else { | ||||
|       if (this.animated === false) { | ||||
|         return `<:${this.name}:${this.id}>` | ||||
|       } else return `<a:${this.name}:${this.id}>` | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   toString(): string { | ||||
|     return this.getEmojiString | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| import { Client } from '../models/client.ts' | ||||
| import { ChannelPayload } from '../types/channel.ts' | ||||
| import { INVITE } from '../types/endpoint.ts' | ||||
| import { GuildPayload } from '../types/guild.ts' | ||||
| import { InvitePayload } from '../types/invite.ts' | ||||
| import { UserPayload } from '../types/user.ts' | ||||
|  | @ -31,6 +32,12 @@ export class Invite extends Base { | |||
|     this.approximatePresenceCount = data.approximate_presence_count | ||||
|   } | ||||
| 
 | ||||
|   /** Delete an invite. Requires the MANAGE_CHANNELS permission on the channel this invite belongs to, or MANAGE_GUILD to remove any invite across the guild. Returns an invite object on success. Fires a Invite Delete Gateway event. */ | ||||
|   async delete(): Promise<Invite> { | ||||
|     const res = await this.client.rest.delete(INVITE(this.code)) | ||||
|     return new Invite(this.client, res) | ||||
|   } | ||||
| 
 | ||||
|   readFromData(data: InvitePayload): void { | ||||
|     this.code = data.code ?? this.code | ||||
|     this.guild = data.guild ?? this.guild | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ import { TextChannel } from './textChannel.ts' | |||
| import { Guild } from './guild.ts' | ||||
| import { MessageReactionsManager } from '../managers/messageReactions.ts' | ||||
| import { MessageSticker } from './messageSticker.ts' | ||||
| import { Emoji } from './emoji.ts' | ||||
| 
 | ||||
| type AllMessageOptions = MessageOption | Embed | ||||
| 
 | ||||
|  | @ -46,6 +47,10 @@ export class Message extends Base { | |||
|   flags?: number | ||||
|   stickers?: MessageSticker[] | ||||
| 
 | ||||
|   get createdAt(): Date { | ||||
|     return new Date(this.timestamp) | ||||
|   } | ||||
| 
 | ||||
|   constructor( | ||||
|     client: Client, | ||||
|     data: MessagePayload, | ||||
|  | @ -108,25 +113,52 @@ export class Message extends Base { | |||
|   } | ||||
| 
 | ||||
|   /** Edits this message. */ | ||||
|   async edit(text?: string, option?: MessageOption): Promise<Message> { | ||||
|   async edit( | ||||
|     content?: string | AllMessageOptions, | ||||
|     option?: AllMessageOptions | ||||
|   ): Promise<Message> { | ||||
|     if (typeof content === 'object') { | ||||
|       option = content | ||||
|       content = undefined | ||||
|     } | ||||
|     if (content === undefined && option === undefined) { | ||||
|       throw new Error('Either text or option is necessary.') | ||||
|     } | ||||
|     if (option instanceof Embed) { | ||||
|       option = { | ||||
|         embed: option | ||||
|       } | ||||
|     } | ||||
|     if ( | ||||
|       this.client.user !== undefined && | ||||
|       this.author.id !== this.client.user?.id | ||||
|     ) | ||||
|     ) { | ||||
|       throw new Error("Cannot edit other users' messages") | ||||
|     return this.channel.editMessage(this.id, text, option) | ||||
|     } | ||||
|     return this.channel.editMessage(this.id, content, option) | ||||
|   } | ||||
| 
 | ||||
|   /** Creates a Reply to this Message. */ | ||||
|   async reply( | ||||
|     text?: string | AllMessageOptions, | ||||
|     content?: string | AllMessageOptions, | ||||
|     option?: AllMessageOptions | ||||
|   ): Promise<Message> { | ||||
|     return this.channel.send(text, option, this) | ||||
|     return this.channel.send(content, option, this) | ||||
|   } | ||||
| 
 | ||||
|   /** Deletes the Message. */ | ||||
|   async delete(): Promise<void> { | ||||
|     return this.client.rest.delete(CHANNEL_MESSAGE(this.channelID, this.id)) | ||||
|   } | ||||
| 
 | ||||
|   async addReaction(emoji: string | Emoji): Promise<void> { | ||||
|     return this.channel.addReaction(this, emoji) | ||||
|   } | ||||
| 
 | ||||
|   async removeReaction( | ||||
|     emoji: string | Emoji, | ||||
|     user?: User | Member | string | ||||
|   ): Promise<void> { | ||||
|     return this.channel.removeReaction(this, emoji, user) | ||||
|   } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										70
									
								
								src/structures/template.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/structures/template.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,70 @@ | |||
| import { Client } from '../models/client.ts' | ||||
| import { TEMPLATE } from '../types/endpoint.ts' | ||||
| import { TemplatePayload } from '../types/template.ts' | ||||
| import { Base } from './base.ts' | ||||
| import { Guild } from './guild.ts' | ||||
| import { User } from './user.ts' | ||||
| 
 | ||||
| export class Template extends Base { | ||||
|   /** The template code (unique ID) */ | ||||
|   code: string | ||||
|   /** The template name */ | ||||
|   name: string | ||||
|   /** The description for the template */ | ||||
|   description: string | null | ||||
|   /** Number of times this template has been used */ | ||||
|   usageCount: number | ||||
|   /** The ID of the user who created the template */ | ||||
|   creatorID: string | ||||
|   /** The user who created the template */ | ||||
|   creator: User | ||||
|   /** When this template was created (in ms) */ | ||||
|   createdAt: number | ||||
|   /** When this template was last synced to the source guild (in ms) */ | ||||
|   updatedAt: number | ||||
|   /** The ID of the guild this template is based on */ | ||||
|   sourceGuildID: string | ||||
|   /** The guild snapshot this template contains */ | ||||
|   serializedSourceGuild: Guild | ||||
|   /** Whether the template has unsynced changes */ | ||||
|   isDirty: boolean | null | ||||
| 
 | ||||
|   constructor(client: Client, data: TemplatePayload) { | ||||
|     super(client, data) | ||||
|     this.code = data.code | ||||
|     this.name = data.name | ||||
|     this.description = data.description | ||||
|     this.usageCount = data.usage_count | ||||
|     this.creatorID = data.creator_id | ||||
|     this.creator = new User(client, data.creator) | ||||
|     this.createdAt = Date.parse(data.created_at) | ||||
|     this.updatedAt = Date.parse(data.updated_at) | ||||
|     this.sourceGuildID = data.source_guild_id | ||||
|     this.serializedSourceGuild = new Guild(client, data.serialized_source_guild) | ||||
|     this.isDirty = Boolean(data.is_dirty) | ||||
|   } | ||||
| 
 | ||||
|   /** Modifies the template's metadata. Requires the MANAGE_GUILD permission. Returns the template object on success. */ | ||||
|   async edit(data: ModifyGuildTemplateParams): Promise<Template> { | ||||
|     const res = await this.client.rest.patch(TEMPLATE(this.code), data) | ||||
|     return new Template(this.client, res) | ||||
|   } | ||||
| 
 | ||||
|   /** Deletes the template. Requires the MANAGE_GUILD permission. Returns the deleted template object on success. */ | ||||
|   async delete(): Promise<Template> { | ||||
|     const res = await this.client.rest.delete(TEMPLATE(this.code)) | ||||
|     return new Template(this.client, res) | ||||
|   } | ||||
| 
 | ||||
|   /** Syncs the template to the guild's current state. Requires the MANAGE_GUILD permission. Returns the template object on success. */ | ||||
|   async sync(): Promise<Template> { | ||||
|     const res = await this.client.rest.put(TEMPLATE(this.code)) | ||||
|     return new Template(this.client, res) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** https://discord.com/developers/docs/resources/template#modify-guild-template-json-params */ | ||||
| export interface ModifyGuildTemplateParams { | ||||
|   name?: string | ||||
|   description?: string | null | ||||
| } | ||||
|  | @ -3,6 +3,7 @@ import { Client } from '../models/client.ts' | |||
| import { | ||||
|   GuildTextChannelPayload, | ||||
|   MessageOption, | ||||
|   MessagePayload, | ||||
|   MessageReference, | ||||
|   ModifyGuildTextChannelOption, | ||||
|   ModifyGuildTextChannelPayload, | ||||
|  | @ -12,12 +13,18 @@ import { | |||
| import { | ||||
|   CHANNEL, | ||||
|   CHANNEL_MESSAGE, | ||||
|   CHANNEL_MESSAGES | ||||
|   CHANNEL_MESSAGES, | ||||
|   MESSAGE_REACTION_ME, | ||||
|   MESSAGE_REACTION_USER | ||||
| } from '../types/endpoint.ts' | ||||
| import { Collection } from '../utils/collection.ts' | ||||
| import { Channel } from './channel.ts' | ||||
| import { Embed } from './embed.ts' | ||||
| import { Emoji } from './emoji.ts' | ||||
| import { Guild } from './guild.ts' | ||||
| import { Member } from './member.ts' | ||||
| import { Message } from './message.ts' | ||||
| import { User } from './user.ts' | ||||
| 
 | ||||
| export type AllMessageOptions = MessageOption | Embed | ||||
| 
 | ||||
|  | @ -41,29 +48,30 @@ export class TextChannel extends Channel { | |||
| 
 | ||||
|   /** | ||||
|    * | ||||
|    * @param text Text content of the Message to send. | ||||
|    * @param content Text content of the Message to send. | ||||
|    * @param option Various other Message options. | ||||
|    * @param reply Reference to a Message object to reply-to. | ||||
|    */ | ||||
|   async send( | ||||
|     text?: string | AllMessageOptions, | ||||
|     content?: string | AllMessageOptions, | ||||
|     option?: AllMessageOptions, | ||||
|     reply?: Message | ||||
|   ): Promise<Message> { | ||||
|     if (typeof text === 'object') { | ||||
|       option = text | ||||
|       text = undefined | ||||
|     if (typeof content === 'object') { | ||||
|       option = content | ||||
|       content = undefined | ||||
|     } | ||||
|     if (text === undefined && option === undefined) { | ||||
|     if (content === undefined && option === undefined) { | ||||
|       throw new Error('Either text or option is necessary.') | ||||
|     } | ||||
|     if (option instanceof Embed) | ||||
|     if (option instanceof Embed) { | ||||
|       option = { | ||||
|         embed: option | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     const payload: any = { | ||||
|       content: text, | ||||
|       content: content, | ||||
|       embed: option?.embed, | ||||
|       file: option?.file, | ||||
|       tts: option?.tts, | ||||
|  | @ -124,6 +132,96 @@ export class TextChannel extends Channel { | |||
|     await res.mentions.fromPayload(newMsg) | ||||
|     return res | ||||
|   } | ||||
| 
 | ||||
|   async addReaction( | ||||
|     message: Message | string, | ||||
|     emoji: Emoji | string | ||||
|   ): Promise<void> { | ||||
|     if (emoji instanceof Emoji) { | ||||
|       emoji = emoji.getEmojiString | ||||
|     } | ||||
|     if (message instanceof Message) { | ||||
|       message = message.id | ||||
|     } | ||||
| 
 | ||||
|     const encodedEmoji = encodeURI(emoji) | ||||
| 
 | ||||
|     await this.client.rest.put( | ||||
|       MESSAGE_REACTION_ME(this.id, message, encodedEmoji) | ||||
|     ) | ||||
|   } | ||||
| 
 | ||||
|   async removeReaction( | ||||
|     message: Message | string, | ||||
|     emoji: Emoji | string, | ||||
|     user?: User | Member | string | ||||
|   ): Promise<void> { | ||||
|     if (emoji instanceof Emoji) { | ||||
|       emoji = emoji.getEmojiString | ||||
|     } | ||||
|     if (message instanceof Message) { | ||||
|       message = message.id | ||||
|     } | ||||
|     if (user !== undefined) { | ||||
|       if (typeof user !== 'string') { | ||||
|         user = user.id | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     const encodedEmoji = encodeURI(emoji) | ||||
| 
 | ||||
|     if (user === undefined) { | ||||
|       await this.client.rest.delete( | ||||
|         MESSAGE_REACTION_ME(this.id, message, encodedEmoji) | ||||
|       ) | ||||
|     } else { | ||||
|       await this.client.rest.delete( | ||||
|         MESSAGE_REACTION_USER(this.id, message, encodedEmoji, user) | ||||
|       ) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * 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 { | ||||
|  | @ -185,4 +283,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 | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ import { Embed } from './embed.ts' | |||
| import { Message } from './message.ts' | ||||
| import { TextChannel } from './textChannel.ts' | ||||
| import { User } from './user.ts' | ||||
| import { fetchAuto } from 'https://raw.githubusercontent.com/DjDeveloperr/fetch-base64/main/mod.ts' | ||||
| import { fetchAuto } from '../../deps.ts' | ||||
| import { WEBHOOK_MESSAGE } from '../types/endpoint.ts' | ||||
| 
 | ||||
| export interface WebhookMessageOptions extends MessageOption { | ||||
|  |  | |||
|  | @ -112,6 +112,17 @@ client.on('messageCreate', async (msg: Message) => { | |||
|     } else { | ||||
|       msg.channel.send('Failed...') | ||||
|     } | ||||
|   } else if (msg.content === '!react') { | ||||
|     msg.addReaction('🤔') | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
| client.on('messageReactionRemove', (reaction, user) => { | ||||
|   const msg = reaction.message | ||||
| 
 | ||||
|   // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
 | ||||
|   if (reaction.me && reaction.emoji.getEmojiString === '🤔') { | ||||
|     msg.removeReaction(reaction.emoji) | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,13 +7,14 @@ import { | |||
|   groupslash, | ||||
|   CommandContext, | ||||
|   Extension, | ||||
|   Collection | ||||
|   Collection, | ||||
|   GuildTextChannel | ||||
| } from '../../mod.ts' | ||||
| import { LL_IP, LL_PASS, LL_PORT, TOKEN } from './config.ts' | ||||
| import { | ||||
|   Manager, | ||||
|   Player | ||||
| } from 'https://raw.githubusercontent.com/Lavaclient/lavadeno/master/mod.ts' | ||||
| } from '../../deps.ts' | ||||
| import { Interaction } from '../structures/slash.ts' | ||||
| import { slash } from '../models/client.ts' | ||||
| // import { SlashCommandOptionType } from '../types/slash.ts'
 | ||||
|  | @ -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 | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ import { UserPayload } from './user.ts' | |||
| export interface TemplatePayload { | ||||
|   code: string | ||||
|   name: string | ||||
|   description: string | undefined | ||||
|   description: string | null | ||||
|   usage_count: number | ||||
|   creator_id: string | ||||
|   creator: UserPayload | ||||
|  | @ -12,5 +12,5 @@ export interface TemplatePayload { | |||
|   updated_at: string | ||||
|   source_guild_id: string | ||||
|   serialized_source_guild: GuildPayload | ||||
|   is_dirty: boolean | undefined | ||||
|   is_dirty: boolean | null | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue