This commit is contained in:
DjDeveloperr 2021-03-14 15:15:37 +05:30
commit 1b77ea0411
22 changed files with 347 additions and 105 deletions

3
.eggignore Normal file
View file

@ -0,0 +1,3 @@
extends .gitignore
./src/test/**/*

View file

@ -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
View 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
View file

@ -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'

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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()
}
} }

View file

@ -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)

View file

@ -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 = []

View file

@ -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'))
} }

View file

@ -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> {

View file

@ -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}`
} }

View file

@ -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.')

View file

@ -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
*/ */

View file

@ -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,

View file

@ -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

View file

@ -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
View 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!'
}
})
})

View file

@ -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'

View file

@ -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
View 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
}