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 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: | For a quick example, run this: | ||||||
| 
 | 
 | ||||||
| ```bash | ```bash | ||||||
| deno run --allow-net https://deno.land/x/harmony/examples/ping.ts | 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, | 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, |   CommandBuilder, | ||||||
|   CommandCategory, |   CommandCategory, | ||||||
|   CommandsManager, |   CommandsManager, | ||||||
|   CategoriesManager |   CategoriesManager, | ||||||
|  |   CommandsLoader | ||||||
| } from './src/models/command.ts' | } from './src/models/command.ts' | ||||||
| export type { CommandContext, CommandOptions } from './src/models/command.ts' | export type { CommandContext, CommandOptions } from './src/models/command.ts' | ||||||
| export { | export { | ||||||
|  | @ -42,6 +43,7 @@ export { ReactionUsersManager } from './src/managers/reactionUsers.ts' | ||||||
| export { MessagesManager } from './src/managers/messages.ts' | export { MessagesManager } from './src/managers/messages.ts' | ||||||
| export { RolesManager } from './src/managers/roles.ts' | export { RolesManager } from './src/managers/roles.ts' | ||||||
| export { UsersManager } from './src/managers/users.ts' | export { UsersManager } from './src/managers/users.ts' | ||||||
|  | export { InviteManager } from './src/managers/invites.ts' | ||||||
| export { Application } from './src/structures/application.ts' | export { Application } from './src/structures/application.ts' | ||||||
| // export { ImageURL } from './src/structures/cdn.ts'
 | // export { ImageURL } from './src/structures/cdn.ts'
 | ||||||
| export { Channel } from './src/structures/channel.ts' | export { Channel } from './src/structures/channel.ts' | ||||||
|  |  | ||||||
|  | @ -27,6 +27,11 @@ export const guildCreate: GatewayEventHandler = async ( | ||||||
|   if (d.voice_states !== undefined) |   if (d.voice_states !== undefined) | ||||||
|     await guild.voiceStates.fromPayload(d.voice_states) |     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) { |   if (hasGuild === undefined) { | ||||||
|     // It wasn't lazy load, so emit event
 |     // It wasn't lazy load, so emit event
 | ||||||
|     gateway.client.emit('guildCreate', guild) |     gateway.client.emit('guildCreate', guild) | ||||||
|  |  | ||||||
|  | @ -13,6 +13,7 @@ export const guildDelete: GatewayEventHandler = async ( | ||||||
|     await guild.channels.flush() |     await guild.channels.flush() | ||||||
|     await guild.roles.flush() |     await guild.roles.flush() | ||||||
|     await guild.presences.flush() |     await guild.presences.flush() | ||||||
|  |     await guild.emojis.flush() | ||||||
|     await gateway.client.guilds._delete(d.id) |     await gateway.client.guilds._delete(d.id) | ||||||
| 
 | 
 | ||||||
|     gateway.client.emit('guildDelete', guild) |     gateway.client.emit('guildDelete', guild) | ||||||
|  |  | ||||||
|  | @ -60,6 +60,13 @@ export class BaseManager<T, T2> { | ||||||
|     return collection |     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 */ |   /** Deletes everything from Cache */ | ||||||
|   flush(): any { |   flush(): any { | ||||||
|     return this.client.cache.deleteCache(this.cacheName) |     return this.client.cache.deleteCache(this.cacheName) | ||||||
|  |  | ||||||
|  | @ -39,4 +39,11 @@ export class BaseChildManager<T, T2> { | ||||||
|     } |     } | ||||||
|     return collection |     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> { |   async create(options: CreateChannelOptions): Promise<GuildChannels> { | ||||||
|     if (options.name === undefined) |     if (options.name === undefined) | ||||||
|       throw new Error('name is required for GuildChannelsManager#create') |       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, |       name: options.name, | ||||||
|       type: options.type, |       type: options.type, | ||||||
|       topic: options.topic, |       topic: options.topic, | ||||||
|  | @ -83,7 +82,7 @@ export class GuildChannelsManager extends BaseChildManager< | ||||||
|           ? options.parent.id |           ? options.parent.id | ||||||
|           : options.parent, |           : options.parent, | ||||||
|       nsfw: options.nsfw |       nsfw: options.nsfw | ||||||
|     }) as unknown) as GuildChannelPayload |     })) as unknown) as GuildChannelPayload | ||||||
| 
 | 
 | ||||||
|     await this.set(res.id, res) |     await this.set(res.id, res) | ||||||
|     const channel = await this.get(res.id) |     const channel = await this.get(res.id) | ||||||
|  |  | ||||||
|  | @ -147,6 +147,7 @@ export class GuildManager extends BaseManager<GuildPayload, Guild> { | ||||||
| 
 | 
 | ||||||
|   /** Sets a value to Cache */ |   /** Sets a value to Cache */ | ||||||
|   async set(key: string, value: GuildPayload): Promise<any> { |   async set(key: string, value: GuildPayload): Promise<any> { | ||||||
|  |     value = { ...value } | ||||||
|     if ('roles' in value) value.roles = [] |     if ('roles' in value) value.roles = [] | ||||||
|     if ('emojis' in value) value.emojis = [] |     if ('emojis' in value) value.emojis = [] | ||||||
|     if ('members' in value) value.members = [] |     if ('members' in value) value.members = [] | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ import { Embed } from '../structures/embed.ts' | ||||||
| import { MessageAttachment } from '../structures/message.ts' | import { MessageAttachment } from '../structures/message.ts' | ||||||
| import { Collection } from '../utils/collection.ts' | import { Collection } from '../utils/collection.ts' | ||||||
| import { Client } from './client.ts' | import { Client } from './client.ts' | ||||||
|  | import { simplifyAPIError } from '../utils/err_fmt.ts' | ||||||
| 
 | 
 | ||||||
| export type RequestMethods = | export type RequestMethods = | ||||||
|   | 'get' |   | 'get' | ||||||
|  | @ -37,15 +38,36 @@ export interface DiscordAPIErrorPayload { | ||||||
|   code?: number |   code?: number | ||||||
|   message?: string |   message?: string | ||||||
|   errors: object |   errors: object | ||||||
|  |   requestData: { [key: string]: any } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class DiscordAPIError extends Error { | export class DiscordAPIError extends Error { | ||||||
|   name = 'DiscordAPIError' |   name = 'DiscordAPIError' | ||||||
|   error?: DiscordAPIErrorPayload |   error?: DiscordAPIErrorPayload | ||||||
| 
 | 
 | ||||||
|   constructor(message?: string, error?: DiscordAPIErrorPayload) { |   constructor(error: string | DiscordAPIErrorPayload) { | ||||||
|     super(message) |     super() | ||||||
|     this.error = error |     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 = { |     const headers: RequestHeaders = { | ||||||
|       'User-Agent': |       'User-Agent': | ||||||
|         this.userAgent ?? |         this.userAgent ?? | ||||||
|         `DiscordBot (harmony, https://github.com/harmony-org/harmony)` |         `DiscordBot (harmony, https://github.com/harmonyland/harmony)` | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (this.token !== undefined) { |     if (this.token !== undefined) { | ||||||
|  | @ -319,7 +341,9 @@ export class RESTManager { | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       const form = new FormData() |       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) |       const json = JSON.stringify(body) | ||||||
|       form.append('payload_json', json) |       form.append('payload_json', json) | ||||||
|       if (body === undefined) body = {} |       if (body === undefined) body = {} | ||||||
|  | @ -448,42 +472,20 @@ export class RESTManager { | ||||||
|         new DiscordAPIError(`Request was Unauthorized. Invalid Token.\n${text}`) |         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
 |     // At this point we know it is error
 | ||||||
|     const error: DiscordAPIErrorPayload = { |     const error: DiscordAPIErrorPayload = { | ||||||
|       url: response.url, |       url: new URL(response.url).pathname, | ||||||
|       status, |       status, | ||||||
|       method: data.method, |       method: data.method, | ||||||
|       code: body?.code, |       code: body?.code, | ||||||
|       message: body?.message, |       message: body?.message, | ||||||
|       errors: Object.fromEntries( |       errors: body?.errors ?? {}, | ||||||
|         Object.entries( |       requestData: _data | ||||||
|           (body?.errors as { |  | ||||||
|             [name: string]: { |  | ||||||
|               _errors: Array<{ code: string; message: string }> |  | ||||||
|     } |     } | ||||||
|           }) ?? {} |  | ||||||
|         ).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 ( |     if ( | ||||||
|       [ |       [ | ||||||
|  | @ -493,9 +495,9 @@ export class RESTManager { | ||||||
|         HttpResponseCode.MethodNotAllowed |         HttpResponseCode.MethodNotAllowed | ||||||
|       ].includes(status) |       ].includes(status) | ||||||
|     ) { |     ) { | ||||||
|       reject(new DiscordAPIError(Deno.inspect(error), error)) |       reject(new DiscordAPIError(error)) | ||||||
|     } else if (status === HttpResponseCode.GatewayUnavailable) { |     } else if (status === HttpResponseCode.GatewayUnavailable) { | ||||||
|       reject(new DiscordAPIError(Deno.inspect(error), error)) |       reject(new DiscordAPIError(error)) | ||||||
|     } else reject(new DiscordAPIError('Request - Unknown Error')) |     } else reject(new DiscordAPIError('Request - Unknown Error')) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| import { Guild } from '../structures/guild.ts' | import { Guild } from '../structures/guild.ts' | ||||||
| import { Interaction } from '../structures/slash.ts' | import { Interaction } from '../structures/slash.ts' | ||||||
| import { | import { | ||||||
|  |   InteractionPayload, | ||||||
|   InteractionType, |   InteractionType, | ||||||
|   SlashCommandChoice, |   SlashCommandChoice, | ||||||
|   SlashCommandOption, |   SlashCommandOption, | ||||||
|  | @ -12,8 +13,7 @@ import { Collection } from '../utils/collection.ts' | ||||||
| import { Client } from './client.ts' | import { Client } from './client.ts' | ||||||
| import { RESTManager } from './rest.ts' | import { RESTManager } from './rest.ts' | ||||||
| import { SlashModule } from './slashModule.ts' | import { SlashModule } from './slashModule.ts' | ||||||
| import { verify as edverify } from 'https://deno.land/x/ed25519/mod.ts' | import { verify as edverify } from 'https://deno.land/x/ed25519@1.0.1/mod.ts' | ||||||
| import { Buffer } from 'https://deno.land/std@0.80.0/node/buffer.ts' |  | ||||||
| 
 | 
 | ||||||
| export class SlashCommand { | export class SlashCommand { | ||||||
|   slash: SlashCommandsManager |   slash: SlashCommandsManager | ||||||
|  | @ -372,7 +372,9 @@ export interface SlashOptions { | ||||||
|   publicKey?: string |   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 { | export class SlashClient { | ||||||
|   id: string | (() => string) |   id: string | (() => string) | ||||||
|   client?: Client |   client?: Client | ||||||
|  | @ -518,25 +520,52 @@ export class SlashClient { | ||||||
|     cmd.handler(interaction) |     cmd.handler(interaction) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /** Verify HTTP based Interaction */ | ||||||
|   async verifyKey( |   async verifyKey( | ||||||
|     rawBody: string | Uint8Array | Buffer, |     rawBody: string | Uint8Array, | ||||||
|     signature: string, |     signature: string | Uint8Array, | ||||||
|     timestamp: string |     timestamp: string | Uint8Array | ||||||
|   ): Promise<boolean> { |   ): Promise<boolean> { | ||||||
|     if (this.publicKey === undefined) |     if (this.publicKey === undefined) | ||||||
|       throw new Error('Public Key is not present') |       throw new Error('Public Key is not present') | ||||||
|     return edverify( | 
 | ||||||
|       signature, |     const fullBody = new Uint8Array([ | ||||||
|       Buffer.concat([ |       ...(typeof timestamp === 'string' | ||||||
|         Buffer.from(timestamp, 'utf-8'), |         ? encoder.encode(timestamp) | ||||||
|         Buffer.from( |         : timestamp), | ||||||
|           rawBody instanceof Uint8Array |       ...(typeof rawBody === 'string' ? encoder.encode(rawBody) : rawBody) | ||||||
|             ? new TextDecoder().decode(rawBody) |     ]) | ||||||
|             : rawBody | 
 | ||||||
|         ) |     return edverify(signature, fullBody, this.publicKey).catch(() => false) | ||||||
|       ]), |   } | ||||||
|       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> { |   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 */ | /** Function to get Image URL from a resource on Discord CDN */ | ||||||
| export const ImageURL = ( | export const ImageURL = ( | ||||||
|   url: string, |   url: string, | ||||||
|   format: ImageFormats | undefined = 'png', |   format: ImageFormats = 'png', | ||||||
|   size: ImageSize | undefined = 128 |   size: ImageSize = 128 | ||||||
| ): string => { | ): string => { | ||||||
|   size = size === undefined ? 128 : size |  | ||||||
|   if (url.includes('a_')) { |   if (url.includes('a_')) { | ||||||
|     return `${url}.${format === undefined ? 'gif' : format}?size=${size}` |     return `${url}.${format === 'dynamic' ? 'gif' : format}?size=${size}` | ||||||
|   } else return `${url}.${format === 'gif' ? 'png' : 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 { Client } from '../models/client.ts' | ||||||
|  | import { ImageSize } from '../types/cdn.ts' | ||||||
| import { EmojiPayload } from '../types/emoji.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 { Snowflake } from '../utils/snowflake.ts' | ||||||
| import { Base } from './base.ts' | import { Base } from './base.ts' | ||||||
|  | import { ImageURL } from './cdn.ts' | ||||||
| import { Guild } from './guild.ts' | import { Guild } from './guild.ts' | ||||||
| import { Role } from './role.ts' | import { Role } from './role.ts' | ||||||
| import { User } from './user.ts' | import { User } from './user.ts' | ||||||
|  | @ -54,6 +56,18 @@ export class Emoji extends Base { | ||||||
|     this.available = data.available |     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. */ |   /** 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> { |   async edit(data: ModifyGuildEmojiParams): Promise<Emoji> { | ||||||
|     if (this.id === null) throw new Error('Emoji ID is not valid.') |     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 { Application } from './application.ts' | ||||||
| import { | import { | ||||||
|   GUILD_BAN, |   GUILD_BAN, | ||||||
|  |   GUILD_BANNER, | ||||||
|   GUILD_BANS, |   GUILD_BANS, | ||||||
|  |   GUILD_DISCOVERY_SPLASH, | ||||||
|  |   GUILD_ICON, | ||||||
|   GUILD_INTEGRATIONS, |   GUILD_INTEGRATIONS, | ||||||
|   GUILD_PRUNE |   GUILD_PRUNE, | ||||||
|  |   GUILD_SPLASH | ||||||
| } from '../types/endpoint.ts' | } from '../types/endpoint.ts' | ||||||
| import { GuildVoiceStatesManager } from '../managers/guildVoiceStates.ts' | import { GuildVoiceStatesManager } from '../managers/guildVoiceStates.ts' | ||||||
| import { RequestMembersOptions } from '../gateway/index.ts' | import { RequestMembersOptions } from '../gateway/index.ts' | ||||||
|  | @ -42,6 +46,8 @@ import { GuildPresencesManager } from '../managers/presences.ts' | ||||||
| import { TemplatePayload } from '../types/template.ts' | import { TemplatePayload } from '../types/template.ts' | ||||||
| import { Template } from './template.ts' | import { Template } from './template.ts' | ||||||
| import { DiscordAPIError } from '../models/rest.ts' | import { DiscordAPIError } from '../models/rest.ts' | ||||||
|  | import { ImageFormats, ImageSize } from '../types/cdn.ts' | ||||||
|  | import { ImageURL } from './cdn.ts' | ||||||
| 
 | 
 | ||||||
| export class GuildBan extends Base { | export class GuildBan extends Base { | ||||||
|   guild: Guild |   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 |    * Gets Everyone role of the Guild | ||||||
|    */ |    */ | ||||||
|  |  | ||||||
|  | @ -86,28 +86,18 @@ export class InteractionUser extends User { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class Interaction extends SnowflakeBase { | export class Interaction extends SnowflakeBase { | ||||||
|   /** Type of Interaction */ |   /** This will be `SlashClient` in case of `SlashClient#verifyServerRequest` */ | ||||||
|   type: InteractionType |   client: Client | ||||||
|   /** Interaction Token */ |   type: number | ||||||
|   token: string |   token: string | ||||||
|   /** Interaction ID */ |   /** Interaction ID */ | ||||||
|   id: string |   id: string | ||||||
|   /** Data sent with Interaction. Only applies to Application Command */ |   data: InteractionData | ||||||
|   data?: InteractionApplicationCommandData |   channel: GuildTextChannel | ||||||
|   /** Channel in which Interaction was initiated */ |   guild: Guild | ||||||
|   channel?: TextChannel | GuildTextChannel |   member: Member | ||||||
|   /** Guild in which Interaction was initiated */ |   _savedHook?: Webhook | ||||||
|   guild?: Guild |   _respond?: (data: InteractionResponsePayload) => unknown | ||||||
|   /** 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 |  | ||||||
| 
 | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     client: Client, |     client: Client, | ||||||
|  |  | ||||||
|  | @ -145,12 +145,15 @@ export class TextChannel extends Channel { | ||||||
|     emoji: Emoji | string |     emoji: Emoji | string | ||||||
|   ): Promise<void> { |   ): Promise<void> { | ||||||
|     if (emoji instanceof Emoji) { |     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) |     const encodedEmoji = encodeURI(emoji) | ||||||
| 
 | 
 | ||||||
|     await this.client.rest.put( |     await this.client.rest.put( | ||||||
|  | @ -165,11 +168,15 @@ export class TextChannel extends Channel { | ||||||
|     user?: User | Member | string |     user?: User | Member | string | ||||||
|   ): Promise<void> { |   ): Promise<void> { | ||||||
|     if (emoji instanceof Emoji) { |     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 (user !== undefined) { | ||||||
|       if (typeof user !== 'string') { |       if (typeof user !== 'string') { | ||||||
|         user = user.id |         user = user.id | ||||||
|  |  | ||||||
|  | @ -117,7 +117,7 @@ client.on('messageCreate', async (msg: Message) => { | ||||||
|       msg.channel.send('Failed...') |       msg.channel.send('Failed...') | ||||||
|     } |     } | ||||||
|   } else if (msg.content === '!react') { |   } else if (msg.content === '!react') { | ||||||
|     msg.addReaction('🤔') |     msg.addReaction('a:programming:785013658257195008') | ||||||
|   } else if (msg.content === '!wait_for') { |   } else if (msg.content === '!wait_for') { | ||||||
|     msg.channel.send('Send anything!') |     msg.channel.send('Send anything!') | ||||||
|     const [receivedMsg] = await client.waitFor( |     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
 |       // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
 | ||||||
|       msg.member as Member |       msg.member as Member | ||||||
|     ) |     ) | ||||||
|     msg.channel.send( |     msg.channel.send(`Your permissions:\n${permissions.toArray().join('\n')}`) | ||||||
|       Object.entries(permissions.serialize()) |   } else if (msg.content === '!addAllRoles') { | ||||||
|         .map((e) => `${e[0]}: ${e[1] === true ? '`✅`' : '`❌`'}`) |     const roles = await msg.guild?.roles.array() | ||||||
|         .join('\n') |     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 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 } = {} |   #flags: { [name: string]: number | bigint } = {} | ||||||
|   bitfield: bigint |   bitfield: bigint | ||||||
| 
 | 
 | ||||||
|   constructor(flags: { [name: string]: number | bigint }, bits: any) { |   constructor( | ||||||
|  |     flags: { [name: string]: number | bigint }, | ||||||
|  |     bits: BitFieldResolvable | ||||||
|  |   ) { | ||||||
|     this.#flags = flags |     this.#flags = flags | ||||||
|     this.bitfield = BitField.resolve(this.#flags, bits) |     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 (bit instanceof BitField) return this.resolve(flags, bit.bitfield) | ||||||
|     if (Array.isArray(bit)) |     if (Array.isArray(bit)) | ||||||
|       return (bit.map as any)((p: any) => this.resolve(flags, p)).reduce( |       return (bit.map as any)((p: any) => this.resolve(flags, p)).reduce( | ||||||
|         (prev: any, p: any) => prev | p, |         (prev: bigint, p: bigint) => prev | p, | ||||||
|         0 |         0n | ||||||
|       ) |       ) | ||||||
|     if (typeof bit === 'string' && typeof flags[bit] !== 'undefined') |     if (typeof bit === 'string' && typeof flags[bit] !== 'undefined') | ||||||
|       return flags[bit] |       return BigInt(flags[bit]) | ||||||
|     const error = new RangeError('BITFIELD_INVALID') |     const error = new RangeError('BITFIELD_INVALID') | ||||||
|     throw error |     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