wt
This commit is contained in:
		
						commit
						1b77ea0411
					
				
					 22 changed files with 347 additions and 105 deletions
				
			
		
							
								
								
									
										3
									
								
								.eggignore
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.eggignore
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| extends .gitignore | ||||
| ./src/test/**/* | ||||
| 
 | ||||
|  | @ -30,13 +30,15 @@ | |||
| 
 | ||||
| You can import the package from https://deno.land/x/harmony/mod.ts (with latest version) or can add a version too, and raw GitHub URL (latest unpublished version) https://raw.githubusercontent.com/harmonyland/harmony/main/mod.ts too. | ||||
| 
 | ||||
| You can also check(not import) the module in https://nest.land/package/harmony (link for importing is in the site). | ||||
| 
 | ||||
| For a quick example, run this: | ||||
| 
 | ||||
| ```bash | ||||
| deno run --allow-net https://deno.land/x/harmony/examples/ping.ts | ||||
| ``` | ||||
| 
 | ||||
| And input your bot's token and Intents. | ||||
| And input your bot's token. | ||||
| 
 | ||||
| Here is a small example of how to use harmony, | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										24
									
								
								egg.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								egg.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | |||
| { | ||||
|   "$schema": "https://x.nest.land/eggs@0.3.4/src/schema.json", | ||||
|   "name": "harmony", | ||||
|   "entry": "./mod.ts", | ||||
|   "description": "An easy to use Discord API Library for Deno.", | ||||
|   "homepage": "https://github.com/harmonyland/harmony", | ||||
|   "version": "v1.1.3", | ||||
|   "files": [ | ||||
|     "./src/**/*", | ||||
|     "./deps.ts", | ||||
|     "./README.md", | ||||
|     "./LICENSE", | ||||
|     "./banner.png", | ||||
|     "./CONTRIBUTING.md", | ||||
|     "./CODE_OF_CONDUCT.md", | ||||
|     "./examples/*" | ||||
|   ], | ||||
|   "checkFormat": "npx eslint src", | ||||
|   "checkTests": false, | ||||
|   "checkInstallation": false, | ||||
|   "check": true, | ||||
|   "unlisted": false, | ||||
|   "ignore": [] | ||||
| } | ||||
							
								
								
									
										4
									
								
								mod.ts
									
										
									
									
									
								
							
							
						
						
									
										4
									
								
								mod.ts
									
										
									
									
									
								
							|  | @ -14,7 +14,8 @@ export { | |||
|   CommandBuilder, | ||||
|   CommandCategory, | ||||
|   CommandsManager, | ||||
|   CategoriesManager | ||||
|   CategoriesManager, | ||||
|   CommandsLoader | ||||
| } from './src/models/command.ts' | ||||
| export type { CommandContext, CommandOptions } from './src/models/command.ts' | ||||
| export { | ||||
|  | @ -42,6 +43,7 @@ export { ReactionUsersManager } from './src/managers/reactionUsers.ts' | |||
| export { MessagesManager } from './src/managers/messages.ts' | ||||
| export { RolesManager } from './src/managers/roles.ts' | ||||
| export { UsersManager } from './src/managers/users.ts' | ||||
| export { InviteManager } from './src/managers/invites.ts' | ||||
| export { Application } from './src/structures/application.ts' | ||||
| // export { ImageURL } from './src/structures/cdn.ts'
 | ||||
| export { Channel } from './src/structures/channel.ts' | ||||
|  |  | |||
|  | @ -27,6 +27,11 @@ export const guildCreate: GatewayEventHandler = async ( | |||
|   if (d.voice_states !== undefined) | ||||
|     await guild.voiceStates.fromPayload(d.voice_states) | ||||
| 
 | ||||
|   for (const emojiPayload of d.emojis) { | ||||
|     if (emojiPayload.id === null) continue | ||||
|     await gateway.client.emojis.set(emojiPayload.id, emojiPayload) | ||||
|   } | ||||
| 
 | ||||
|   if (hasGuild === undefined) { | ||||
|     // It wasn't lazy load, so emit event
 | ||||
|     gateway.client.emit('guildCreate', guild) | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ export const guildDelete: GatewayEventHandler = async ( | |||
|     await guild.channels.flush() | ||||
|     await guild.roles.flush() | ||||
|     await guild.presences.flush() | ||||
|     await guild.emojis.flush() | ||||
|     await gateway.client.guilds._delete(d.id) | ||||
| 
 | ||||
|     gateway.client.emit('guildDelete', guild) | ||||
|  |  | |||
|  | @ -60,6 +60,13 @@ export class BaseManager<T, T2> { | |||
|     return collection | ||||
|   } | ||||
| 
 | ||||
|   async *[Symbol.asyncIterator](): AsyncIterableIterator<T2> { | ||||
|     const arr = (await this.array()) ?? [] | ||||
|     const { readable, writable } = new TransformStream() | ||||
|     arr.forEach((el) => writable.getWriter().write(el)) | ||||
|     yield* readable.getIterator() | ||||
|   } | ||||
| 
 | ||||
|   /** Deletes everything from Cache */ | ||||
|   flush(): any { | ||||
|     return this.client.cache.deleteCache(this.cacheName) | ||||
|  |  | |||
|  | @ -39,4 +39,11 @@ export class BaseChildManager<T, T2> { | |||
|     } | ||||
|     return collection | ||||
|   } | ||||
| 
 | ||||
|   async *[Symbol.asyncIterator](): AsyncIterableIterator<T2> { | ||||
|     const arr = (await this.array()) ?? [] | ||||
|     const { readable, writable } = new TransformStream() | ||||
|     arr.forEach((el: unknown) => writable.getWriter().write(el)) | ||||
|     yield* readable.getIterator() | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -66,8 +66,7 @@ export class GuildChannelsManager extends BaseChildManager< | |||
|   async create(options: CreateChannelOptions): Promise<GuildChannels> { | ||||
|     if (options.name === undefined) | ||||
|       throw new Error('name is required for GuildChannelsManager#create') | ||||
|     const res = ((await this.client.rest.post(GUILD_CHANNELS(this.guild.id)), | ||||
|     { | ||||
|     const res = ((await this.client.rest.post(GUILD_CHANNELS(this.guild.id), { | ||||
|       name: options.name, | ||||
|       type: options.type, | ||||
|       topic: options.topic, | ||||
|  | @ -83,7 +82,7 @@ export class GuildChannelsManager extends BaseChildManager< | |||
|           ? options.parent.id | ||||
|           : options.parent, | ||||
|       nsfw: options.nsfw | ||||
|     }) as unknown) as GuildChannelPayload | ||||
|     })) as unknown) as GuildChannelPayload | ||||
| 
 | ||||
|     await this.set(res.id, res) | ||||
|     const channel = await this.get(res.id) | ||||
|  |  | |||
|  | @ -147,6 +147,7 @@ export class GuildManager extends BaseManager<GuildPayload, Guild> { | |||
| 
 | ||||
|   /** Sets a value to Cache */ | ||||
|   async set(key: string, value: GuildPayload): Promise<any> { | ||||
|     value = { ...value } | ||||
|     if ('roles' in value) value.roles = [] | ||||
|     if ('emojis' in value) value.emojis = [] | ||||
|     if ('members' in value) value.members = [] | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ import { Embed } from '../structures/embed.ts' | |||
| import { MessageAttachment } from '../structures/message.ts' | ||||
| import { Collection } from '../utils/collection.ts' | ||||
| import { Client } from './client.ts' | ||||
| import { simplifyAPIError } from '../utils/err_fmt.ts' | ||||
| 
 | ||||
| export type RequestMethods = | ||||
|   | 'get' | ||||
|  | @ -37,15 +38,36 @@ export interface DiscordAPIErrorPayload { | |||
|   code?: number | ||||
|   message?: string | ||||
|   errors: object | ||||
|   requestData: { [key: string]: any } | ||||
| } | ||||
| 
 | ||||
| export class DiscordAPIError extends Error { | ||||
|   name = 'DiscordAPIError' | ||||
|   error?: DiscordAPIErrorPayload | ||||
| 
 | ||||
|   constructor(message?: string, error?: DiscordAPIErrorPayload) { | ||||
|     super(message) | ||||
|     this.error = error | ||||
|   constructor(error: string | DiscordAPIErrorPayload) { | ||||
|     super() | ||||
|     const fmt = Object.entries( | ||||
|       typeof error === 'object' ? simplifyAPIError(error.errors) : {} | ||||
|     ) | ||||
|     this.message = | ||||
|       typeof error === 'string' | ||||
|         ? `${error} ` | ||||
|         : `\n${error.method} ${error.url.slice(7)} returned ${error.status}\n(${ | ||||
|             error.code ?? 'unknown' | ||||
|           }) ${error.message}${ | ||||
|             fmt.length === 0 | ||||
|               ? '' | ||||
|               : `\n${fmt | ||||
|                   .map( | ||||
|                     (e) => | ||||
|                       `  at ${e[0]}:\n${e[1] | ||||
|                         .map((e) => `   - ${e}`) | ||||
|                         .join('\n')}` | ||||
|                   ) | ||||
|                   .join('\n')}\n` | ||||
|           }` | ||||
|     if (typeof error === 'object') this.error = error | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  | @ -269,7 +291,7 @@ export class RESTManager { | |||
|     const headers: RequestHeaders = { | ||||
|       'User-Agent': | ||||
|         this.userAgent ?? | ||||
|         `DiscordBot (harmony, https://github.com/harmony-org/harmony)` | ||||
|         `DiscordBot (harmony, https://github.com/harmonyland/harmony)` | ||||
|     } | ||||
| 
 | ||||
|     if (this.token !== undefined) { | ||||
|  | @ -319,7 +341,9 @@ export class RESTManager { | |||
|         } | ||||
|       } | ||||
|       const form = new FormData() | ||||
|       files.forEach((file, index) => form.append(`file${index + 1}`, file.blob, file.name)) | ||||
|       files.forEach((file, index) => | ||||
|         form.append(`file${index + 1}`, file.blob, file.name) | ||||
|       ) | ||||
|       const json = JSON.stringify(body) | ||||
|       form.append('payload_json', json) | ||||
|       if (body === undefined) body = {} | ||||
|  | @ -448,42 +472,20 @@ export class RESTManager { | |||
|         new DiscordAPIError(`Request was Unauthorized. Invalid Token.\n${text}`) | ||||
|       ) | ||||
| 
 | ||||
|     const _data = { ...data } | ||||
|     if (_data?.headers !== undefined) delete _data.headers | ||||
|     if (_data?.method !== undefined) delete _data.method | ||||
| 
 | ||||
|     // At this point we know it is error
 | ||||
|     const error: DiscordAPIErrorPayload = { | ||||
|       url: response.url, | ||||
|       url: new URL(response.url).pathname, | ||||
|       status, | ||||
|       method: data.method, | ||||
|       code: body?.code, | ||||
|       message: body?.message, | ||||
|       errors: Object.fromEntries( | ||||
|         Object.entries( | ||||
|           (body?.errors as { | ||||
|             [name: string]: { | ||||
|               _errors: Array<{ code: string; message: string }> | ||||
|       errors: body?.errors ?? {}, | ||||
|       requestData: _data | ||||
|     } | ||||
|           }) ?? {} | ||||
|         ).map((entry) => { | ||||
|           return [entry[0], entry[1]._errors ?? []] | ||||
|         }) | ||||
|       ) | ||||
|     } | ||||
| 
 | ||||
|     // if (typeof error.errors === 'object') {
 | ||||
|     //   const errors = error.errors as {
 | ||||
|     //     [name: string]: { _errors: Array<{ code: string; message: string }> }
 | ||||
|     //   }
 | ||||
|     //   console.log(`%cREST Error:`, 'color: #F14C39;')
 | ||||
|     //   Object.entries(errors).forEach((entry) => {
 | ||||
|     //     console.log(`  %c${entry[0]}:`, 'color: #12BC79;')
 | ||||
|     //     entry[1]._errors.forEach((e) => {
 | ||||
|     //       console.log(
 | ||||
|     //         `    %c${e.code}: %c${e.message}`,
 | ||||
|     //         'color: skyblue;',
 | ||||
|     //         'color: #CECECE;'
 | ||||
|     //       )
 | ||||
|     //     })
 | ||||
|     //   })
 | ||||
|     // }
 | ||||
| 
 | ||||
|     if ( | ||||
|       [ | ||||
|  | @ -493,9 +495,9 @@ export class RESTManager { | |||
|         HttpResponseCode.MethodNotAllowed | ||||
|       ].includes(status) | ||||
|     ) { | ||||
|       reject(new DiscordAPIError(Deno.inspect(error), error)) | ||||
|       reject(new DiscordAPIError(error)) | ||||
|     } else if (status === HttpResponseCode.GatewayUnavailable) { | ||||
|       reject(new DiscordAPIError(Deno.inspect(error), error)) | ||||
|       reject(new DiscordAPIError(error)) | ||||
|     } else reject(new DiscordAPIError('Request - Unknown Error')) | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| import { Guild } from '../structures/guild.ts' | ||||
| import { Interaction } from '../structures/slash.ts' | ||||
| import { | ||||
|   InteractionPayload, | ||||
|   InteractionType, | ||||
|   SlashCommandChoice, | ||||
|   SlashCommandOption, | ||||
|  | @ -12,8 +13,7 @@ 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 { verify as edverify } from 'https://deno.land/x/ed25519@1.0.1/mod.ts' | ||||
| 
 | ||||
| export class SlashCommand { | ||||
|   slash: SlashCommandsManager | ||||
|  | @ -372,7 +372,9 @@ export interface SlashOptions { | |||
|   publicKey?: string | ||||
| } | ||||
| 
 | ||||
| /** Slash Client represents an Interactions Client which can be used without Harmony Client. */ | ||||
| const encoder = new TextEncoder() | ||||
| const decoder = new TextDecoder('utf-8') | ||||
| 
 | ||||
| export class SlashClient { | ||||
|   id: string | (() => string) | ||||
|   client?: Client | ||||
|  | @ -518,25 +520,52 @@ export class SlashClient { | |||
|     cmd.handler(interaction) | ||||
|   } | ||||
| 
 | ||||
|   /** Verify HTTP based Interaction */ | ||||
|   async verifyKey( | ||||
|     rawBody: string | Uint8Array | Buffer, | ||||
|     signature: string, | ||||
|     timestamp: string | ||||
|     rawBody: string | Uint8Array, | ||||
|     signature: string | Uint8Array, | ||||
|     timestamp: string | Uint8Array | ||||
|   ): 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) | ||||
| 
 | ||||
|     const fullBody = new Uint8Array([ | ||||
|       ...(typeof timestamp === 'string' | ||||
|         ? encoder.encode(timestamp) | ||||
|         : timestamp), | ||||
|       ...(typeof rawBody === 'string' ? encoder.encode(rawBody) : rawBody) | ||||
|     ]) | ||||
| 
 | ||||
|     return edverify(signature, fullBody, this.publicKey).catch(() => false) | ||||
|   } | ||||
| 
 | ||||
|   /** Verify [Deno Std HTTP Server Request](https://deno.land/std/http/server.ts) and return Interaction */ | ||||
|   async verifyServerRequest(req: { | ||||
|     headers: Headers | ||||
|     method: string | ||||
|     body: Deno.Reader | ||||
|     respond: (options: { | ||||
|       status?: number | ||||
|       body?: string | Uint8Array | ||||
|     }) => Promise<void> | ||||
|   }): Promise<boolean | Interaction> { | ||||
|     if (req.method.toLowerCase() !== 'post') return false | ||||
| 
 | ||||
|     const signature = req.headers.get('x-signature-ed25519') | ||||
|     const timestamp = req.headers.get('x-signature-timestamp') | ||||
|     if (signature === null || timestamp === null) return false | ||||
| 
 | ||||
|     const rawbody = await Deno.readAll(req.body) | ||||
|     const verify = await this.verifyKey(rawbody, signature, timestamp) | ||||
|     if (!verify) return false | ||||
| 
 | ||||
|     try { | ||||
|       const payload: InteractionPayload = JSON.parse(decoder.decode(rawbody)) | ||||
|       const res = new Interaction(this as any, payload, {}) | ||||
|       return res | ||||
|     } catch (e) { | ||||
|       return false | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   async verifyOpineRequest(req: any): Promise<boolean> { | ||||
|  |  | |||
|  | @ -3,11 +3,13 @@ import { ImageFormats, ImageSize } from '../types/cdn.ts' | |||
| /** Function to get Image URL from a resource on Discord CDN */ | ||||
| export const ImageURL = ( | ||||
|   url: string, | ||||
|   format: ImageFormats | undefined = 'png', | ||||
|   size: ImageSize | undefined = 128 | ||||
|   format: ImageFormats = 'png', | ||||
|   size: ImageSize = 128 | ||||
| ): string => { | ||||
|   size = size === undefined ? 128 : size | ||||
|   if (url.includes('a_')) { | ||||
|     return `${url}.${format === undefined ? 'gif' : format}?size=${size}` | ||||
|   } else return `${url}.${format === 'gif' ? 'png' : format}?size=${size}` | ||||
|     return `${url}.${format === 'dynamic' ? 'gif' : format}?size=${size}` | ||||
|   } else | ||||
|     return `${url}.${ | ||||
|       format === 'gif' || format === 'dynamic' ? 'png' : format | ||||
|     }?size=${size}` | ||||
| } | ||||
|  |  | |||
|  | @ -1,8 +1,10 @@ | |||
| import { Client } from '../models/client.ts' | ||||
| import { ImageSize } from '../types/cdn.ts' | ||||
| import { EmojiPayload } from '../types/emoji.ts' | ||||
| import { EMOJI } from '../types/endpoint.ts' | ||||
| import { CUSTOM_EMOJI, EMOJI } from '../types/endpoint.ts' | ||||
| import { Snowflake } from '../utils/snowflake.ts' | ||||
| import { Base } from './base.ts' | ||||
| import { ImageURL } from './cdn.ts' | ||||
| import { Guild } from './guild.ts' | ||||
| import { Role } from './role.ts' | ||||
| import { User } from './user.ts' | ||||
|  | @ -54,6 +56,18 @@ export class Emoji extends Base { | |||
|     this.available = data.available | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Gets emoji image URL | ||||
|    */ | ||||
|   emojiImageURL( | ||||
|     format: 'png' | 'gif' | 'dynamic' = 'png', | ||||
|     size: ImageSize = 512 | ||||
|   ): string | undefined { | ||||
|     return this.id != null | ||||
|       ? `${ImageURL(CUSTOM_EMOJI(this.id), format, size)}` | ||||
|       : undefined | ||||
|   } | ||||
| 
 | ||||
|   /** Modify the given emoji. Requires the MANAGE_EMOJIS permission. Returns the updated emoji object on success. Fires a Guild Emojis Update Gateway event. */ | ||||
|   async edit(data: ModifyGuildEmojiParams): Promise<Emoji> { | ||||
|     if (this.id === null) throw new Error('Emoji ID is not valid.') | ||||
|  |  | |||
|  | @ -32,9 +32,13 @@ import { User } from './user.ts' | |||
| import { Application } from './application.ts' | ||||
| import { | ||||
|   GUILD_BAN, | ||||
|   GUILD_BANNER, | ||||
|   GUILD_BANS, | ||||
|   GUILD_DISCOVERY_SPLASH, | ||||
|   GUILD_ICON, | ||||
|   GUILD_INTEGRATIONS, | ||||
|   GUILD_PRUNE | ||||
|   GUILD_PRUNE, | ||||
|   GUILD_SPLASH | ||||
| } from '../types/endpoint.ts' | ||||
| import { GuildVoiceStatesManager } from '../managers/guildVoiceStates.ts' | ||||
| import { RequestMembersOptions } from '../gateway/index.ts' | ||||
|  | @ -42,6 +46,8 @@ import { GuildPresencesManager } from '../managers/presences.ts' | |||
| import { TemplatePayload } from '../types/template.ts' | ||||
| import { Template } from './template.ts' | ||||
| import { DiscordAPIError } from '../models/rest.ts' | ||||
| import { ImageFormats, ImageSize } from '../types/cdn.ts' | ||||
| import { ImageURL } from './cdn.ts' | ||||
| 
 | ||||
| export class GuildBan extends Base { | ||||
|   guild: Guild | ||||
|  | @ -258,6 +264,58 @@ export class Guild extends SnowflakeBase { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Gets guild icon URL | ||||
|    */ | ||||
|   iconURL( | ||||
|     format: ImageFormats = 'png', | ||||
|     size: ImageSize = 512 | ||||
|   ): string | undefined { | ||||
|     return this.icon != null | ||||
|       ? `${ImageURL(GUILD_ICON(this.id, this.icon), format, size)}` | ||||
|       : undefined | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Gets guild splash URL | ||||
|    */ | ||||
|   splashURL( | ||||
|     format: ImageFormats = 'png', | ||||
|     size: ImageSize = 512 | ||||
|   ): string | undefined { | ||||
|     return this.splash != null | ||||
|       ? `${ImageURL(GUILD_SPLASH(this.id, this.splash), format, size)}` | ||||
|       : undefined | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Gets guild discover splash URL | ||||
|    */ | ||||
|   discoverSplashURL( | ||||
|     format: ImageFormats = 'png', | ||||
|     size: ImageSize = 512 | ||||
|   ): string | undefined { | ||||
|     return this.discoverySplash != null | ||||
|       ? `${ImageURL( | ||||
|           GUILD_DISCOVERY_SPLASH(this.id, this.discoverySplash), | ||||
|           format, | ||||
|           size | ||||
|         )}` | ||||
|       : undefined | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Gets guild banner URL | ||||
|    */ | ||||
|   bannerURL( | ||||
|     format: ImageFormats = 'png', | ||||
|     size: ImageSize = 512 | ||||
|   ): string | undefined { | ||||
|     return this.banner != null | ||||
|       ? `${ImageURL(GUILD_BANNER(this.id, this.banner), format, size)}` | ||||
|       : undefined | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Gets Everyone role of the Guild | ||||
|    */ | ||||
|  |  | |||
|  | @ -86,28 +86,18 @@ export class InteractionUser extends User { | |||
| } | ||||
| 
 | ||||
| export class Interaction extends SnowflakeBase { | ||||
|   /** Type of Interaction */ | ||||
|   type: InteractionType | ||||
|   /** Interaction Token */ | ||||
|   /** This will be `SlashClient` in case of `SlashClient#verifyServerRequest` */ | ||||
|   client: Client | ||||
|   type: number | ||||
|   token: string | ||||
|   /** Interaction ID */ | ||||
|   id: string | ||||
|   /** Data sent with Interaction. Only applies to Application Command */ | ||||
|   data?: InteractionApplicationCommandData | ||||
|   /** Channel in which Interaction was initiated */ | ||||
|   channel?: TextChannel | GuildTextChannel | ||||
|   /** Guild in which Interaction was initiated */ | ||||
|   guild?: Guild | ||||
|   /** Member object of who initiated the Interaction */ | ||||
|   member?: Member | ||||
|   /** User object of who invoked Interaction */ | ||||
|   user: User | ||||
|   /** Whether we have responded to Interaction or not */ | ||||
|   responded: boolean = false | ||||
|   /** Resolved data for Snowflakes in Slash Command Arguments */ | ||||
|   resolved: InteractionApplicationCommandResolved | ||||
|   /** Whether response was deferred or not */ | ||||
|   deferred: boolean = false | ||||
|   data: InteractionData | ||||
|   channel: GuildTextChannel | ||||
|   guild: Guild | ||||
|   member: Member | ||||
|   _savedHook?: Webhook | ||||
|   _respond?: (data: InteractionResponsePayload) => unknown | ||||
| 
 | ||||
|   constructor( | ||||
|     client: Client, | ||||
|  |  | |||
|  | @ -145,12 +145,15 @@ export class TextChannel extends Channel { | |||
|     emoji: Emoji | string | ||||
|   ): Promise<void> { | ||||
|     if (emoji instanceof Emoji) { | ||||
|       emoji = emoji.getEmojiString | ||||
|       emoji = `${emoji.name}:${emoji.id}` | ||||
|     } else if (emoji.length > 4) { | ||||
|       if (!isNaN(Number(emoji))) { | ||||
|         const findEmoji = await this.client.emojis.get(emoji) | ||||
|         if (findEmoji !== undefined) emoji = `${findEmoji.name}:${findEmoji.id}` | ||||
|         else throw new Error(`Emoji not found: ${emoji}`) | ||||
|       } | ||||
|     if (message instanceof Message) { | ||||
|       message = message.id | ||||
|     } | ||||
| 
 | ||||
|     if (message instanceof Message) message = message.id | ||||
|     const encodedEmoji = encodeURI(emoji) | ||||
| 
 | ||||
|     await this.client.rest.put( | ||||
|  | @ -165,11 +168,15 @@ export class TextChannel extends Channel { | |||
|     user?: User | Member | string | ||||
|   ): Promise<void> { | ||||
|     if (emoji instanceof Emoji) { | ||||
|       emoji = emoji.getEmojiString | ||||
|       emoji = `${emoji.name}:${emoji.id}` | ||||
|     } else if (emoji.length > 4) { | ||||
|       if (!isNaN(Number(emoji))) { | ||||
|         const findEmoji = await this.client.emojis.get(emoji) | ||||
|         if (findEmoji !== undefined) emoji = `${findEmoji.name}:${findEmoji.id}` | ||||
|         else throw new Error(`Emoji not found: ${emoji}`) | ||||
|       } | ||||
|     if (message instanceof Message) { | ||||
|       message = message.id | ||||
|     } | ||||
|     if (message instanceof Message) message = message.id | ||||
|     if (user !== undefined) { | ||||
|       if (typeof user !== 'string') { | ||||
|         user = user.id | ||||
|  |  | |||
|  | @ -117,7 +117,7 @@ client.on('messageCreate', async (msg: Message) => { | |||
|       msg.channel.send('Failed...') | ||||
|     } | ||||
|   } else if (msg.content === '!react') { | ||||
|     msg.addReaction('🤔') | ||||
|     msg.addReaction('a:programming:785013658257195008') | ||||
|   } else if (msg.content === '!wait_for') { | ||||
|     msg.channel.send('Send anything!') | ||||
|     const [receivedMsg] = await client.waitFor( | ||||
|  | @ -210,11 +210,30 @@ client.on('messageCreate', async (msg: Message) => { | |||
|       // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
 | ||||
|       msg.member as Member | ||||
|     ) | ||||
|     msg.channel.send( | ||||
|       Object.entries(permissions.serialize()) | ||||
|         .map((e) => `${e[0]}: ${e[1] === true ? '`✅`' : '`❌`'}`) | ||||
|         .join('\n') | ||||
|     ) | ||||
|     msg.channel.send(`Your permissions:\n${permissions.toArray().join('\n')}`) | ||||
|   } else if (msg.content === '!addAllRoles') { | ||||
|     const roles = await msg.guild?.roles.array() | ||||
|     if (roles !== undefined) { | ||||
|       roles.forEach(async (role) => { | ||||
|         await msg.member?.roles.add(role) | ||||
|         console.log(role) | ||||
|       }) | ||||
|     } | ||||
|   } else if (msg.content === '!createAndAddRole') { | ||||
|     if (msg.guild !== undefined) { | ||||
|       const role = await msg.guild.roles.create({ | ||||
|         name: 'asdf', | ||||
|         permissions: 0 | ||||
|       }) | ||||
|       await msg.member?.roles.add(role) | ||||
|     } | ||||
|   } else if (msg.content === '!roles') { | ||||
|     let buf = 'Roles:' | ||||
|     if (msg.member === undefined) return | ||||
|     for await (const role of msg.member.roles) { | ||||
|       buf += `\n${role.name}` | ||||
|     } | ||||
|     msg.reply(buf) | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										44
									
								
								src/test/slash-http.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/test/slash-http.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,44 @@ | |||
| import { SlashClient } from '../../mod.ts' | ||||
| import { SLASH_ID, SLASH_PUB_KEY, SLASH_TOKEN } from './config.ts' | ||||
| import { listenAndServe } from 'https://deno.land/std@0.90.0/http/server.ts' | ||||
| 
 | ||||
| const slash = new SlashClient({ | ||||
|   id: SLASH_ID, | ||||
|   token: SLASH_TOKEN, | ||||
|   publicKey: SLASH_PUB_KEY | ||||
| }) | ||||
| 
 | ||||
| await slash.commands.bulkEdit([ | ||||
|   { | ||||
|     name: 'ping', | ||||
|     description: 'Just ping!' | ||||
|   } | ||||
| ]) | ||||
| 
 | ||||
| const options = { port: 8000 } | ||||
| console.log('Listen on port: ' + options.port.toString()) | ||||
| listenAndServe(options, async (req) => { | ||||
|   const verify = await slash.verifyServerRequest(req) | ||||
|   if (verify === false) | ||||
|     return req.respond({ status: 401, body: 'not authorized' }) | ||||
| 
 | ||||
|   const respond = async (d: any): Promise<void> => | ||||
|     req.respond({ | ||||
|       status: 200, | ||||
|       body: JSON.stringify(d), | ||||
|       headers: new Headers({ | ||||
|         'content-type': 'application/json' | ||||
|       }) | ||||
|     }) | ||||
| 
 | ||||
|   const body = JSON.parse( | ||||
|     new TextDecoder('utf-8').decode(await Deno.readAll(req.body)) | ||||
|   ) | ||||
|   if (body.type === 1) return await respond({ type: 1 }) | ||||
|   await respond({ | ||||
|     type: 4, | ||||
|     data: { | ||||
|       content: 'Pong!' | ||||
|     } | ||||
|   }) | ||||
| }) | ||||
|  | @ -1,2 +1,2 @@ | |||
| export type ImageSize = 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | ||||
| export type ImageFormats = 'jpg' | 'jpeg' | 'png' | 'webp' | 'gif' | ||||
| export type ImageFormats = 'jpg' | 'jpeg' | 'png' | 'webp' | 'gif' | 'dynamic' | ||||
|  |  | |||
|  | @ -14,7 +14,10 @@ export class BitField { | |||
|   #flags: { [name: string]: number | bigint } = {} | ||||
|   bitfield: bigint | ||||
| 
 | ||||
|   constructor(flags: { [name: string]: number | bigint }, bits: any) { | ||||
|   constructor( | ||||
|     flags: { [name: string]: number | bigint }, | ||||
|     bits: BitFieldResolvable | ||||
|   ) { | ||||
|     this.#flags = flags | ||||
|     this.bitfield = BitField.resolve(this.#flags, bits) | ||||
|   } | ||||
|  | @ -104,11 +107,11 @@ export class BitField { | |||
|     if (bit instanceof BitField) return this.resolve(flags, bit.bitfield) | ||||
|     if (Array.isArray(bit)) | ||||
|       return (bit.map as any)((p: any) => this.resolve(flags, p)).reduce( | ||||
|         (prev: any, p: any) => prev | p, | ||||
|         0 | ||||
|         (prev: bigint, p: bigint) => prev | p, | ||||
|         0n | ||||
|       ) | ||||
|     if (typeof bit === 'string' && typeof flags[bit] !== 'undefined') | ||||
|       return flags[bit] | ||||
|       return BigInt(flags[bit]) | ||||
|     const error = new RangeError('BITFIELD_INVALID') | ||||
|     throw error | ||||
|   } | ||||
|  |  | |||
							
								
								
									
										23
									
								
								src/utils/err_fmt.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/utils/err_fmt.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | |||
| export interface SimplifiedError { | ||||
|   [name: string]: string[] | ||||
| } | ||||
| 
 | ||||
| export function simplifyAPIError(errors: any): SimplifiedError { | ||||
|   const res: SimplifiedError = {} | ||||
|   function fmt(obj: any, acum: string = ''): void { | ||||
|     if (typeof obj._errors === 'object' && Array.isArray(obj._errors)) | ||||
|       res[acum] = obj._errors.map((e: any) => `${e.code}: ${e.message}`) | ||||
|     else { | ||||
|       Object.entries(obj).forEach((obj: [string, any]) => { | ||||
|         const arrayIndex = !isNaN(Number(obj[0])) | ||||
|         if (arrayIndex) obj[0] = `[${obj[0]}]` | ||||
|         if (acum !== '' && !arrayIndex) acum += '.' | ||||
|         fmt(obj[1], (acum += obj[0])) | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
|   Object.entries(errors).forEach((obj: [string, any]) => { | ||||
|     fmt(obj[1], obj[0]) | ||||
|   }) | ||||
|   return res | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue