Added GuildEmojisManager, Guild#emojis, and refactor RESTManager

This commit is contained in:
DjDeveloperr 2020-11-08 16:40:33 +05:30
parent 4796252798
commit c2e690fe78
8 changed files with 343 additions and 237 deletions

View file

@ -10,6 +10,17 @@ export class EmojisManager extends BaseManager<EmojiPayload, Emoji> {
super(client, `emojis`, Emoji) super(client, `emojis`, Emoji)
} }
async get (key: string): Promise<Emoji | undefined> {
const raw = await this._get(key)
if (raw === undefined) return
const emoji = new this.DataType(this.client, raw)
if((raw as any).guild_id !== undefined) {
const guild = await this.client.guilds.get((raw as any).guild_id)
if(guild !== undefined) emoji.guild = guild
}
return emoji
}
async fetch (guildID: string, id: string): Promise<Emoji> { async fetch (guildID: string, id: string): Promise<Emoji> {
return await new Promise((resolve, reject) => { return await new Promise((resolve, reject) => {
this.client.rest this.client.rest

View file

@ -0,0 +1,91 @@
import { Client } from '../models/client.ts'
import { Emoji } from "../structures/emoji.ts"
import { Guild } from '../structures/guild.ts'
import { Role } from "../structures/role.ts"
import { EmojiPayload } from "../types/emoji.ts"
import { CHANNEL, GUILD_EMOJI, GUILD_EMOJIS } from '../types/endpoint.ts'
import { BaseChildManager } from './baseChild.ts'
import { EmojisManager } from "./emojis.ts"
import { fetchAuto } from 'https://raw.githubusercontent.com/DjDeveloperr/fetch-base64/main/mod.ts'
export class GuildEmojisManager extends BaseChildManager<
EmojiPayload,
Emoji
> {
guild: Guild
constructor(client: Client, parent: EmojisManager, guild: Guild) {
super(client, parent as any)
this.guild = guild
}
async get(id: string): Promise<Emoji | undefined> {
const res = await this.parent.get(id)
if (res !== undefined && res.guild?.id === this.guild.id) return res
else return undefined
}
async delete(id: string): Promise<boolean> {
return this.client.rest.delete(CHANNEL(id))
}
async fetch(id: string): Promise<Emoji | undefined> {
return await new Promise((resolve, reject) => {
this.client.rest
.get(GUILD_EMOJI(this.guild.id, id))
.then(async data => {
const emoji = new Emoji(this.client, data as EmojiPayload)
data.guild_id = this.guild.id
await this.set(id, data as EmojiPayload)
emoji.guild = this.guild
resolve(emoji)
})
.catch(e => reject(e))
})
}
async create(name: string, url: string, roles?: Role[] | string[] | string): Promise<Emoji | undefined> {
let data = url
if (!data.startsWith("data:")) {
data = await fetchAuto(url)
}
return await new Promise((resolve, reject) => {
let roleIDs: string[] = []
if (roles !== undefined && typeof roles === "string") roleIDs = [roles]
else if (roles !== undefined) {
if(roles?.length === 0) reject(new Error("Empty Roles array was provided"))
if(roles[0] instanceof Role) roleIDs = (roles as any).map((r: Role) => r.id)
else roleIDs = roles as string[]
} else roles = [this.guild.id]
this.client.rest
.post(GUILD_EMOJIS(this.guild.id), {
name,
image: data,
roles: roleIDs
})
.then(async data => {
const emoji = new Emoji(this.client, data as EmojiPayload)
data.guild_id = this.guild.id
await this.set(data.id, data as EmojiPayload)
emoji.guild = this.guild
resolve(emoji)
})
.catch(e => reject(e))
})
}
async array(): Promise<Emoji[]> {
const arr = (await this.parent.array()) as Emoji[]
return arr.filter(
(c: any) => c.guild !== undefined && c.guild.id === this.guild.id
) as any
}
async flush(): Promise<boolean> {
const arr = await this.array()
for (const elem of arr) {
this.parent.delete(elem.id)
}
return true
}
}

View file

@ -2,6 +2,9 @@ import { delay } from '../utils/index.ts'
import * as baseEndpoints from '../consts/urlsAndVersions.ts' import * as baseEndpoints from '../consts/urlsAndVersions.ts'
import { Client } from './client.ts' import { Client } from './client.ts'
import { getBuildInfo } from '../utils/buildInfo.ts' import { getBuildInfo } from '../utils/buildInfo.ts'
import { Collection } from "../utils/collection.ts"
export type RequestMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete'
export enum HttpResponseCode { export enum HttpResponseCode {
Ok = 200, Ok = 200,
@ -17,122 +20,102 @@ export enum HttpResponseCode {
GatewayUnavailable = 502 GatewayUnavailable = 502
} }
export type RequestMethods = export interface RequestHeaders {
| 'get' [name: string]: string
| 'post' }
| 'put'
| 'patch'
| 'head'
| 'delete'
export interface QueuedRequest { export interface QueuedItem {
callback: () => Promise< onComplete: () => Promise<{
| {
rateLimited: any rateLimited: any
beforeFetch: boolean bucket?: string | null
bucketID?: string | null before: boolean
} } | undefined>
| undefined bucket?: string | null
>
bucketID?: string | null
url: string url: string
} }
export interface RateLimitedPath { export interface RateLimit {
url: string url: string
resetTimestamp: number resetAt: number
bucketID: string | null bucket: string | null
} }
export class RESTManager { export class RESTManager {
client: Client client: Client
globallyRateLimited: boolean = false queues: { [key: string]: QueuedItem[] } = {}
queueInProcess: boolean = false rateLimits = new Collection<string, RateLimit>()
pathQueues: { [key: string]: QueuedRequest[] } = {} globalRateLimit: boolean = false
ratelimitedPaths = new Map<string, RateLimitedPath>() processing: boolean = false
constructor (client: Client) { constructor(client: Client) {
this.client = client this.client = client
setTimeout(() => this.processRateLimitedPaths, 1000) // eslint-disable-next-line @typescript-eslint/no-floating-promises
this.handleRateLimits()
} }
async processRateLimitedPaths (): Promise<void> { async checkQueues(): Promise<void> {
const now = Date.now() Object.entries(this.queues).forEach(([key, value]) => {
this.ratelimitedPaths.forEach((value, key) => { if (value.length === 0) {
if (value.resetTimestamp > now) return // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
this.ratelimitedPaths.delete(key) delete this.queues[key]
if (key === 'global') this.globallyRateLimited = false }
}) })
} }
addToQueue (request: QueuedRequest): void { queue(request: QueuedItem): void {
const route = request.url.substring( const route = request.url.substring(
// eslint seriously? // eslint seriously?
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
baseEndpoints.DISCORD_API_URL.length + 1 baseEndpoints.DISCORD_API_URL.length + 1
) )
const parts = route.split('/') const parts = route.split('/')
// Remove the major param
parts.shift() parts.shift()
const [id] = parts const [id] = parts
if (this.pathQueues[id] !== undefined) { if (this.queues[id] !== undefined) {
this.pathQueues[id].push(request) this.queues[id].push(request)
} else { } else {
this.pathQueues[id] = [request] this.queues[id] = [request]
} }
} }
async cleanupQueues (): Promise<void> { async processQueue(): Promise<void> {
Object.entries(this.pathQueues).forEach(([key, value]) => { if (Object.keys(this.queues).length !== 0 && !this.globalRateLimit) {
if (value.length === 0) {
// Remove it entirely
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete this.pathQueues[key]
}
})
}
async processQueue (): Promise<void> {
if (
Object.keys(this.pathQueues).length !== 0 &&
!this.globallyRateLimited
) {
await Promise.allSettled( await Promise.allSettled(
Object.values(this.pathQueues).map(async pathQueue => { Object.values(this.queues).map(async pathQueue => {
const request = pathQueue.shift() const request = pathQueue.shift()
if (request === undefined) return if (request === undefined) return
const rateLimitedURLResetIn = await this.checkRatelimits(request.url) const rateLimitedURLResetIn = await this.isRateLimited(request.url)
if (typeof request.bucketID === 'string') { if (typeof request.bucket === 'string') {
const rateLimitResetIn = await this.checkRatelimits( const rateLimitResetIn = await this.isRateLimited(
request.bucketID request.bucket
) )
if (rateLimitResetIn !== false) { if (rateLimitResetIn !== false) {
// This request is still rate limited read to queue // This request is still rate limited read to queue
this.addToQueue(request) this.queue(request)
} else { } else {
// This request is not rate limited so it should be run // This request is not rate limited so it should be run
const result = await request.callback() const result = await request.onComplete()
if (result?.rateLimited !== undefined) { if (result?.rateLimited !== undefined) {
this.addToQueue({ this.queue({
...request, ...request,
bucketID: result.bucketID ?? request.bucketID bucket: result.bucket ?? request.bucket
}) })
} }
} }
} else { } else {
if (rateLimitedURLResetIn !== false) { if (rateLimitedURLResetIn !== false) {
// This URL is rate limited readd to queue // This URL is rate limited readd to queue
this.addToQueue(request) this.queue(request)
} else { } else {
// This request has no bucket id so it should be processed // This request has no bucket id so it should be processed
const result = await request.callback() const result = await request.onComplete()
if (result?.rateLimited !== undefined) { if (result?.rateLimited !== undefined) {
this.addToQueue({ this.queue({
...request, ...request,
bucketID: result.bucketID ?? request.bucketID bucket: result.bucket ?? request.bucket
}) })
} }
} }
@ -141,27 +124,28 @@ export class RESTManager {
) )
} }
if (Object.keys(this.pathQueues).length !== 0) { if (Object.keys(this.queues).length !== 0) {
await delay(1000) await delay(1000)
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
this.processQueue() this.processQueue()
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
this.cleanupQueues() this.checkQueues()
} else this.queueInProcess = false } else this.processing = false
} }
createRequestBody ( prepare(
body: any, body: any,
method: RequestMethods method: RequestMethods
): { [key: string]: any } { ): { [key: string]: any } {
const headers: { [key: string]: string } = {
Authorization: `Bot ${this.client.token}`, const headers: RequestHeaders = {
'User-Agent': `DiscordBot (harmony)` 'Authorization': `Bot ${this.client.token}`,
'User-Agent': `DiscordBot (harmony, https://github.com/harmony-org/harmony)`
} }
if (this.client.token === undefined) delete headers.Authorization if (this.client.token === undefined) delete headers.Authorization
if (method === 'get') body = undefined if (method === 'get' || method === 'head' || method === 'delete') body = undefined
if (body?.reason !== undefined) { if (body?.reason !== undefined) {
headers['X-Audit-Log-Reason'] = encodeURIComponent(body.reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(body.reason)
@ -203,40 +187,117 @@ export class RESTManager {
return data return data
} }
async checkRatelimits (url: string): Promise<number | false> { async isRateLimited(url: string): Promise<number | false> {
const ratelimited = this.ratelimitedPaths.get(url) const global = this.rateLimits.get('global')
const global = this.ratelimitedPaths.get('global') const rateLimited = this.rateLimits.get(url)
const now = Date.now() const now = Date.now()
if (ratelimited !== undefined && now < ratelimited.resetTimestamp) { if (rateLimited !== undefined && now < rateLimited.resetAt) {
return ratelimited.resetTimestamp - now return rateLimited.resetAt - now
} }
if (global !== undefined && now < global.resetTimestamp) { if (global !== undefined && now < global.resetAt) {
return global.resetTimestamp - now return global.resetAt - now
} }
return false return false
} }
async runMethod ( processHeaders(url: string, headers: Headers): string | null | undefined {
let rateLimited = false
const global = headers.get('x-ratelimit-global')
const bucket = headers.get('x-ratelimit-bucket')
const remaining = headers.get('x-ratelimit-remaining')
const resetAt = headers.get('x-ratelimit-reset')
const retryAfter = headers.get('retry-after')
if (remaining !== null && remaining === '0') {
rateLimited = true
this.rateLimits.set(url, {
url,
resetAt: Number(resetAt) * 1000,
bucket
})
if (bucket !== null) {
this.rateLimits.set(bucket, {
url,
resetAt: Number(resetAt) * 1000,
bucket
})
}
}
if (global !== null) {
const reset = Date.now() + Number(retryAfter)
this.globalRateLimit = true
rateLimited = true
this.rateLimits.set('global', {
url: 'global',
resetAt: reset,
bucket
})
if (bucket !== null) {
this.rateLimits.set(bucket, {
url: 'global',
resetAt: reset,
bucket
})
}
}
return rateLimited ? bucket : undefined
}
handleStatusCode(
response: Response
): undefined | boolean {
const status = response.status
if (
(status >= 200 && status < 400) ||
status === HttpResponseCode.TooManyRequests
) {
return true
}
if (status === HttpResponseCode.Unauthorized)
throw new Error('Request was not successful. Invalid Token.')
switch (status) {
case HttpResponseCode.BadRequest:
case HttpResponseCode.Unauthorized:
case HttpResponseCode.Forbidden:
case HttpResponseCode.NotFound:
case HttpResponseCode.MethodNotAllowed:
throw new Error('Request Client Error.')
case HttpResponseCode.GatewayUnavailable:
throw new Error('Request Server Error.')
}
// left are all unknown
throw new Error('Request Unknown Error')
}
async make(
method: RequestMethods, method: RequestMethods,
url: string, url: string,
body?: unknown, body?: unknown,
retryCount = 0, retryCount = 0,
bucketID?: string | null bucket?: string | null
): Promise<any> { ): Promise<any> {
const errorStack = new Error('Location In Your Files:')
Error.captureStackTrace(errorStack)
return await new Promise((resolve, reject) => { return await new Promise((resolve, reject) => {
const callback = async (): Promise<undefined | any> => { const onComplete = async (): Promise<undefined | any> => {
try { try {
const rateLimitResetIn = await this.checkRatelimits(url) const rateLimitResetIn = await this.isRateLimited(url)
if (rateLimitResetIn !== false) { if (rateLimitResetIn !== false) {
return { return {
rateLimited: rateLimitResetIn, rateLimited: rateLimitResetIn,
beforeFetch: true, before: true,
bucketID bucket
} }
} }
@ -259,13 +320,12 @@ export class RESTManager {
urlToUse = split[0] + '//canary.' + split[1] urlToUse = split[0] + '//canary.' + split[1]
} }
const requestData = this.createRequestBody(body, method) const requestData = this.prepare(body, method)
const response = await fetch(urlToUse, requestData) const response = await fetch(urlToUse, requestData)
const bucketIDFromHeaders = this.processHeaders(url, response.headers) const bucketFromHeaders = this.processHeaders(url, response.headers)
this.handleStatusCode(response, errorStack) this.handleStatusCode(response)
// Sometimes Discord returns an empty 204 response that can't be made to JSON.
if (response.status === 204) return resolve(undefined) if (response.status === 204) return resolve(undefined)
const json = await response.json() const json = await response.json()
@ -279,8 +339,8 @@ export class RESTManager {
return { return {
rateLimited: json.retry_after, rateLimited: json.retry_after,
beforeFetch: false, before: false,
bucketID: bucketIDFromHeaders bucket: bucketFromHeaders
} }
} }
return resolve(json) return resolve(json)
@ -289,132 +349,45 @@ export class RESTManager {
} }
} }
this.addToQueue({ this.queue({
callback, onComplete,
bucketID, bucket,
url url
}) })
if (!this.queueInProcess) { if (!this.processing) {
this.queueInProcess = true this.processing = true
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
this.processQueue() this.processQueue()
} }
}) })
} }
async logErrors (response: Response, errorStack?: unknown): Promise<void> { async handleRateLimits(): Promise<void> {
try { const now = Date.now()
const error = await response.json() this.rateLimits.forEach((value, key) => {
console.error(error) if (value.resetAt > now) return
} catch { this.rateLimits.delete(key)
console.error(response) if (key === 'global') this.globalRateLimit = false
}
}
handleStatusCode (
response: Response,
errorStack?: unknown
): undefined | boolean {
const status = response.status
if (
(status >= 200 && status < 400) ||
status === HttpResponseCode.TooManyRequests
) {
return true
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.logErrors(response, errorStack)
if (status === HttpResponseCode.Unauthorized)
throw new Error('Request was not successful. Invalid Token.')
switch (status) {
case HttpResponseCode.BadRequest:
case HttpResponseCode.Unauthorized:
case HttpResponseCode.Forbidden:
case HttpResponseCode.NotFound:
case HttpResponseCode.MethodNotAllowed:
throw new Error('Request Client Error.')
case HttpResponseCode.GatewayUnavailable:
throw new Error('Request Server Error.')
}
// left are all unknown
throw new Error('Request Unknown Error')
}
processHeaders (url: string, headers: Headers): string | null | undefined {
let ratelimited = false
// Get all useful headers
const remaining = headers.get('x-ratelimit-remaining')
const resetTimestamp = headers.get('x-ratelimit-reset')
const retryAfter = headers.get('retry-after')
const global = headers.get('x-ratelimit-global')
const bucketID = headers.get('x-ratelimit-bucket')
// If there is no remaining rate limit for this endpoint, we save it in cache
if (remaining !== null && remaining === '0') {
ratelimited = true
this.ratelimitedPaths.set(url, {
url,
resetTimestamp: Number(resetTimestamp) * 1000,
bucketID
})
if (bucketID !== null) {
this.ratelimitedPaths.set(bucketID, {
url,
resetTimestamp: Number(resetTimestamp) * 1000,
bucketID
}) })
} }
async get(url: string, body?: unknown): Promise<any> {
return await this.make('get', url, body)
} }
// If there is no remaining global limit, we save it in cache async post(url: string, body?: unknown): Promise<any> {
if (global !== null) { return await this.make('post', url, body)
const reset = Date.now() + Number(retryAfter)
this.globallyRateLimited = true
ratelimited = true
this.ratelimitedPaths.set('global', {
url: 'global',
resetTimestamp: reset,
bucketID
})
if (bucketID !== null) {
this.ratelimitedPaths.set(bucketID, {
url: 'global',
resetTimestamp: reset,
bucketID
})
}
} }
return ratelimited ? bucketID : undefined async delete(url: string, body?: unknown): Promise<any> {
return await this.make('delete', url, body)
} }
async get (url: string, body?: unknown): Promise<any> { async patch(url: string, body?: unknown): Promise<any> {
return await this.runMethod('get', url, body) return await this.make('patch', url, body)
} }
async post (url: string, body?: unknown): Promise<any> { async put(url: string, body?: unknown): Promise<any> {
return await this.runMethod('post', url, body) return await this.make('put', url, body)
}
async delete (url: string, body?: unknown): Promise<any> {
return await this.runMethod('delete', url, body)
}
async patch (url: string, body?: unknown): Promise<any> {
return await this.runMethod('patch', url, body)
}
async put (url: string, body?: unknown): Promise<any> {
return await this.runMethod('put', url, body)
} }
} }

View file

@ -2,6 +2,7 @@ import { Client } from '../models/client.ts'
import { EmojiPayload } from '../types/emoji.ts' import { EmojiPayload } from '../types/emoji.ts'
import { USER } from '../types/endpoint.ts' import { USER } from '../types/endpoint.ts'
import { Base } from './base.ts' import { Base } from './base.ts'
import { Guild } from "./guild.ts"
import { User } from './user.ts' import { User } from './user.ts'
export class Emoji extends Base { export class Emoji extends Base {
@ -9,6 +10,7 @@ export class Emoji extends Base {
name: string name: string
roles?: string[] roles?: string[]
user?: User user?: User
guild?: Guild
requireColons?: boolean requireColons?: boolean
managed?: boolean managed?: boolean
animated?: boolean animated?: boolean
@ -20,17 +22,16 @@ export class Emoji extends Base {
} else return `<a:${this.name}:${this.id}>` } else return `<a:${this.name}:${this.id}>`
} }
toString(): string {
return this.getEmojiString
}
constructor (client: Client, data: EmojiPayload) { constructor (client: Client, data: EmojiPayload) {
super(client, data) super(client, data)
this.id = data.id this.id = data.id
this.name = data.name this.name = data.name
if(data.user !== undefined) this.user = new User(this.client, data.user)
this.roles = data.roles this.roles = data.roles
if (data.user !== undefined) {
User.autoInit(this.client, {
endpoint: USER,
restURLfuncArgs: [data.user.id]
}).then(user => (this.user = user))
}
this.requireColons = data.require_colons this.requireColons = data.require_colons
this.managed = data.managed this.managed = data.managed
this.animated = data.animated this.animated = data.animated

View file

@ -6,7 +6,8 @@ import { VoiceState } from './voiceState.ts'
import { RolesManager } from '../managers/roles.ts' import { RolesManager } from '../managers/roles.ts'
import { GuildChannelsManager } from '../managers/guildChannels.ts' import { GuildChannelsManager } from '../managers/guildChannels.ts'
import { MembersManager } from '../managers/members.ts' import { MembersManager } from '../managers/members.ts'
import { Emoji } from "./emoji.ts" import { Role } from "./role.ts"
import { GuildEmojisManager } from "../managers/guildEmojis.ts"
export class Guild extends Base { export class Guild extends Base {
id: string id: string
@ -27,7 +28,7 @@ export class Guild extends Base {
defaultMessageNotifications?: string defaultMessageNotifications?: string
explicitContentFilter?: string explicitContentFilter?: string
roles: RolesManager roles: RolesManager
emojis: Emoji[] = [] emojis: GuildEmojisManager
features?: GuildFeatures[] features?: GuildFeatures[]
mfaLevel?: string mfaLevel?: string
applicationID?: string applicationID?: string
@ -66,6 +67,7 @@ export class Guild extends Base {
this this
) )
this.roles = new RolesManager(this.client, this) this.roles = new RolesManager(this.client, this)
this.emojis = new GuildEmojisManager(this.client, this.client.emojis, this)
if (!this.unavailable) { if (!this.unavailable) {
this.name = data.name this.name = data.name
@ -208,4 +210,8 @@ export class Guild extends Base {
data.approximate_presence_count ?? this.approximatePresenceCount data.approximate_presence_count ?? this.approximatePresenceCount
} }
} }
async getEveryoneRole(): Promise<Role> {
return (await this.roles.array().then(arr => arr?.sort((b, a) => a.position - b.position)[0]) as any) as Role
}
} }

View file

@ -1,5 +1,6 @@
import { CommandClient, Intents } from '../../mod.ts' import { CommandClient, Intents } from '../../mod.ts'
import PingCommand from './cmds/ping.ts' import PingCommand from './cmds/ping.ts'
import AddEmojiCommand from './cmds/addemoji.ts'
import UserinfoCommand from './cmds/userinfo.ts' import UserinfoCommand from './cmds/userinfo.ts'
import { TOKEN } from './config.ts' import { TOKEN } from './config.ts'
@ -14,9 +15,10 @@ client.on('ready', () => {
console.log(`[Login] Logged in as ${client.user?.tag}!`) console.log(`[Login] Logged in as ${client.user?.tag}!`)
}) })
client.on("commandError", console.log) client.on("commandError", console.error)
client.commands.add(PingCommand) client.commands.add(PingCommand)
client.commands.add(UserinfoCommand) client.commands.add(UserinfoCommand)
client.commands.add(AddEmojiCommand)
client.connect(TOKEN, Intents.All) client.connect(TOKEN, Intents.All)

22
src/test/cmds/addemoji.ts Normal file
View file

@ -0,0 +1,22 @@
import { Command } from '../../../mod.ts'
import { CommandContext } from '../../models/command.ts'
export default class AddEmojiCommand extends Command {
name = 'addemoji'
aliases = [ 'ae', 'emojiadd' ]
args = 2
guildOnly = true
execute(ctx: CommandContext): any {
const name = ctx.args[0]
if(name === undefined) return ctx.message.reply('No name was given!')
const url = ctx.argString.slice(name.length).trim()
if(url === '') return ctx.message.reply('No URL was given!')
ctx.message.guild?.emojis.create(name, url).then(emoji => {
if(emoji === undefined) throw new Error('Unknown')
ctx.message.reply(`Successfuly added emoji ${emoji.toString()} ${emoji.name}!`)
}).catch(e => {
ctx.message.reply(`Failed to add emoji. Reason: ${e.message}`)
})
}
}

View file

@ -14,6 +14,8 @@ const GUILD_WIDGET = (guildID: string): string =>
`${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/widget` `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/widget`
const GUILD_EMOJI = (guildID: string, emojiID: string): string => const GUILD_EMOJI = (guildID: string, emojiID: string): string =>
`${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/emojis/${emojiID}` `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/emojis/${emojiID}`
const GUILD_EMOJIS = (guildID: string): string =>
`${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/emojis`
const GUILD_ROLE = (guildID: string, roleID: string): string => const GUILD_ROLE = (guildID: string, roleID: string): string =>
`${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/roles/${roleID}` `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/roles/${roleID}`
const GUILD_ROLES = (guildID: string): string => const GUILD_ROLES = (guildID: string): string =>
@ -172,8 +174,6 @@ const TEAM_ICON = (teamID: string, iconID: string): string =>
// Emoji Endpoints // Emoji Endpoints
const EMOJI = (guildID: string, emojiID: string): string => const EMOJI = (guildID: string, emojiID: string): string =>
`${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/emojis/${emojiID}` `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/emojis/${emojiID}`
const EMOJIS = (guildID: string): string =>
`${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/emojis`
// Template Endpoint // Template Endpoint
const TEMPLATE = (templateCODE: string): string => const TEMPLATE = (templateCODE: string): string =>
@ -259,7 +259,7 @@ export default [
ACHIEVEMENT_ICON, ACHIEVEMENT_ICON,
TEAM_ICON, TEAM_ICON,
EMOJI, EMOJI,
EMOJIS, GUILD_EMOJIS,
TEMPLATE, TEMPLATE,
INVITE, INVITE,
VOICE_REGIONS VOICE_REGIONS
@ -305,6 +305,7 @@ export {
CHANNEL_PIN, CHANNEL_PIN,
CHANNEL_PINS, CHANNEL_PINS,
CHANNEL_PERMISSION, CHANNEL_PERMISSION,
GUILD_EMOJIS,
CHANNEL_TYPING, CHANNEL_TYPING,
GROUP_RECIPIENT, GROUP_RECIPIENT,
CURRENT_USER, CURRENT_USER,
@ -333,7 +334,6 @@ export {
ACHIEVEMENT_ICON, ACHIEVEMENT_ICON,
TEAM_ICON, TEAM_ICON,
EMOJI, EMOJI,
EMOJIS,
TEMPLATE, TEMPLATE,
INVITE, INVITE,
VOICE_REGIONS VOICE_REGIONS