From 00baa2759e69cb92cce460b6fdac71299dd109f9 Mon Sep 17 00:00:00 2001 From: Helloyunho Date: Mon, 2 Nov 2020 15:58:23 +0900 Subject: [PATCH] Fix some lint errors and make more runtime errors(for now) --- .gitignore | 5 +- src/gateway/handlers/channelCreate.ts | 3 +- src/gateway/handlers/channelDelete.ts | 5 +- src/gateway/handlers/channelPinsUpdate.ts | 14 +- src/gateway/handlers/channelUpdate.ts | 3 +- src/gateway/handlers/guildBanAdd.ts | 13 +- src/gateway/handlers/guildBanRemove.ts | 3 +- src/gateway/handlers/guildCreate.ts | 11 +- src/gateway/handlers/guildDelete.ts | 8 +- src/gateway/handlers/guildEmojiUpdate.ts | 14 + src/gateway/handlers/guildUpdate.ts | 12 +- src/gateway/handlers/messageCreate.ts | 25 +- src/gateway/index.ts | 132 +++-- src/managers/BaseManager.ts | 25 +- src/managers/ChannelsManager.ts | 37 +- src/managers/EmojisManager.ts | 31 +- src/managers/GatewayCache.ts | 41 +- src/managers/GuildsManager.ts | 31 +- src/managers/MessagesManager.ts | 60 +- src/managers/RolesManager.ts | 34 +- src/managers/UsersManager.ts | 31 +- src/models/CacheAdapter.ts | 95 +-- src/models/client.ts | 37 +- src/models/rest.ts | 691 +++++++++++----------- src/structures/channel.ts | 1 - src/structures/dmChannel.ts | 1 - src/structures/groupChannel.ts | 1 - src/structures/guild.ts | 5 +- src/structures/guildCategoryChannel.ts | 6 +- src/structures/guildTextChannel.ts | 1 - src/structures/guildVoiceChannel.ts | 1 - src/structures/message.ts | 25 +- src/structures/role.ts | 1 - src/structures/textChannel.ts | 48 +- src/structures/user.ts | 9 +- src/structures/voicestate.ts | 1 - src/types/gateway.ts | 149 ++--- src/types/gatewayTypes.ts | 396 ------------- src/types/permissionFlags.ts | 2 + src/types/presence.ts | 24 +- 40 files changed, 853 insertions(+), 1179 deletions(-) create mode 100644 src/gateway/handlers/guildEmojiUpdate.ts delete mode 100644 src/types/gatewayTypes.ts diff --git a/.gitignore b/.gitignore index 405031f..c08a6ec 100644 --- a/.gitignore +++ b/.gitignore @@ -109,4 +109,7 @@ yarn.lock # PRIVACY XDDDD src/test/config.ts -.vscode \ No newline at end of file +.vscode + +# macOS is shit xD +**/.DS_Store diff --git a/src/gateway/handlers/channelCreate.ts b/src/gateway/handlers/channelCreate.ts index c65ec3d..6ab641a 100644 --- a/src/gateway/handlers/channelCreate.ts +++ b/src/gateway/handlers/channelCreate.ts @@ -1,9 +1,10 @@ import { Gateway, GatewayEventHandler } from '../index.ts' import getChannelByType from '../../utils/getChannelByType.ts' +import { ChannelPayload } from '../../types/channel.ts' export const channelCreate: GatewayEventHandler = async ( gateway: Gateway, - d: any + d: ChannelPayload ) => { const channel = getChannelByType(gateway.client, d) if (channel !== undefined) { diff --git a/src/gateway/handlers/channelDelete.ts b/src/gateway/handlers/channelDelete.ts index 71e7b32..494e4cf 100644 --- a/src/gateway/handlers/channelDelete.ts +++ b/src/gateway/handlers/channelDelete.ts @@ -1,9 +1,10 @@ import { Gateway, GatewayEventHandler } from '../index.ts' import { Channel } from '../../structures/channel.ts' +import { ChannelPayload } from '../../types/channel.ts' -export const channelDelete: GatewayEventHandler = async( +export const channelDelete: GatewayEventHandler = async ( gateway: Gateway, - d: any + d: ChannelPayload ) => { const channel: Channel = await gateway.client.channels.get(d.id) if (channel !== undefined) { diff --git a/src/gateway/handlers/channelPinsUpdate.ts b/src/gateway/handlers/channelPinsUpdate.ts index a5f3d6c..745ff1b 100644 --- a/src/gateway/handlers/channelPinsUpdate.ts +++ b/src/gateway/handlers/channelPinsUpdate.ts @@ -1,19 +1,21 @@ import { Gateway, GatewayEventHandler } from '../index.ts' -import cache from '../../models/cache.ts' import { TextChannel } from '../../structures/textChannel.ts' -import { ChannelPayload } from "../../types/channel.ts" +import { ChannelPinsUpdatePayload } from '../../types/gateway.ts' -export const channelPinsUpdate: GatewayEventHandler = async( +export const channelPinsUpdate: GatewayEventHandler = async ( gateway: Gateway, - d: any + d: ChannelPinsUpdatePayload ) => { const after: TextChannel = await gateway.client.channels.get(d.channel_id) if (after !== undefined) { const before = after.refreshFromData({ last_pin_timestamp: d.last_pin_timestamp }) - const raw = await gateway.client.channels._get(d.channel_id) ; - await gateway.client.channels.set(after.id, Object.assign(raw, { last_pin_timestamp: d.last_pin_timestamp })) + const raw = await gateway.client.channels._get(d.channel_id) + await gateway.client.channels.set( + after.id, + Object.assign(raw, { last_pin_timestamp: d.last_pin_timestamp }) + ) gateway.client.emit('channelPinsUpdate', before, after) } } diff --git a/src/gateway/handlers/channelUpdate.ts b/src/gateway/handlers/channelUpdate.ts index 27372c9..9a75e61 100644 --- a/src/gateway/handlers/channelUpdate.ts +++ b/src/gateway/handlers/channelUpdate.ts @@ -1,10 +1,11 @@ import { Channel } from '../../structures/channel.ts' +import { ChannelPayload } from '../../types/channel.ts' import getChannelByType from '../../utils/getChannelByType.ts' import { Gateway, GatewayEventHandler } from '../index.ts' export const channelUpdate: GatewayEventHandler = async ( gateway: Gateway, - d: any + d: ChannelPayload ) => { const oldChannel: Channel = await gateway.client.channels.get(d.id) diff --git a/src/gateway/handlers/guildBanAdd.ts b/src/gateway/handlers/guildBanAdd.ts index 6446050..699b23d 100644 --- a/src/gateway/handlers/guildBanAdd.ts +++ b/src/gateway/handlers/guildBanAdd.ts @@ -1,14 +1,19 @@ import { Gateway, GatewayEventHandler } from '../index.ts' -import cache from '../../models/cache.ts' import { Guild } from '../../structures/guild.ts' import { User } from '../../structures/user.ts' +import { GuildBanAddPayload } from '../../types/gateway.ts' -export const guildBanAdd: GatewayEventHandler = (gateway: Gateway, d: any) => { - const guild: Guild = cache.get('guild', d.guild_id) +export const guildBanAdd: GatewayEventHandler = async ( + gateway: Gateway, + d: GuildBanAddPayload +) => { + const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) const user: User = - cache.get('user', d.user.id) ?? new User(gateway.client, d.user) + (await gateway.client.users.get(d.user.id)) ?? + new User(gateway.client, d.user) if (guild !== undefined) { + guild.members = guild.members?.filter(member => member.id !== d.user.id) gateway.client.emit('guildBanAdd', guild, user) } } diff --git a/src/gateway/handlers/guildBanRemove.ts b/src/gateway/handlers/guildBanRemove.ts index 904f383..09d8640 100644 --- a/src/gateway/handlers/guildBanRemove.ts +++ b/src/gateway/handlers/guildBanRemove.ts @@ -2,10 +2,11 @@ import { Gateway, GatewayEventHandler } from '../index.ts' import cache from '../../models/cache.ts' import { Guild } from '../../structures/guild.ts' import { User } from '../../structures/user.ts' +import { GuildBanRemovePayload } from '../../types/gateway.ts' export const guildBanRemove: GatewayEventHandler = ( gateway: Gateway, - d: any + d: GuildBanRemovePayload ) => { const guild: Guild = cache.get('guild', d.guild_id) const user: User = diff --git a/src/gateway/handlers/guildCreate.ts b/src/gateway/handlers/guildCreate.ts index 48f9f7a..d885db6 100644 --- a/src/gateway/handlers/guildCreate.ts +++ b/src/gateway/handlers/guildCreate.ts @@ -1,16 +1,19 @@ import { Gateway, GatewayEventHandler } from '../index.ts' import { Guild } from '../../structures/guild.ts' -import { GuildPayload } from "../../types/guild.ts" +import { GuildPayload } from '../../types/guild.ts' -export const guildCreate: GatewayEventHandler = async(gateway: Gateway, d: any) => { - let guild: Guild | void = await gateway.client.guilds.get(d.id) +export const guildCreate: GatewayEventHandler = async ( + gateway: Gateway, + d: GuildPayload +) => { + let guild: Guild | undefined = await gateway.client.guilds.get(d.id) if (guild !== undefined) { // It was just lazy load, so we don't fire the event as its gonna fire for every guild bot is in await gateway.client.guilds.set(d.id, d) guild.refreshFromData(d) } else { await gateway.client.guilds.set(d.id, d) - guild = new Guild(gateway.client, d as GuildPayload) + guild = new Guild(gateway.client, d) gateway.client.emit('guildCreate', guild) } } diff --git a/src/gateway/handlers/guildDelete.ts b/src/gateway/handlers/guildDelete.ts index 7460e12..3db66a7 100644 --- a/src/gateway/handlers/guildDelete.ts +++ b/src/gateway/handlers/guildDelete.ts @@ -1,8 +1,12 @@ import { Guild } from '../../structures/guild.ts' +import { GuildPayload } from '../../types/guild.ts' import { Gateway, GatewayEventHandler } from '../index.ts' -export const guildDelte: GatewayEventHandler = async (gateway: Gateway, d: any) => { - const guild: Guild | void = await gateway.client.guilds.get(d.id) +export const guildDelte: GatewayEventHandler = async ( + gateway: Gateway, + d: GuildPayload +) => { + const guild: Guild | undefined = await gateway.client.guilds.get(d.id) if (guild !== undefined) { guild.refreshFromData(d) diff --git a/src/gateway/handlers/guildEmojiUpdate.ts b/src/gateway/handlers/guildEmojiUpdate.ts new file mode 100644 index 0000000..5d08bab --- /dev/null +++ b/src/gateway/handlers/guildEmojiUpdate.ts @@ -0,0 +1,14 @@ +import cache from '../../models/cache.ts' +import { Guild } from '../../structures/guild.ts' +import { GuildEmojiUpdatePayload } from '../../types/gatewayTypes.ts' +import { Gateway, GatewayEventHandler } from '../index.ts' + +const guildEmojiUpdate: GatewayEventHandler = ( + gateway: Gateway, + d: GuildEmojiUpdatePayload +) => { + const guild: Guild = cache.get('guild', d.guild_id) + if (guild !== undefined) { + const emojis = guild.emojis + } +} diff --git a/src/gateway/handlers/guildUpdate.ts b/src/gateway/handlers/guildUpdate.ts index 4e703e9..c7e7cc4 100644 --- a/src/gateway/handlers/guildUpdate.ts +++ b/src/gateway/handlers/guildUpdate.ts @@ -1,10 +1,14 @@ import { Gateway, GatewayEventHandler } from '../index.ts' import { Guild } from '../../structures/guild.ts' +import { GuildPayload } from '../../types/guild.ts' -export const guildUpdate: GatewayEventHandler = async(gateway: Gateway, d: any) => { - const before: Guild | void = await gateway.client.guilds.get(d.id) - if(!before) return +export const guildUpdate: GatewayEventHandler = async ( + gateway: Gateway, + d: GuildPayload +) => { + const after: Guild | undefined = await gateway.client.guilds.get(d.id) + if (after === undefined) return + const before = after.refreshFromData(d) await gateway.client.guilds.set(d.id, d) - const after: Guild | void = await gateway.client.guilds.get(d.id) gateway.client.emit('guildUpdate', before, after) } diff --git a/src/gateway/handlers/messageCreate.ts b/src/gateway/handlers/messageCreate.ts index eb898bd..b3dd359 100644 --- a/src/gateway/handlers/messageCreate.ts +++ b/src/gateway/handlers/messageCreate.ts @@ -1,21 +1,22 @@ -import { Channel } from "../../structures/channel.ts" -import { Message } from "../../structures/message.ts" -import { MessageMentions } from "../../structures/MessageMentions.ts" -import { TextChannel } from "../../structures/textChannel.ts" -import { User } from "../../structures/user.ts" -import { MessagePayload } from "../../types/channel.ts" +import { Channel } from '../../structures/channel.ts' +import { Message } from '../../structures/message.ts' +import { MessageMentions } from '../../structures/MessageMentions.ts' +import { TextChannel } from '../../structures/textChannel.ts' +import { User } from '../../structures/user.ts' +import { MessagePayload } from '../../types/channel.ts' import { Gateway, GatewayEventHandler } from '../index.ts' -export const messageCreate: GatewayEventHandler = async( +export const messageCreate: GatewayEventHandler = async ( gateway: Gateway, d: MessagePayload ) => { - let channel = await gateway.client.channels.get(d.channel_id) + let channel = (await gateway.client.channels.get(d.channel_id)) as TextChannel // Fetch the channel if not cached - if(!channel) channel = (await gateway.client.channels.fetch(d.channel_id) as any) as TextChannel - let user = new User(gateway.client, d.author) + if (channel === undefined) + channel = (await gateway.client.channels.fetch(d.channel_id)) as TextChannel + const user = new User(gateway.client, d.author) await gateway.client.users.set(d.author.id, d.author) - let mentions = new MessageMentions() - let message = new Message(gateway.client, d, channel, user, mentions) + const mentions = new MessageMentions() + const message = new Message(gateway.client, d, channel, user, mentions) gateway.client.emit('messageCreate', message) } diff --git a/src/gateway/index.ts b/src/gateway/index.ts index 85ee727..4ffbe85 100644 --- a/src/gateway/index.ts +++ b/src/gateway/index.ts @@ -5,11 +5,14 @@ import { DISCORD_API_VERSION } from '../consts/urlsAndVersions.ts' import { GatewayResponse } from '../types/gatewayResponse.ts' -import { GatewayOpcodes, GatewayIntents, GatewayCloseCodes } from '../types/gateway.ts' +import { + GatewayOpcodes, + GatewayIntents, + GatewayCloseCodes +} from '../types/gateway.ts' import { gatewayHandlers } from './handlers/index.ts' import { GATEWAY_BOT } from '../types/endpoint.ts' -import { GatewayBotPayload } from "../types/gatewayBot.ts" -import { GatewayCache } from "../managers/GatewayCache.ts" +import { GatewayCache } from '../managers/GatewayCache.ts' /** * Handles Discord gateway connection. @@ -51,7 +54,7 @@ class Gateway { private onopen (): void { this.connected = true - this.debug("Connected to Gateway!") + this.debug('Connected to Gateway!') } private async onmessage (event: MessageEvent): Promise { @@ -69,12 +72,15 @@ class Gateway { switch (op) { case GatewayOpcodes.HELLO: this.heartbeatInterval = d.heartbeat_interval - this.debug(`Received HELLO. Heartbeat Interval: ${this.heartbeatInterval}`) + this.debug( + `Received HELLO. Heartbeat Interval: ${this.heartbeatInterval}` + ) this.heartbeatIntervalID = setInterval(() => { if (this.heartbeatServerResponded) { this.heartbeatServerResponded = false } else { clearInterval(this.heartbeatIntervalID) + // eslint-disable-next-line @typescript-eslint/no-floating-promises this.reconnect() return } @@ -89,10 +95,12 @@ class Gateway { }, this.heartbeatInterval) if (!this.initialized) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises this.sendIdentify() this.initialized = true } else { - console.log("Calling Resume") + console.log('Calling Resume') + // eslint-disable-next-line @typescript-eslint/no-floating-promises this.sendResume() } break @@ -100,13 +108,16 @@ class Gateway { case GatewayOpcodes.HEARTBEAT_ACK: this.heartbeatServerResponded = true this.client.ping = Date.now() - this.lastPingTimestamp - this.debug(`Received Heartbeat Ack. Ping Recognized: ${this.client.ping}ms`) + this.debug( + `Received Heartbeat Ack. Ping Recognized: ${this.client.ping}ms` + ) break case GatewayOpcodes.INVALID_SESSION: // Because we know this gonna be bool this.debug(`Invalid Session! Identifying with forced new session`) // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + // eslint-disable-next-line @typescript-eslint/promise-function-async setTimeout(() => this.sendIdentify(true), 3000) break @@ -114,7 +125,7 @@ class Gateway { this.heartbeatServerResponded = true if (s !== null) { this.sequenceID = s - await this.cache.set("seq", s) + await this.cache.set('seq', s) } if (t !== null && t !== undefined) { const handler = gatewayHandlers[t] @@ -129,11 +140,12 @@ class Gateway { // this.token = d.token this.sessionID = d.session_id this.sequenceID = d.seq - await this.cache.set("seq", d.seq) - await this.cache.set("session_id", this.sessionID) + await this.cache.set('seq', d.seq) + await this.cache.set('session_id', this.sessionID) break } case GatewayOpcodes.RECONNECT: { + // eslint-disable-next-line @typescript-eslint/no-floating-promises this.reconnect() break } @@ -143,40 +155,45 @@ class Gateway { } private onclose (event: CloseEvent): void { - this.debug("Connection Closed with code: " + event.code) + this.debug(`Connection Closed with code: ${event.code}`) - if(event.code == GatewayCloseCodes.UNKNOWN_ERROR) { - this.debug("API has encountered Unknown Error. Reconnecting...") + if (event.code === GatewayCloseCodes.UNKNOWN_ERROR) { + this.debug('API has encountered Unknown Error. Reconnecting...') + // eslint-disable-next-line @typescript-eslint/no-floating-promises this.reconnect() - } else if(event.code == GatewayCloseCodes.UNKNOWN_OPCODE) { + } else if (event.code === GatewayCloseCodes.UNKNOWN_OPCODE) { throw new Error("Unknown OP Code was sent. This shouldn't happen!") - } else if(event.code == GatewayCloseCodes.DECODE_ERROR) { + } else if (event.code === GatewayCloseCodes.DECODE_ERROR) { throw new Error("Invalid Payload was sent. This shouldn't happen!") - } else if(event.code == GatewayCloseCodes.NOT_AUTHENTICATED) { - throw new Error("Not Authorized: Payload was sent before Identifying.") - } else if(event.code == GatewayCloseCodes.AUTHENTICATION_FAILED) { - throw new Error("Invalid Token provided!") - } else if(event.code == GatewayCloseCodes.INVALID_SEQ) { - this.debug("Invalid Seq was sent. Reconnecting.") + } else if (event.code === GatewayCloseCodes.NOT_AUTHENTICATED) { + throw new Error('Not Authorized: Payload was sent before Identifying.') + } else if (event.code === GatewayCloseCodes.AUTHENTICATION_FAILED) { + throw new Error('Invalid Token provided!') + } else if (event.code === GatewayCloseCodes.INVALID_SEQ) { + this.debug('Invalid Seq was sent. Reconnecting.') + // eslint-disable-next-line @typescript-eslint/no-floating-promises this.reconnect() - } else if(event.code == GatewayCloseCodes.RATE_LIMITED) { + } else if (event.code === GatewayCloseCodes.RATE_LIMITED) { throw new Error("You're ratelimited. Calm down.") - } else if(event.code == GatewayCloseCodes.SESSION_TIMED_OUT) { - this.debug("Session Timeout. Reconnecting.") + } else if (event.code === GatewayCloseCodes.SESSION_TIMED_OUT) { + this.debug('Session Timeout. Reconnecting.') + // eslint-disable-next-line @typescript-eslint/no-floating-promises this.reconnect(true) - } else if(event.code == GatewayCloseCodes.INVALID_SHARD) { - this.debug("Invalid Shard was sent. Reconnecting.") + } else if (event.code === GatewayCloseCodes.INVALID_SHARD) { + this.debug('Invalid Shard was sent. Reconnecting.') + // eslint-disable-next-line @typescript-eslint/no-floating-promises this.reconnect() - } else if(event.code == GatewayCloseCodes.SHARDING_REQUIRED) { + } else if (event.code === GatewayCloseCodes.SHARDING_REQUIRED) { throw new Error("Couldn't connect. Sharding is requried!") - } else if(event.code == GatewayCloseCodes.INVALID_API_VERSION) { + } else if (event.code === GatewayCloseCodes.INVALID_API_VERSION) { throw new Error("Invalid API Version was used. This shouldn't happen!") - } else if(event.code == GatewayCloseCodes.INVALID_INTENTS) { - throw new Error("Invalid Intents") - } else if(event.code == GatewayCloseCodes.DISALLOWED_INTENTS) { + } else if (event.code === GatewayCloseCodes.INVALID_INTENTS) { + throw new Error('Invalid Intents') + } else if (event.code === GatewayCloseCodes.DISALLOWED_INTENTS) { throw new Error("Given Intents aren't allowed") } else { - this.debug("Unknown Close code, probably connection error. Reconnecting.") + this.debug('Unknown Close code, probably connection error. Reconnecting.') + // eslint-disable-next-line @typescript-eslint/no-floating-promises this.reconnect() } } @@ -186,20 +203,25 @@ class Gateway { console.log(eventError) } - private async sendIdentify (forceNewSession?: boolean) { - this.debug("Fetching /gateway/bot...") - const info = await this.client.rest.get(GATEWAY_BOT()) as GatewayBotPayload - if(info.session_start_limit.remaining == 0) throw new Error("Session Limit Reached. Retry After " + info.session_start_limit.reset_after + "ms") - this.debug("Recommended Shards: " + info.shards) - this.debug("=== Session Limit Info ===") - this.debug(`Remaining: ${info.session_start_limit.remaining}/${info.session_start_limit.total}`) + private async sendIdentify (forceNewSession?: boolean): Promise { + this.debug('Fetching /gateway/bot...') + const info = await this.client.rest.get(GATEWAY_BOT()) + if (info.session_start_limit.remaining === 0) + throw new Error( + `Session Limit Reached. Retry After ${info.session_start_limit.reset_after}ms` + ) + this.debug(`Recommended Shards: ${info.shards}`) + this.debug('=== Session Limit Info ===') + this.debug( + `Remaining: ${info.session_start_limit.remaining}/${info.session_start_limit.total}` + ) this.debug(`Reset After: ${info.session_start_limit.reset_after}ms`) - if(!forceNewSession) { - let sessionIDCached = await this.cache.get("session_id") - if(sessionIDCached) { - this.debug("Found Cached SessionID: " + sessionIDCached) + if (forceNewSession === undefined || !forceNewSession) { + const sessionIDCached = await this.cache.get('session_id') + if (sessionIDCached !== undefined) { + this.debug(`Found Cached SessionID: ${sessionIDCached}`) this.sessionID = sessionIDCached - return this.sendResume() + return await this.sendResume() } } this.websocket.send( @@ -231,30 +253,30 @@ class Gateway { private async sendResume (): Promise { this.debug(`Preparing to resume with Session: ${this.sessionID}`) - if(this.sequenceID === undefined) { - let cached = await this.cache.get("seq") - if(cached) this.sequenceID = typeof cached == "string" ? parseInt(cached) : cached + if (this.sequenceID === undefined) { + const cached = await this.cache.get('seq') + if (cached !== undefined) + this.sequenceID = typeof cached === 'string' ? parseInt(cached) : cached } const resumePayload = { op: GatewayOpcodes.RESUME, d: { token: this.token, session_id: this.sessionID, - seq: this.sequenceID || null + seq: this.sequenceID ?? null } } - this.websocket.send( - JSON.stringify(resumePayload) - ) + this.websocket.send(JSON.stringify(resumePayload)) } - debug(msg: string) { - this.client.debug("Gateway", msg) + debug (msg: string): void { + this.client.debug('Gateway', msg) } - async reconnect(forceNew?: boolean) { + async reconnect (forceNew?: boolean): Promise { clearInterval(this.heartbeatIntervalID) - if(forceNew) await this.cache.delete("session_id") + if (forceNew === undefined || !forceNew) + await this.cache.delete('session_id') this.close() this.initWebsocket() } diff --git a/src/managers/BaseManager.ts b/src/managers/BaseManager.ts index 3efcf2c..921cdd5 100644 --- a/src/managers/BaseManager.ts +++ b/src/managers/BaseManager.ts @@ -1,32 +1,31 @@ -import { Client } from "../models/client.ts"; -import { Base } from "../structures/base.ts"; +import { Client } from '../models/client.ts' export class BaseManager { client: Client cacheName: string - dataType: any + DataType: any - constructor(client: Client, cacheName: string, dataType: any) { + constructor (client: Client, cacheName: string, DataType: any) { this.client = client this.cacheName = cacheName - this.dataType = dataType + this.DataType = DataType } - _get(key: string): Promise { - return this.client.cache.get(this.cacheName, key) as Promise + async _get (key: string): Promise { + return this.client.cache.get(this.cacheName, key) } - async get(key: string): Promise { + async get (key: string): Promise { const raw = await this._get(key) - if(!raw) return - return new this.dataType(this.client, raw) as any + if (raw === undefined) return + return new this.DataType(this.client, raw) } - async set(key: string, value: T) { + async set (key: string, value: T): Promise { return this.client.cache.set(this.cacheName, key, value) } - async delete(key: string) { + async delete (key: string): Promise { return this.client.cache.delete(this.cacheName, key) } -} \ No newline at end of file +} diff --git a/src/managers/ChannelsManager.ts b/src/managers/ChannelsManager.ts index 79532d7..0cea8dd 100644 --- a/src/managers/ChannelsManager.ts +++ b/src/managers/ChannelsManager.ts @@ -1,26 +1,29 @@ -import { Client } from "../models/client.ts"; -import { Channel } from "../structures/channel.ts"; -import { User } from "../structures/user.ts"; -import { ChannelPayload } from "../types/channel.ts"; -import { CHANNEL } from "../types/endpoint.ts"; -import { BaseManager } from "./BaseManager.ts"; +import { Client } from '../models/client.ts' +import { Channel } from '../structures/channel.ts' +import { User } from '../structures/user.ts' +import { ChannelPayload } from '../types/channel.ts' +import { CHANNEL } from '../types/endpoint.ts' +import { BaseManager } from './BaseManager.ts' export class ChannelsManager extends BaseManager { - constructor(client: Client) { - super(client, "channels", User) + constructor (client: Client) { + super(client, 'channels', User) } // Override get method as Generic - async get(key: string): Promise { - return new this.dataType(this.client, this._get(key)) as any + async get (key: string): Promise { + return new this.DataType(this.client, this._get(key)) } - fetch(id: string): Promise { - return new Promise((res, rej) => { - this.client.rest.get(CHANNEL(id)).then(data => { - this.set(id, data as ChannelPayload) - res(new Channel(this.client, data as ChannelPayload)) - }).catch(e => rej(e)) + async fetch (id: string): Promise { + return await new Promise((resolve, reject) => { + this.client.rest + .get(CHANNEL(id)) + .then(data => { + this.set(id, data as ChannelPayload) + resolve(new Channel(this.client, data as ChannelPayload)) + }) + .catch(e => reject(e)) }) } -} \ No newline at end of file +} diff --git a/src/managers/EmojisManager.ts b/src/managers/EmojisManager.ts index 1c07887..c05912a 100644 --- a/src/managers/EmojisManager.ts +++ b/src/managers/EmojisManager.ts @@ -1,20 +1,23 @@ -import { Client } from "../models/client.ts"; -import { Emoji } from "../structures/emoji.ts"; -import { EmojiPayload } from "../types/emoji.ts"; -import { CHANNEL } from "../types/endpoint.ts"; -import { BaseManager } from "./BaseManager.ts"; +import { Client } from '../models/client.ts' +import { Emoji } from '../structures/emoji.ts' +import { EmojiPayload } from '../types/emoji.ts' +import { CHANNEL } from '../types/endpoint.ts' +import { BaseManager } from './BaseManager.ts' export class EmojisManager extends BaseManager { - constructor(client: Client) { - super(client, "emojis", Emoji) + constructor (client: Client) { + super(client, 'emojis', Emoji) } - fetch(id: string) { - return new Promise((res, rej) => { - this.client.rest.get(CHANNEL(id)).then(data => { - this.set(id, data as EmojiPayload) - res(new Emoji(this.client, data as EmojiPayload)) - }).catch(e => rej(e)) + async fetch (id: string): Promise { + return await new Promise((resolve, reject) => { + this.client.rest + .get(CHANNEL(id)) + .then(data => { + this.set(id, data as EmojiPayload) + resolve(new Emoji(this.client, data as EmojiPayload)) + }) + .catch(e => reject(e)) }) } -} \ No newline at end of file +} diff --git a/src/managers/GatewayCache.ts b/src/managers/GatewayCache.ts index 72be001..82c3f47 100644 --- a/src/managers/GatewayCache.ts +++ b/src/managers/GatewayCache.ts @@ -1,24 +1,27 @@ -import { Client } from "../models/client.ts"; +import { Client } from '../models/client.ts' export class GatewayCache { - client: Client - cacheName: string = "discord_gateway_cache" - - constructor(client: Client, cacheName?: string) { - this.client = client - if(cacheName) this.cacheName = cacheName - } + client: Client + cacheName: string = 'discord_gateway_cache' - get(key: string) { - return this.client.cache.get(this.cacheName, key) - } + constructor (client: Client, cacheName?: string) { + this.client = client + if (cacheName !== undefined) this.cacheName = cacheName + } - set(key: string, value: any) { - return this.client.cache.set(this.cacheName, key, value) - } + async get (key: string): Promise { + const result = await this.client.cache.get(this.cacheName, key) + return result + } - delete(key: string) { - console.log(`[GatewayCache] DEL ${key}`) - return this.client.cache.delete(this.cacheName, key) - } -} \ No newline at end of file + async set (key: string, value: any): Promise { + const result = await this.client.cache.set(this.cacheName, key, value) + return result + } + + async delete (key: string): Promise { + console.log(`[GatewayCache] DEL ${key}`) + const result = await this.client.cache.delete(this.cacheName, key) + return result + } +} diff --git a/src/managers/GuildsManager.ts b/src/managers/GuildsManager.ts index 95fe153..5d78002 100644 --- a/src/managers/GuildsManager.ts +++ b/src/managers/GuildsManager.ts @@ -1,20 +1,23 @@ -import { Client } from "../models/client.ts"; -import { Guild } from "../structures/guild.ts"; -import { GUILD } from "../types/endpoint.ts"; -import { GuildPayload } from "../types/guild.ts"; -import { BaseManager } from "./BaseManager.ts"; +import { Client } from '../models/client.ts' +import { Guild } from '../structures/guild.ts' +import { GUILD } from '../types/endpoint.ts' +import { GuildPayload } from '../types/guild.ts' +import { BaseManager } from './BaseManager.ts' export class GuildManager extends BaseManager { - constructor(client: Client) { - super(client, "guilds", Guild) + constructor (client: Client) { + super(client, 'guilds', Guild) } - fetch(id: string) { - return new Promise((res, rej) => { - this.client.rest.get(GUILD(id)).then(data => { - this.set(id, data as GuildPayload) - res(new Guild(this.client, data as GuildPayload)) - }).catch(e => rej(e)) + async fetch (id: string): Promise { + return await new Promise((resolve, reject) => { + this.client.rest + .get(GUILD(id)) + .then(data => { + this.set(id, data as GuildPayload) + resolve(new Guild(this.client, data as GuildPayload)) + }) + .catch(e => reject(e)) }) } -} \ No newline at end of file +} diff --git a/src/managers/MessagesManager.ts b/src/managers/MessagesManager.ts index 42a78b6..2843d92 100644 --- a/src/managers/MessagesManager.ts +++ b/src/managers/MessagesManager.ts @@ -1,29 +1,43 @@ -import { Client } from "../models/client.ts"; -import { Message } from "../structures/message.ts"; -import { MessageMentions } from "../structures/MessageMentions.ts"; -import { User } from "../structures/user.ts"; -import { MessagePayload } from "../types/channel.ts"; -import { CHANNEL_MESSAGE } from "../types/endpoint.ts"; -import { UserPayload } from "../types/user.ts"; -import { BaseManager } from "./BaseManager.ts"; +import { Client } from '../models/client.ts' +import { Message } from '../structures/message.ts' +import { MessageMentions } from '../structures/MessageMentions.ts' +import { User } from '../structures/user.ts' +import { MessagePayload } from '../types/channel.ts' +import { CHANNEL_MESSAGE } from '../types/endpoint.ts' +import { BaseManager } from './BaseManager.ts' export class MessagesManager extends BaseManager { - constructor(client: Client) { - super(client, "messages", Message) + constructor (client: Client) { + super(client, 'messages', Message) } - fetch(channelID: string, id: string) { - return new Promise((res, rej) => { - this.client.rest.get(CHANNEL_MESSAGE(channelID, id)).then(async data => { - this.set(id, data as MessagePayload) - let channel = await this.client.channels.get(channelID) - if(!channel) channel = await this.client.channels.fetch(channelID) - let author = new User(this.client, (data as MessagePayload).author as UserPayload) - await this.client.users.set(author.id, (data as MessagePayload).author) - // TODO: Make this thing work (MessageMentions) - let mentions = new MessageMentions() - res(new Message(this.client, data as MessagePayload, channel, author, mentions)) - }).catch(e => rej(e)) + async fetch (channelID: string, id: string): Promise { + return await new Promise((resolve, reject) => { + this.client.rest + .get(CHANNEL_MESSAGE(channelID, id)) + .then(async data => { + this.set(id, data as MessagePayload) + let channel = await this.client.channels.get(channelID) + if (channel === undefined) + channel = await this.client.channels.fetch(channelID) + const author = new User(this.client, (data as MessagePayload).author) + await this.client.users.set( + author.id, + (data as MessagePayload).author + ) + // TODO: Make this thing work (MessageMentions) + const mentions = new MessageMentions() + resolve( + new Message( + this.client, + data as MessagePayload, + channel, + author, + mentions + ) + ) + }) + .catch(e => reject(e)) }) } -} \ No newline at end of file +} diff --git a/src/managers/RolesManager.ts b/src/managers/RolesManager.ts index 6e6f2ab..6bf0644 100644 --- a/src/managers/RolesManager.ts +++ b/src/managers/RolesManager.ts @@ -1,25 +1,27 @@ -import { Client } from "../models/client.ts"; -import { Guild } from "../structures/guild.ts"; -import { Role } from "../structures/role.ts"; -import { User } from "../structures/user.ts"; -import { GUILD_ROLE } from "../types/endpoint.ts"; -import { RolePayload } from "../types/role.ts"; -import { BaseManager } from "./BaseManager.ts"; +import { Client } from '../models/client.ts' +import { Guild } from '../structures/guild.ts' +import { Role } from '../structures/role.ts' +import { GUILD_ROLE } from '../types/endpoint.ts' +import { RolePayload } from '../types/role.ts' +import { BaseManager } from './BaseManager.ts' export class RolesManager extends BaseManager { guild: Guild - constructor(client: Client, guild: Guild) { - super(client, "roles:" + guild.id, Role) + constructor (client: Client, guild: Guild) { + super(client, `roles:${guild.id}`, Role) this.guild = guild } - fetch(id: string) { - return new Promise((res, rej) => { - this.client.rest.get(GUILD_ROLE(this.guild.id, id)).then(data => { - this.set(id, data as RolePayload) - res(new Role(this.client, data as RolePayload)) - }).catch(e => rej(e)) + async fetch (id: string): Promise { + return await new Promise((resolve, reject) => { + this.client.rest + .get(GUILD_ROLE(this.guild.id, id)) + .then(data => { + this.set(id, data as RolePayload) + resolve(new Role(this.client, data as RolePayload)) + }) + .catch(e => reject(e)) }) } -} \ No newline at end of file +} diff --git a/src/managers/UsersManager.ts b/src/managers/UsersManager.ts index c321f5b..e96b0a8 100644 --- a/src/managers/UsersManager.ts +++ b/src/managers/UsersManager.ts @@ -1,20 +1,23 @@ -import { Client } from "../models/client.ts"; -import { User } from "../structures/user.ts"; -import { USER } from "../types/endpoint.ts"; -import { UserPayload } from "../types/user.ts"; -import { BaseManager } from "./BaseManager.ts"; +import { Client } from '../models/client.ts' +import { User } from '../structures/user.ts' +import { USER } from '../types/endpoint.ts' +import { UserPayload } from '../types/user.ts' +import { BaseManager } from './BaseManager.ts' export class UserManager extends BaseManager { - constructor(client: Client) { - super(client, "users", User) + constructor (client: Client) { + super(client, 'users', User) } - fetch(id: string) { - return new Promise((res, rej) => { - this.client.rest.get(USER(id)).then(data => { - this.set(id, data as UserPayload) - res(new User(this.client, data as UserPayload)) - }).catch(e => rej(e)) + async fetch (id: string): Promise { + return await new Promise((resolve, reject) => { + this.client.rest + .get(USER(id)) + .then(data => { + this.set(id, data as UserPayload) + resolve(new User(this.client, data as UserPayload)) + }) + .catch(e => reject(e)) }) } -} \ No newline at end of file +} diff --git a/src/models/CacheAdapter.ts b/src/models/CacheAdapter.ts index 842afbf..4b1e14d 100644 --- a/src/models/CacheAdapter.ts +++ b/src/models/CacheAdapter.ts @@ -1,13 +1,17 @@ -import { Collection } from "../utils/collection.ts"; -import { Client } from "./client.ts"; -import { connect, Redis, RedisConnectOptions } from "https://denopkg.com/keroxp/deno-redis/mod.ts"; +import { Collection } from '../utils/collection.ts' +import { Client } from './client.ts' +import { + connect, + Redis, + RedisConnectOptions +} from 'https://denopkg.com/keroxp/deno-redis/mod.ts' export interface ICacheAdapter { client: Client - get: (cacheName: string, key: string) => Promise | any - set: (cacheName: string, key: string, value: any) => Promise | any - delete: (cacheName: string, key: string) => Promise | boolean - array: (cacheName: string) => void | any[] | Promise + get: (cacheName: string, key: string) => Promise + set: (cacheName: string, key: string, value: any) => Promise + delete: (cacheName: string, key: string) => Promise + array: (cacheName: string) => Promise } export class DefaultCacheAdapter implements ICacheAdapter { @@ -16,34 +20,34 @@ export class DefaultCacheAdapter implements ICacheAdapter { [name: string]: Collection } = {} - constructor(client: Client) { + constructor (client: Client) { this.client = client } - async get(cacheName: string, key: string) { + async get (cacheName: string, key: string): Promise { const cache = this.data[cacheName] - if (!cache) return; + if (cache === undefined) return return cache.get(key) } - async set(cacheName: string, key: string, value: any) { + async set (cacheName: string, key: string, value: any): Promise { let cache = this.data[cacheName] - if (!cache) { + if (cache === undefined) { this.data[cacheName] = new Collection() cache = this.data[cacheName] } - cache.set(key, value) + return cache.set(key, value) } - async delete(cacheName: string, key: string) { + async delete (cacheName: string, key: string): Promise { const cache = this.data[cacheName] - if (!cache) return false + if (cache === undefined) return false return cache.delete(key) } - async array(cacheName: string) { + async array (cacheName: string): Promise { const cache = this.data[cacheName] - if (!cache) return + if (cache === undefined) return return cache.array() } } @@ -54,45 +58,60 @@ export class RedisCacheAdapter implements ICacheAdapter { redis?: Redis ready: boolean = false - constructor(client: Client, options: RedisConnectOptions) { + constructor (client: Client, options: RedisConnectOptions) { this.client = client this._redis = connect(options) - this._redis.then(redis => { - this.redis = redis - this.ready = true - }) + this._redis.then( + redis => { + this.redis = redis + this.ready = true + }, + () => { + // TODO: Make error for this + } + ) } - async _checkReady() { - if(!this.ready) return await this._redis; - else return; + async _checkReady (): Promise { + if (!this.ready) await this._redis } - async get(cacheName: string, key: string) { + async get (cacheName: string, key: string): Promise { await this._checkReady() - let cache = await this.redis?.hget(cacheName, key) - if(!cache) return + const cache = await this.redis?.hget(cacheName, key) + if (cache === undefined) return try { - return JSON.parse(cache as string) - } catch(e) { return cache } + return JSON.parse(cache) + } catch (e) { + return cache + } } - async set(cacheName: string, key: string, value: any) { + async set ( + cacheName: string, + key: string, + value: any + ): Promise { await this._checkReady() - return await this.redis?.hset(cacheName, key, typeof value === "object" ? JSON.stringify(value) : value) + const result = await this.redis?.hset( + cacheName, + key, + typeof value === 'object' ? JSON.stringify(value) : value + ) + return result } - async delete(cacheName: string, key: string) { + async delete (cacheName: string, key: string): Promise { await this._checkReady() - let exists = await this.redis?.hexists(cacheName, key) - if(!exists) return false + const exists = await this.redis?.hexists(cacheName, key) + if (exists === 0) return false await this.redis?.hdel(cacheName, key) return true } - async array(cacheName: string) { + async array (cacheName: string): Promise { await this._checkReady() - let data = await this.redis?.hvals(cacheName) + const data = await this.redis?.hvals(cacheName) return data?.map((e: string) => JSON.parse(e)) } -} \ No newline at end of file +} diff --git a/src/models/client.ts b/src/models/client.ts index f236b77..bce377a 100644 --- a/src/models/client.ts +++ b/src/models/client.ts @@ -3,12 +3,12 @@ import { GatewayIntents } from '../types/gateway.ts' import { Gateway } from '../gateway/index.ts' import { RESTManager } from './rest.ts' import EventEmitter from 'https://deno.land/std@0.74.0/node/events.ts' -import { DefaultCacheAdapter, ICacheAdapter } from "./CacheAdapter.ts" -import { UserManager } from "../managers/UsersManager.ts" -import { GuildManager } from "../managers/GuildsManager.ts" -import { EmojisManager } from "../managers/EmojisManager.ts" -import { ChannelsManager } from "../managers/ChannelsManager.ts" -import { MessagesManager } from "../managers/MessagesManager.ts" +import { DefaultCacheAdapter, ICacheAdapter } from './CacheAdapter.ts' +import { UserManager } from '../managers/UsersManager.ts' +import { GuildManager } from '../managers/GuildsManager.ts' +import { EmojisManager } from '../managers/EmojisManager.ts' +import { ChannelsManager } from '../managers/ChannelsManager.ts' +import { MessagesManager } from '../managers/MessagesManager.ts' /** Some Client Options to modify behaviour */ export interface ClientOptions { @@ -28,7 +28,7 @@ export class Client extends EventEmitter { token?: string cache: ICacheAdapter = new DefaultCacheAdapter(this) intents?: GatewayIntents[] - + users: UserManager = new UserManager(this) guilds: GuildManager = new GuildManager(this) channels: ChannelsManager = new ChannelsManager(this) @@ -39,16 +39,16 @@ export class Client extends EventEmitter { super() this.token = options.token this.intents = options.intents - if(options.cache) this.cache = options.cache + if (options.cache !== undefined) this.cache = options.cache } - setAdapter(adapter: ICacheAdapter) { + setAdapter (adapter: ICacheAdapter): Client { this.cache = adapter return this } - debug(tag: string, msg: string) { - this.emit("debug", `[${tag}] ${msg}`) + debug (tag: string, msg: string): void { + this.emit('debug', `[${tag}] ${msg}`) } /** @@ -57,16 +57,15 @@ export class Client extends EventEmitter { * @param intents Gateway intents in array. This is required. */ connect (token?: string, intents?: GatewayIntents[]): void { - if(!token && this.token) token = this.token - else if(!this.token && token) { + if (token === undefined && this.token !== undefined) token = this.token + else if (this.token === undefined && token !== undefined) { this.token = token - } - else throw new Error("No Token Provided") - if(!intents && this.intents) intents = this.intents - else if(intents && !this.intents) { + } else throw new Error('No Token Provided') + if (intents === undefined && this.intents !== undefined) + intents = this.intents + else if (intents !== undefined && this.intents === undefined) { this.intents = intents - } - else throw new Error("No Gateway Intents were provided") + } else throw new Error('No Gateway Intents were provided') this.gateway = new Gateway(this, token, intents) } } diff --git a/src/models/rest.ts b/src/models/rest.ts index a3553fb..8632e7e 100644 --- a/src/models/rest.ts +++ b/src/models/rest.ts @@ -1,6 +1,6 @@ -import { delay } from "../utils/index.ts"; -import * as baseEndpoints from "../consts/urlsAndVersions.ts"; -import { Client } from "./client.ts"; +import { delay } from '../utils/index.ts' +import * as baseEndpoints from '../consts/urlsAndVersions.ts' +import { Client } from './client.ts' export enum HttpResponseCode { Ok = 200, @@ -17,351 +17,376 @@ export enum HttpResponseCode { } export type RequestMethods = - | "get" - | "post" - | "put" - | "patch" - | "head" - | "delete"; + | 'get' + | 'post' + | 'put' + | 'patch' + | 'head' + | 'delete' export interface QueuedRequest { - callback: () => Promise< - void | { - rateLimited: any; - beforeFetch: boolean; - bucketID?: string | null; - } - >; - bucketID?: string | null; - url: string; + callback: () => Promise< + | { + rateLimited: any + beforeFetch: boolean + bucketID?: string | null + } + | undefined + > + bucketID?: string | null + url: string } export interface RateLimitedPath { - url: string; - resetTimestamp: number; - bucketID: string | null; + url: string + resetTimestamp: number + bucketID: string | null } export class RESTManager { - client: Client; - globallyRateLimited: boolean = false; - queueInProcess: boolean = false; - pathQueues: { [key: string]: QueuedRequest[] } = {}; - ratelimitedPaths = new Map(); + client: Client + globallyRateLimited: boolean = false + queueInProcess: boolean = false + pathQueues: { [key: string]: QueuedRequest[] } = {} + ratelimitedPaths = new Map() - constructor(client: Client) { - this.client = client; - } - - async processRateLimitedPaths() { - const now = Date.now(); - this.ratelimitedPaths.forEach((value, key) => { - if (value.resetTimestamp > now) return; - this.ratelimitedPaths.delete(key); - if (key === "global") this.globallyRateLimited = false; - }); - - await delay(1000); - this.processRateLimitedPaths(); - } - - addToQueue(request: QueuedRequest) { - const route = request.url.substring(baseEndpoints.DISCORD_API_URL.length + 1); - const parts = route.split("/"); - // Remove the major param - parts.shift(); - const [id] = parts; - - if (this.pathQueues[id]) { - this.pathQueues[id].push(request); - } else { - this.pathQueues[id] = [request]; - } - } - - async cleanupQueues() { - Object.entries(this.pathQueues).map(([key, value]) => { - if (!value.length) { - // Remove it entirely - delete this.pathQueues[key]; - } - }); - } - - async processQueue() { - if ( - (Object.keys(this.pathQueues).length) && !this.globallyRateLimited - ) { - await Promise.allSettled( - Object.values(this.pathQueues).map(async (pathQueue) => { - const request = pathQueue.shift(); - if (!request) return; - - const rateLimitedURLResetIn = await this.checkRatelimits(request.url); - - if (request.bucketID) { - const rateLimitResetIn = await this.checkRatelimits(request.bucketID); - if (rateLimitResetIn) { - // This request is still rate limited readd to queue - this.addToQueue(request); - } else if (rateLimitedURLResetIn) { - // This URL is rate limited readd to queue - this.addToQueue(request); - } else { - // This request is not rate limited so it should be run - const result = await request.callback(); - if (result && result.rateLimited) { - this.addToQueue( - { ...request, bucketID: result.bucketID || request.bucketID }, - ); - } - } - } else { - if (rateLimitedURLResetIn) { - // This URL is rate limited readd to queue - this.addToQueue(request); - } else { - // This request has no bucket id so it should be processed - const result = await request.callback(); - if (request && result && result.rateLimited) { - this.addToQueue( - { ...request, bucketID: result.bucketID || request.bucketID }, - ); - } - } - } - }), - ); - } - - if (Object.keys(this.pathQueues).length) { - await delay(1000); - this.processQueue(); - this.cleanupQueues(); - } else this.queueInProcess = false; - } - - createRequestBody(body: any, method: RequestMethods) { - const headers: { [key: string]: string } = { - Authorization: `Bot ${this.client.token}`, - "User-Agent": - `DiscordBot (discord.deno)`, - }; - - if(!this.client.token) delete headers.Authorization; - - if (method === "get") body = undefined; - - if (body?.reason) { - headers["X-Audit-Log-Reason"] = encodeURIComponent(body.reason); - } - - if (body?.file) { - const form = new FormData(); - form.append("file", body.file.blob, body.file.name); - form.append("payload_json", JSON.stringify({ ...body, file: undefined })); - body.file = form; - } else if ( - body && !["get", "delete"].includes(method) - ) { - headers["Content-Type"] = "application/json"; - } - - return { - headers, - body: body?.file || JSON.stringify(body), - method: method.toUpperCase(), - }; - } - - async checkRatelimits(url: string) { - const ratelimited = this.ratelimitedPaths.get(url); - const global = this.ratelimitedPaths.get("global"); - const now = Date.now(); - - if (ratelimited && now < ratelimited.resetTimestamp) { - return ratelimited.resetTimestamp - now; - } - if (global && now < global.resetTimestamp) { - return global.resetTimestamp - now; - } - - return false; - } - - async runMethod( - method: RequestMethods, - url: string, - body?: unknown, - retryCount = 0, - bucketID?: string | null, - ) { - - const errorStack = new Error("Location In Your Files:"); - Error.captureStackTrace(errorStack); - - return await new Promise((resolve, reject) => { - const callback = async () => { - try { - const rateLimitResetIn = await this.checkRatelimits(url); - if (rateLimitResetIn) { - return { rateLimited: rateLimitResetIn, beforeFetch: true, bucketID }; - } - - const query = method === "get" && body - ? Object.entries(body as any).map(([key, value]) => - `${encodeURIComponent(key)}=${encodeURIComponent(value as any)}` - ) - .join("&") - : ""; - const urlToUse = method === "get" && query ? `${url}?${query}` : url; - - const response = await fetch(urlToUse, this.createRequestBody(body, method)); - const bucketIDFromHeaders = this.processHeaders(url, response.headers); - this.handleStatusCode(response, errorStack); - - // Sometimes Discord returns an empty 204 response that can't be made to JSON. - if (response.status === 204) return resolve(); - - const json = await response.json(); - if ( - json.retry_after || - json.message === "You are being rate limited." - ) { - if (retryCount > 10) { - throw new Error("Max RateLimit Retries hit"); - } - - return { - rateLimited: json.retry_after, - beforeFetch: false, - bucketID: bucketIDFromHeaders, - }; - } - return resolve(json); - } catch (error) { - return reject(error); - } - }; - - this.addToQueue({ - callback, - bucketID, - url, - }); - if (!this.queueInProcess) { - this.queueInProcess = true; - this.processQueue(); - } - }); - } - - async logErrors(response: Response, errorStack?: unknown) { - try { - const error = await response.json(); - console.error(error); - } catch { - console.error(response); - } - } - - handleStatusCode(response: Response, errorStack?: unknown) { - const status = response.status; - - if ( - (status >= 200 && status < 400) || - status === HttpResponseCode.TooManyRequests - ) { - return true; - } - - this.logErrors(response, errorStack); - - 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) { - 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 && remaining === "0") { - ratelimited = true; - - this.ratelimitedPaths.set(url, { - url, - resetTimestamp: Number(resetTimestamp) * 1000, - bucketID, - }); - - if (bucketID) { - this.ratelimitedPaths.set(bucketID, { - url, - resetTimestamp: Number(resetTimestamp) * 1000, - bucketID, - }); - } - } - - // If there is no remaining global limit, we save it in cache - if (global) { - const reset = Date.now() + Number(retryAfter); - this.globallyRateLimited = true; - ratelimited = true; - - this.ratelimitedPaths.set("global", { - url: "global", - resetTimestamp: reset, - bucketID, - }); - - if (bucketID) { - this.ratelimitedPaths.set(bucketID, { - url: "global", - resetTimestamp: reset, - bucketID, - }); - } - } - - return ratelimited ? bucketID : undefined; - } - - get(url: string, body?: unknown) { - return this.runMethod("get", url, body); + constructor (client: Client) { + this.client = client + setTimeout(this.processRateLimitedPaths, 1000) } - post(url: string, body?: unknown) { - return this.runMethod("post", url, body); + async processRateLimitedPaths (): Promise { + const now = Date.now() + this.ratelimitedPaths.forEach((value, key) => { + if (value.resetTimestamp > now) return + this.ratelimitedPaths.delete(key) + if (key === 'global') this.globallyRateLimited = false + }) } - delete(url: string, body?: unknown) { - return this.runMethod("delete", url, body); + addToQueue (request: QueuedRequest): void { + const route = request.url.substring( + // eslint seriously? + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + baseEndpoints.DISCORD_API_URL.length + 1 + ) + const parts = route.split('/') + // Remove the major param + parts.shift() + const [id] = parts + + if (this.pathQueues[id] !== undefined) { + this.pathQueues[id].push(request) + } else { + this.pathQueues[id] = [request] + } } - patch(url: string, body?: unknown) { - return this.runMethod("patch", url, body); + async cleanupQueues (): Promise { + Object.entries(this.pathQueues).forEach(([key, value]) => { + if (value.length === 0) { + // Remove it entirely + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete this.pathQueues[key] + } + }) } - put(url: string, body?: unknown) { - return this.runMethod("put", url, body); + async processQueue (): Promise { + if ( + Object.keys(this.pathQueues).length !== 0 && + !this.globallyRateLimited + ) { + await Promise.allSettled( + Object.values(this.pathQueues).map(async pathQueue => { + const request = pathQueue.shift() + if (request === undefined) return + + const rateLimitedURLResetIn = await this.checkRatelimits(request.url) + + if (typeof request.bucketID === 'string') { + const rateLimitResetIn = await this.checkRatelimits( + request.bucketID + ) + if (rateLimitResetIn !== false) { + // This request is still rate limited read to queue + this.addToQueue(request) + } else { + // This request is not rate limited so it should be run + const result = await request.callback() + if (result?.rateLimited !== undefined) { + this.addToQueue({ + ...request, + bucketID: result.bucketID ?? request.bucketID + }) + } + } + } else { + if (rateLimitedURLResetIn !== false) { + // This URL is rate limited readd to queue + this.addToQueue(request) + } else { + // This request has no bucket id so it should be processed + const result = await request.callback() + if (result?.rateLimited !== undefined) { + this.addToQueue({ + ...request, + bucketID: result.bucketID ?? request.bucketID + }) + } + } + } + }) + ) + } + + if (Object.keys(this.pathQueues).length !== 0) { + await delay(1000) + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.processQueue() + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.cleanupQueues() + } else this.queueInProcess = false } -} \ No newline at end of file + + createRequestBody ( + body: any, + method: RequestMethods + ): { [key: string]: any } { + const headers: { [key: string]: string } = { + Authorization: `Bot ${this.client.token}`, + 'User-Agent': `DiscordBot (discord.deno)` + } + + if (this.client.token !== undefined) delete headers.Authorization + + if (method === 'get') body = undefined + + if (body?.reason !== undefined) { + headers['X-Audit-Log-Reason'] = encodeURIComponent(body.reason) + } + + if (body?.file !== undefined) { + const form = new FormData() + form.append('file', body.file.blob, body.file.name) + form.append('payload_json', JSON.stringify({ ...body, file: undefined })) + body.file = form + } else if (body !== undefined && !['get', 'delete'].includes(method)) { + headers['Content-Type'] = 'application/json' + } + + return { + headers, + body: body?.file ?? JSON.stringify(body), + method: method.toUpperCase() + } + } + + async checkRatelimits (url: string): Promise { + const ratelimited = this.ratelimitedPaths.get(url) + const global = this.ratelimitedPaths.get('global') + const now = Date.now() + + if (ratelimited !== undefined && now < ratelimited.resetTimestamp) { + return ratelimited.resetTimestamp - now + } + if (global !== undefined && now < global.resetTimestamp) { + return global.resetTimestamp - now + } + + return false + } + + async runMethod ( + method: RequestMethods, + url: string, + body?: unknown, + retryCount = 0, + bucketID?: string | null + ): Promise { + const errorStack = new Error('Location In Your Files:') + Error.captureStackTrace(errorStack) + + return await new Promise((resolve, reject) => { + const callback = async (): Promise => { + try { + const rateLimitResetIn = await this.checkRatelimits(url) + if (rateLimitResetIn !== false) { + return { + rateLimited: rateLimitResetIn, + beforeFetch: true, + bucketID + } + } + + const query = + method === 'get' && body !== undefined + ? Object.entries(body as any) + .map( + ([key, value]) => + `${encodeURIComponent(key)}=${encodeURIComponent( + value as any + )}` + ) + .join('&') + : '' + const urlToUse = + method === 'get' && query !== '' ? `${url}?${query}` : url + + const response = await fetch( + urlToUse, + this.createRequestBody(body, method) + ) + const bucketIDFromHeaders = this.processHeaders(url, response.headers) + this.handleStatusCode(response, errorStack) + + // Sometimes Discord returns an empty 204 response that can't be made to JSON. + if (response.status === 204) return resolve(undefined) + + const json = await response.json() + if ( + json.retry_after !== undefined || + json.message === 'You are being rate limited.' + ) { + if (retryCount > 10) { + throw new Error('Max RateLimit Retries hit') + } + + return { + rateLimited: json.retry_after, + beforeFetch: false, + bucketID: bucketIDFromHeaders + } + } + return resolve(json) + } catch (error) { + return reject(error) + } + } + + this.addToQueue({ + callback, + bucketID, + url + }) + if (!this.queueInProcess) { + this.queueInProcess = true + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.processQueue() + } + }) + } + + async logErrors (response: Response, errorStack?: unknown): Promise { + try { + const error = await response.json() + console.error(error) + } catch { + console.error(response) + } + } + + 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) + + 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 + }) + } + } + + // If there is no remaining global limit, we save it in cache + if (global !== null) { + 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 get (url: string, body?: unknown): Promise { + return await this.runMethod('get', url, body) + } + + async post (url: string, body?: unknown): Promise { + return await this.runMethod('post', url, body) + } + + async delete (url: string, body?: unknown): Promise { + return await this.runMethod('delete', url, body) + } + + async patch (url: string, body?: unknown): Promise { + return await this.runMethod('patch', url, body) + } + + async put (url: string, body?: unknown): Promise { + return await this.runMethod('put', url, body) + } +} diff --git a/src/structures/channel.ts b/src/structures/channel.ts index bdb5f06..ed8d97b 100644 --- a/src/structures/channel.ts +++ b/src/structures/channel.ts @@ -1,4 +1,3 @@ -import cache from '../models/cache.ts' import { Client } from '../models/client.ts' import { ChannelPayload, ChannelTypes } from '../types/channel.ts' import { Base } from './base.ts' diff --git a/src/structures/dmChannel.ts b/src/structures/dmChannel.ts index a9d6ed1..f1ee30f 100644 --- a/src/structures/dmChannel.ts +++ b/src/structures/dmChannel.ts @@ -1,4 +1,3 @@ -import cache from '../models/cache.ts' import { Client } from '../models/client.ts' import { DMChannelPayload } from '../types/channel.ts' import { UserPayload } from '../types/user.ts' diff --git a/src/structures/groupChannel.ts b/src/structures/groupChannel.ts index a0fdfa5..6baf354 100644 --- a/src/structures/groupChannel.ts +++ b/src/structures/groupChannel.ts @@ -1,4 +1,3 @@ -import cache from '../models/cache.ts' import { Client } from '../models/client.ts' import { GroupDMChannelPayload } from '../types/channel.ts' import { Channel } from './channel.ts' diff --git a/src/structures/guild.ts b/src/structures/guild.ts index f536d11..15b1156 100644 --- a/src/structures/guild.ts +++ b/src/structures/guild.ts @@ -1,6 +1,6 @@ import { Client } from '../models/client.ts' import { GuildFeatures, GuildPayload } from '../types/guild.ts' -import { PresenceUpdatePayload } from '../types/presence.ts' +import { PresenceUpdatePayload } from '../types/gateway.ts' import { Base } from './base.ts' import { Channel } from './channel.ts' import { Emoji } from './emoji.ts' @@ -8,8 +8,7 @@ import { Member } from './member.ts' import { VoiceState } from './voiceState.ts' import cache from '../models/cache.ts' import getChannelByType from '../utils/getChannelByType.ts' -import { RolesManager } from "../managers/RolesManager.ts" -import { Role } from "./role.ts" +import { RolesManager } from '../managers/RolesManager.ts' export class Guild extends Base { id: string diff --git a/src/structures/guildCategoryChannel.ts b/src/structures/guildCategoryChannel.ts index 76f38fa..15f9f8f 100644 --- a/src/structures/guildCategoryChannel.ts +++ b/src/structures/guildCategoryChannel.ts @@ -1,10 +1,6 @@ import { Client } from '../models/client.ts' import { Channel } from './channel.ts' -import { - GuildChannelCategoryPayload, - Overwrite -} from '../types/channel.ts' -import cache from '../models/cache.ts' +import { GuildChannelCategoryPayload, Overwrite } from '../types/channel.ts' export class CategoryChannel extends Channel { guildID: string diff --git a/src/structures/guildTextChannel.ts b/src/structures/guildTextChannel.ts index 0bd8b59..49ae7a2 100644 --- a/src/structures/guildTextChannel.ts +++ b/src/structures/guildTextChannel.ts @@ -1,6 +1,5 @@ import { Client } from '../models/client.ts' import { GuildTextChannelPayload, Overwrite } from '../types/channel.ts' -import cache from '../models/cache.ts' import { TextChannel } from './textChannel.ts' export class GuildTextChannel extends TextChannel { diff --git a/src/structures/guildVoiceChannel.ts b/src/structures/guildVoiceChannel.ts index dbfe03d..dc7f7ba 100644 --- a/src/structures/guildVoiceChannel.ts +++ b/src/structures/guildVoiceChannel.ts @@ -1,4 +1,3 @@ -import cache from '../models/cache.ts' import { Client } from '../models/client.ts' import { GuildVoiceChannelPayload, Overwrite } from '../types/channel.ts' import { Channel } from './channel.ts' diff --git a/src/structures/message.ts b/src/structures/message.ts index 5c501eb..b0f9be4 100644 --- a/src/structures/message.ts +++ b/src/structures/message.ts @@ -14,10 +14,9 @@ import { User } from './user.ts' import { Member } from './member.ts' import { Embed } from './embed.ts' import { CHANNEL_MESSAGE } from '../types/endpoint.ts' -import cache from '../models/cache.ts' -import { Channel } from "./channel.ts" -import { MessageMentions } from "./MessageMentions.ts" -import { TextChannel } from "./textChannel.ts" +import { Channel } from './channel.ts' +import { MessageMentions } from './MessageMentions.ts' +import { TextChannel } from './textChannel.ts' export class Message extends Base { // eslint-disable-next-line @typescript-eslint/prefer-readonly @@ -48,7 +47,13 @@ export class Message extends Base { messageReference?: MessageReference flags?: number - constructor (client: Client, data: MessagePayload, channel: Channel, author: User, mentions: MessageMentions) { + constructor ( + client: Client, + data: MessagePayload, + channel: Channel, + author: User, + mentions: MessageMentions + ) { super(client) this.data = data this.id = data.id @@ -116,11 +121,13 @@ export class Message extends Base { this.flags = data.flags ?? this.flags } - edit (text?: string, option?: MessageOption): Promise { - return (this.channel as TextChannel).editMessage(this.id, text, option) + async edit (text?: string, option?: MessageOption): Promise { + // Seriously eslint? + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + return (this.channel as TextChannel).editMessage(this.id, text, option) } - delete (): Promise { - return this.client.rest.delete(CHANNEL_MESSAGE(this.channelID, this.id)) as any + async delete (): Promise { + return this.client.rest.delete(CHANNEL_MESSAGE(this.channelID, this.id)) } } diff --git a/src/structures/role.ts b/src/structures/role.ts index a8e9083..c955e50 100644 --- a/src/structures/role.ts +++ b/src/structures/role.ts @@ -1,4 +1,3 @@ -import cache from '../models/cache.ts' import { Client } from '../models/client.ts' import { Base } from './base.ts' import { RolePayload } from '../types/role.ts' diff --git a/src/structures/textChannel.ts b/src/structures/textChannel.ts index 1662e52..bcc9522 100644 --- a/src/structures/textChannel.ts +++ b/src/structures/textChannel.ts @@ -1,11 +1,9 @@ -import cache from '../models/cache.ts' import { Client } from '../models/client.ts' -import { MessageOption, MessagePayload, TextChannelPayload } from '../types/channel.ts' +import { MessageOption, TextChannelPayload } from '../types/channel.ts' import { CHANNEL_MESSAGE, CHANNEL_MESSAGES } from '../types/endpoint.ts' import { Channel } from './channel.ts' import { Message } from './message.ts' -import { MessageMentions } from "./MessageMentions.ts" -import { User } from "./user.ts" +import { MessageMentions } from './MessageMentions.ts' export class TextChannel extends Channel { lastMessageID?: string @@ -29,6 +27,10 @@ export class TextChannel extends Channel { if (text !== undefined && option !== undefined) { throw new Error('Either text or option is necessary.') } + if (this.client.user === undefined) { + throw new Error('Client user has not initialized.') + } + const resp = await fetch(CHANNEL_MESSAGES(this.id), { headers: { Authorization: `Bot ${this.client.token}`, @@ -44,7 +46,13 @@ export class TextChannel extends Channel { }) }) - return new Message(this.client, await resp.json(), this, this.client.user as User, new MessageMentions()) + return new Message( + this.client, + await resp.json(), + this, + this.client.user, + new MessageMentions() + ) } async editMessage ( @@ -52,21 +60,31 @@ export class TextChannel extends Channel { text?: string, option?: MessageOption ): Promise { - if (text !== undefined && option !== undefined) { + if (text === undefined && option === undefined) { throw new Error('Either text or option is necessary.') } - let newMsg = await this.client.rest.patch(CHANNEL_MESSAGE(this.id, typeof message == "string" ? message : message.id), { - content: text, - embed: option?.embed.toJSON(), - file: option?.file, - tts: option?.tts, - allowed_mentions: option?.allowedMention - }) as MessagePayload + if (this.client.user === undefined) { + throw new Error('Client user has not initialized.') + } + + const newMsg = await this.client.rest.patch( + CHANNEL_MESSAGE( + this.id, + typeof message === 'string' ? message : message.id + ), + { + content: text, + embed: option?.embed.toJSON(), + file: option?.file, + tts: option?.tts, + allowed_mentions: option?.allowedMention + } + ) // TODO: Actually construct this object - let mentions = new MessageMentions() + const mentions = new MessageMentions() - return new Message(this.client, newMsg, this, this.client.user as User, mentions) + return new Message(this.client, newMsg, this, this.client.user, mentions) } } diff --git a/src/structures/user.ts b/src/structures/user.ts index e6e1e13..da86e04 100644 --- a/src/structures/user.ts +++ b/src/structures/user.ts @@ -1,4 +1,3 @@ -import cache from '../models/cache.ts' import { Client } from '../models/client.ts' import { UserPayload } from '../types/user.ts' import { Base } from './base.ts' @@ -18,8 +17,8 @@ export class User extends Base { premiumType?: 0 | 1 | 2 publicFlags?: number - get tag(): string { - return `${this.username}#${this.discriminator}`; + get tag (): string { + return `${this.username}#${this.discriminator}` } get nickMention (): string { @@ -65,7 +64,7 @@ export class User extends Base { this.publicFlags = data.public_flags ?? this.publicFlags } - toString() { - return this.mention; + toString (): string { + return this.mention } } diff --git a/src/structures/voicestate.ts b/src/structures/voicestate.ts index 266e60e..b1672f6 100644 --- a/src/structures/voicestate.ts +++ b/src/structures/voicestate.ts @@ -1,4 +1,3 @@ -import cache from '../models/cache.ts' import { Client } from '../models/client.ts' import { MemberPayload } from '../types/guild.ts' import { VoiceStatePayload } from '../types/voice.ts' diff --git a/src/types/gateway.ts b/src/types/gateway.ts index 4577541..49d038e 100644 --- a/src/types/gateway.ts +++ b/src/types/gateway.ts @@ -2,7 +2,7 @@ // https://discord.com/developers/docs/topics/gateway#commands-and-events-gateway-events import { EmojiPayload } from './emoji.ts' import { MemberPayload } from './guild.ts' -import { ActivityPayload, PresenceUpdatePayload } from './presence.ts' +import { ActivityPayload } from './presence.ts' import { RolePayload } from './role.ts' import { UserPayload } from './user.ts' @@ -100,7 +100,7 @@ enum GatewayEvents { Webhooks_Update = 'WEBHOOKS_UPDATE' } -interface IdentityPayload { +export interface IdentityPayload { token: string properties: IdentityConnection compress?: boolean @@ -119,19 +119,19 @@ enum UpdateStatus { offline = 'offline' } -interface IdentityConnection { +export interface IdentityConnection { $os: 'darwin' | 'windows' | 'linux' | 'custom os' $browser: 'discord.deno' $device: 'discord.deno' } -interface Resume { +export interface Resume { token: string session_id: string seq: number } -interface GuildRequestMembers { +export interface GuildRequestMembers { guild_id: string | string[] query?: string limit: number @@ -140,25 +140,25 @@ interface GuildRequestMembers { nonce?: string } -interface GatewayVoiceStateUpdate { +export interface GatewayVoiceStateUpdate { guild_id: string channel_id: string self_mute: boolean self_deaf: boolean } -interface GatewayStatusUpdate { +export interface GatewayStatusUpdate { since: number | undefined activities: ActivityPayload[] status: string afk: boolean } -interface Hello { +export interface Hello { heartbeat_interval: number } -interface ReadyEvent { +export interface ReadyEvent { v: number user: UserPayload privateChannels: [] @@ -167,40 +167,40 @@ interface ReadyEvent { shard?: number[] } -interface ChannelPinsUpdate { +export interface ChannelPinsUpdatePayload { guild_id?: string channel_id: string last_pin_timestamp?: string } -interface GuildBanAdd { +export interface GuildBanAddPayload { guild_id: string user: UserPayload } -interface GuildBanRemove { +export interface GuildBanRemovePayload { guild_id: string user: UserPayload } -interface GuildEmojiUpdate { +export interface GuildEmojiUpdatePayload { guild_id: string emojis: [] } -interface GuildIntegrationsUpdate { +export interface GuildIntegrationsUpdatePayload { guild_id: string } -interface GuildMemberAddExtra { +export interface GuildMemberAddPayload { guild_id: string } -interface GuildMemberRemove { +export interface GuildMemberRemovePayload { guild_id: string user: UserPayload } -interface GuildMemberUpdate { +export interface GuildMemberUpdatePayload { guild_id: string roles: string[] user: UserPayload @@ -209,7 +209,7 @@ interface GuildMemberUpdate { premium_since?: string | undefined } -interface GuildMemberChunk { +export interface GuildMemberChunkPayload { guild_id: string members: MemberPayload[] chunk_index: number @@ -219,22 +219,22 @@ interface GuildMemberChunk { nonce?: string } -interface GuildRoleCreate { +export interface GuildRoleCreatePayload { guild_id: string role: RolePayload } -interface GuildRoleUpdate { +export interface GuildRoleUpdatePayload { guild_id: string role: RolePayload } -interface GuildRoleDelete { +export interface GuildRoleDeletePayload { guild_id: string role_id: string } -interface InviteCreate { +export interface InviteCreatePayload { channel_id: string code: string created_at: string @@ -248,25 +248,25 @@ interface InviteCreate { uses: number } -interface InviteDelete { +export interface InviteDeletePayload { channel_id: string guild_id?: string code: string } -interface MessageDelete { +export interface MessageDeletePayload { id: string channel_id: string guild_id?: string } -interface MessageDeleteBulk { +export interface MessageDeleteBulkPayload { ids: string[] channel_id: string guild_id: string } -interface MessageReactionAdd { +export interface MessageReactionAddPayload { user_id: string channel_id: string message_id: string @@ -274,7 +274,7 @@ interface MessageReactionAdd { emoji: EmojiPayload } -interface MessageReactionRemove { +export interface MessageReactionRemovePayload { user_id: string channel_id: string message_id: string @@ -282,21 +282,13 @@ interface MessageReactionRemove { emoji: EmojiPayload } -interface MessageReactionRemoveAll { +export interface MessageReactionRemoveAllPayload { channel_id: string guild_id?: string message_id: string - emoji: EmojiPayload } -interface MessageReactionRemove { - channel_id: string - guild_id?: string - message_id: string - emoji: EmojiPayload -} - -interface PresenceUpdate { +export interface PresenceUpdatePayload { user: UserPayload guild_id: string status: string @@ -304,76 +296,7 @@ interface PresenceUpdate { client_status: UpdateStatus[] } -interface CilentStatus { - desktop?: string - moblie?: string - web?: string -} - -interface Activity { - name: string - type: number - url?: string | undefined - created_at: number - timestamps?: string - application_id: string - details?: string | undefined - state?: string | undefined - emoji?: EmojiPayload | undefined - party?: ActivityParty - assets?: ActivityAssets - secrets?: ActivitySecrets - instance?: boolean - flags?: number -} - -enum ActivityTypes { - GAME = 0, - STREAMING = 1, - LISTENING = 2, - CUSTOM = 4, - COMPETING = 5 -} - -interface ActivityTimestamps { - start?: number - end?: number -} - -interface ActivityEmoji { - name: string - id?: string - animated?: boolean -} - -interface ActivityParty { - id?: string - size?: number[] -} - -interface ActivityAssets { - large_image?: string - large_text?: string - small_image?: string - small_text?: string -} - -interface ActivitySecrets { - join?: string - spectate?: string - match?: string -} - -enum ActivityFlags { - INSTANCE = 1 << 0, - JOIN = 1 << 1, - SPECTATE = 1 << 2, - JOIN_REQUEST = 1 << 3, - SYNC = 1 << 4, - PLAY = 1 << 5 -} - -interface TypeStart { +export interface TypeStart { channel_id: string guild_id?: string user_id: string @@ -381,16 +304,22 @@ interface TypeStart { member?: MemberPayload } -interface VoiceServerUpdate { +export interface VoiceServerUpdatePayload { token: string guild_id: string endpoint: string } -interface WebhooksUpdate { +export interface WebhooksUpdatePayload { guild_id: string channel_id: string } // https://discord.com/developers/docs/topics/gateway#typing-start-typing-start-event-fields -export { GatewayCloseCodes, GatewayOpcodes, GatewayIntents, GatewayEvents } +export { + GatewayCloseCodes, + GatewayOpcodes, + GatewayIntents, + GatewayEvents, + UpdateStatus +} diff --git a/src/types/gatewayTypes.ts b/src/types/gatewayTypes.ts deleted file mode 100644 index 4577541..0000000 --- a/src/types/gatewayTypes.ts +++ /dev/null @@ -1,396 +0,0 @@ -// https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway -// https://discord.com/developers/docs/topics/gateway#commands-and-events-gateway-events -import { EmojiPayload } from './emoji.ts' -import { MemberPayload } from './guild.ts' -import { ActivityPayload, PresenceUpdatePayload } from './presence.ts' -import { RolePayload } from './role.ts' -import { UserPayload } from './user.ts' - -/** - * Gateway OPcodes from Discord docs. - */ -enum GatewayOpcodes { // 문서를 확인해본 결과 Opcode 5번은 비어있다. - UnderC - - DISPATCH = 0, - HEARTBEAT = 1, - IDENTIFY = 2, - PRESENCE_UPDATE = 3, - VOICE_STATE_UPDATE = 4, - RESUME = 6, - RECONNECT = 7, - REQUEST_GUILD_MEMBERS = 8, - INVALID_SESSION = 9, - HELLO = 10, - HEARTBEAT_ACK = 11 -} - -/** - * Gateway Close Codes from Discord docs. - */ -enum GatewayCloseCodes { - UNKNOWN_ERROR = 4000, - UNKNOWN_OPCODE = 4001, - DECODE_ERROR = 4002, - NOT_AUTHENTICATED = 4003, - AUTHENTICATION_FAILED = 4004, - ALREADY_AUTHENTICATED = 4005, - INVALID_SEQ = 4007, - RATE_LIMITED = 4008, - SESSION_TIMED_OUT = 4009, - INVALID_SHARD = 4010, - SHARDING_REQUIRED = 4011, - INVALID_API_VERSION = 4012, - INVALID_INTENTS = 4013, - DISALLOWED_INTENTS = 4014 -} - -enum GatewayIntents { - GUILDS = 1 << 0, - GUILD_MEMBERS = 1 << 1, - GUILD_BANS = 1 << 2, - GUILD_EMOJIS = 1 << 3, - GUILD_INTEGRATIONS = 1 << 4, - GUILD_WEBHOOKS = 1 << 5, - GUILD_INVITES = 1 << 6, - GUILD_VOICE_STATES = 1 << 7, - GUILD_PRESENCES = 1 << 8, - GUILD_MESSAGES = 1 << 9, - GUILD_MESSAGE_REACTIONS = 1 << 10, - GUILD_MESSAGE_TYPING = 1 << 11, - DIRECT_MESSAGES = 1 << 12, - DIRECT_MESSAGE_REACTIONS = 1 << 13, - DIRECT_MESSAGE_TYPING = 1 << 13 -} - -enum GatewayEvents { - Ready = 'READY', - Resumed = 'RESUMED', - Reconnect = 'RECONNECT', - Channel_Create = 'CHANNEL_CREATE', - Channel_Update = 'CHANNEL_UPDATE', - Channel_Delete = 'CHANNEL_DELETE', - Channel_Pins_Update = 'CHANNEL_PINS_UPDATE', - Guild_Create = 'GUILD_CREATE', - Guild_Update = 'GUILD_UPDATE', - Guild_Delete = 'GUILD_DELETE', - Guild_Ban_Add = 'GUILD_BAN_ADD', - Guild_Ban_Remove = 'GUILD_BAN_REMOVE', - Guild_Emojis_Update = 'GUILD_EMOJIS_UPDATE', - Guild_Integrations_Update = 'GUILD_INTEGRATIONS_UPDATE', - Guild_Member_Add = 'GUILD_MEMBER_ADD', - Guild_Member_Remove = 'GUILD_MEMBER_REMOVE', - Guild_Member_Update = 'GUILD_MEMBER_UPDATE', - Guild_Members_Chunk = 'GUILD_MEMBERS_CHUNK', - Guild_Role_Create = 'GUILD_ROLE_CREATE', - Guild_Role_Update = 'GUILD_ROLE_UPDATE', - Guild_Role_Delete = 'GUILD_ROLE_DELETE', - Invite_Create = 'INVITE_CREATE', - Invite_Delete = 'INVITE_DELETE', - Message_Create = 'MESSAGE_CREATE', - Message_Update = 'MESSAGE_UPDATE', - Message_Delete = 'MESSAGE_DELETE', - Message_Delete_Bulk = 'MESSAGE_DELETE_BULK', - Message_Reaction_Add = 'MESSAGE_REACTION_ADD', - Message_Reaction_Remove = 'MESSAGE_REACTION_REMOVE', - Message_Reaction_Remove_All = 'MESSAGE_REACTION_REMOVE_ALL', - Message_Reaction_Remove_Emoji = 'MESSAGE_REACTION_REMOVE_EMOJI', - Presence_Update = 'PRESENCE_UPDATE', - Typing_Start = 'TYPING_START', - User_Update = 'USER_UPDATE', - Voice_Server_Update = 'VOICE_SERVER_UPDATE', - Webhooks_Update = 'WEBHOOKS_UPDATE' -} - -interface IdentityPayload { - token: string - properties: IdentityConnection - compress?: boolean - large_threshold?: number - shard?: number[] - presence?: UpdateStatus - guildSubscriptions?: boolean - intents: number -} - -enum UpdateStatus { - online = 'online', - dnd = 'dnd', - afk = 'idle', - invisible = 'invisible', - offline = 'offline' -} - -interface IdentityConnection { - $os: 'darwin' | 'windows' | 'linux' | 'custom os' - $browser: 'discord.deno' - $device: 'discord.deno' -} - -interface Resume { - token: string - session_id: string - seq: number -} - -interface GuildRequestMembers { - guild_id: string | string[] - query?: string - limit: number - presences?: boolean - user_ids?: string | string[] - nonce?: string -} - -interface GatewayVoiceStateUpdate { - guild_id: string - channel_id: string - self_mute: boolean - self_deaf: boolean -} - -interface GatewayStatusUpdate { - since: number | undefined - activities: ActivityPayload[] - status: string - afk: boolean -} - -interface Hello { - heartbeat_interval: number -} - -interface ReadyEvent { - v: number - user: UserPayload - privateChannels: [] - guilds: [] - session_id: string - shard?: number[] -} - -interface ChannelPinsUpdate { - guild_id?: string - channel_id: string - last_pin_timestamp?: string -} - -interface GuildBanAdd { - guild_id: string - user: UserPayload -} - -interface GuildBanRemove { - guild_id: string - user: UserPayload -} - -interface GuildEmojiUpdate { - guild_id: string - emojis: [] -} - -interface GuildIntegrationsUpdate { - guild_id: string -} - -interface GuildMemberAddExtra { - guild_id: string -} - -interface GuildMemberRemove { - guild_id: string - user: UserPayload -} -interface GuildMemberUpdate { - guild_id: string - roles: string[] - user: UserPayload - nick?: string | undefined - joined_at: string - premium_since?: string | undefined -} - -interface GuildMemberChunk { - guild_id: string - members: MemberPayload[] - chunk_index: number - chunk_count: number - not_found?: [] - presences?: PresenceUpdatePayload[] - nonce?: string -} - -interface GuildRoleCreate { - guild_id: string - role: RolePayload -} - -interface GuildRoleUpdate { - guild_id: string - role: RolePayload -} - -interface GuildRoleDelete { - guild_id: string - role_id: string -} - -interface InviteCreate { - channel_id: string - code: string - created_at: string - guild_id?: string - inviter?: UserPayload - max_age: number - max_uses: number - target_user?: UserPayload - target_user_type?: number - temporary: boolean - uses: number -} - -interface InviteDelete { - channel_id: string - guild_id?: string - code: string -} - -interface MessageDelete { - id: string - channel_id: string - guild_id?: string -} - -interface MessageDeleteBulk { - ids: string[] - channel_id: string - guild_id: string -} - -interface MessageReactionAdd { - user_id: string - channel_id: string - message_id: string - guild_id?: string - emoji: EmojiPayload -} - -interface MessageReactionRemove { - user_id: string - channel_id: string - message_id: string - guild_id?: string - emoji: EmojiPayload -} - -interface MessageReactionRemoveAll { - channel_id: string - guild_id?: string - message_id: string - emoji: EmojiPayload -} - -interface MessageReactionRemove { - channel_id: string - guild_id?: string - message_id: string - emoji: EmojiPayload -} - -interface PresenceUpdate { - user: UserPayload - guild_id: string - status: string - activities: ActivityPayload[] - client_status: UpdateStatus[] -} - -interface CilentStatus { - desktop?: string - moblie?: string - web?: string -} - -interface Activity { - name: string - type: number - url?: string | undefined - created_at: number - timestamps?: string - application_id: string - details?: string | undefined - state?: string | undefined - emoji?: EmojiPayload | undefined - party?: ActivityParty - assets?: ActivityAssets - secrets?: ActivitySecrets - instance?: boolean - flags?: number -} - -enum ActivityTypes { - GAME = 0, - STREAMING = 1, - LISTENING = 2, - CUSTOM = 4, - COMPETING = 5 -} - -interface ActivityTimestamps { - start?: number - end?: number -} - -interface ActivityEmoji { - name: string - id?: string - animated?: boolean -} - -interface ActivityParty { - id?: string - size?: number[] -} - -interface ActivityAssets { - large_image?: string - large_text?: string - small_image?: string - small_text?: string -} - -interface ActivitySecrets { - join?: string - spectate?: string - match?: string -} - -enum ActivityFlags { - INSTANCE = 1 << 0, - JOIN = 1 << 1, - SPECTATE = 1 << 2, - JOIN_REQUEST = 1 << 3, - SYNC = 1 << 4, - PLAY = 1 << 5 -} - -interface TypeStart { - channel_id: string - guild_id?: string - user_id: string - timestamp: number - member?: MemberPayload -} - -interface VoiceServerUpdate { - token: string - guild_id: string - endpoint: string -} - -interface WebhooksUpdate { - guild_id: string - channel_id: string -} - -// https://discord.com/developers/docs/topics/gateway#typing-start-typing-start-event-fields -export { GatewayCloseCodes, GatewayOpcodes, GatewayIntents, GatewayEvents } diff --git a/src/types/permissionFlags.ts b/src/types/permissionFlags.ts index 83936a4..9af057e 100644 --- a/src/types/permissionFlags.ts +++ b/src/types/permissionFlags.ts @@ -39,3 +39,5 @@ enum PermissionFlags { MANAGE_WEBHOOKS = 0x20000000, MANAGE_EMOJIS = 0x40000000 } + +export { PermissionFlags } diff --git a/src/types/presence.ts b/src/types/presence.ts index f4564bf..e304012 100644 --- a/src/types/presence.ts +++ b/src/types/presence.ts @@ -1,14 +1,4 @@ -import { UserPayload } from './user.ts' - -export interface PresenceUpdatePayload { - user: UserPayload - guild_id: string - status: string - activities: ActivityPayload - client_status: ClientStatus -} - -interface ClientStatus { +export interface ClientStatus { desktop?: string mobile?: string web?: string @@ -31,30 +21,30 @@ export interface ActivityPayload { flags?: number } -interface ActivityTimestamps { +export interface ActivityTimestamps { start?: number end?: number } -interface ActivityEmoji { +export interface ActivityEmoji { name: string id?: string animated?: boolean } -interface ActivityParty { +export interface ActivityParty { id?: string size?: number[] } -interface ActivityAssets { +export interface ActivityAssets { large_image?: string large_text?: string small_image?: string small_text?: string } -interface ActivitySecrets { +export interface ActivitySecrets { join?: string spectate?: string match?: string @@ -68,3 +58,5 @@ enum ActivityFlags { SYNC = 1 << 4, PLAY = 1 << 5 } + +export { ActivityFlags }