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…
Reference in a new issue