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/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f83bd91 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# Welcome! + +This document is for people who want to contribute to this repository! + +## Code Style + +We use [standard.js](https://standardjs.org) with [eslint](https://eslint.org) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint). +So please don't make as lint errors as possible. There're many rules in standard.js but the highlight things are: + +- Use `camelCase` for function names, variables, etc. +- Use `PascalCase` for class names. +- Add return types on function. Ex: + +```ts +const example = (): void => {} +``` + +- Do not make unused variables or unused imports. + +These are not on standard.js but we want you to follow. + +- Make names to simple but understandable for someone whose English is not a primary language. + +## File Name Style + +Nothing much, but please make it as simple as possible, and in `camelCase`. + +## Submitting PR + +When submitting PR, please make the title as simple as possible. Ex: `[Feature improvement]: Cache can now be loaded much faster` +Also, please make it understandable for someone whose English is not a primary language. + +Thanks! diff --git a/README.md b/README.md index bab4ebe..f6d6b4b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,52 @@ -# discord.deno +# discord-deno -## Feature +![banner](images/discord-deno.png) + +[![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) + +Discord Deno API that is easy to use + +## Table of Contents + +- [Usage](#usage) +- [Docs](#docs) +- [Maintainers](#maintainers) +- [Contributing](#contributing) +- [License](#license) + +## Usage + +```ts +import { Client } from 'https://deno.land/x/discord-deno/models/client.ts' +import { Message } from 'https://deno.land/x/discord-deno/structures/message.ts' + +const bot = new Client() + +bot.on('messageCreate', (msg: Message): void => { + if (msg.content === '!ping') { + msg.channel.send(`Pong! ping: ${bot.ping}`) + } +}) + +bot.connect(TOKEN, [GatewayIntents.GUILD_MESSAGES]) +``` + +## Docs + +Not made yet + +## Maintainers + +[@Helloyunho](https://github.com/Helloyunho) + +## Contributing + +See [the contributing file](CONTRIBUTING.md)! + +PRs accepted. + +Small note: If editing the README, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification. + +## License + +MIT © 2020 Helloyunho 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 910a695..4866bb5 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 | void = 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 3c676c9..da12344 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 | void = 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 6c66393..77e102d 100644 --- a/src/gateway/handlers/channelUpdate.ts +++ b/src/gateway/handlers/channelUpdate.ts @@ -1,11 +1,12 @@ import { Channel } from '../../structures/channel.ts' import { Guild } from "../../structures/guild.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 | void = 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 2e47d51..6352743 100644 --- a/src/gateway/handlers/guildCreate.ts +++ b/src/gateway/handlers/guildCreate.ts @@ -7,7 +7,7 @@ import { RolePayload } from "../../types/role.ts" import { RolesManager } from "../../managers/RolesManager.ts" export const guildCreate: GatewayEventHandler = async(gateway: Gateway, d: GuildPayload) => { - let guild: Guild | void = await gateway.client.guilds.get(d.id) + 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) @@ -47,6 +47,7 @@ export const guildCreate: GatewayEventHandler = async(gateway: Gateway, d: Guild guild.roles = roles } await guild.roles.fromPayload(d.roles) + 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 4877713..f9e6db3 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 734f03b..7cae77a 100644 --- a/src/gateway/handlers/messageCreate.ts +++ b/src/gateway/handlers/messageCreate.ts @@ -1,19 +1,20 @@ -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) // 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 guild if(d.guild_id) { diff --git a/src/gateway/index.ts b/src/gateway/index.ts index 7aa72a8..067d416 100644 --- a/src/gateway/index.ts +++ b/src/gateway/index.ts @@ -5,7 +5,11 @@ 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" @@ -52,7 +56,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 { @@ -70,12 +74,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 } @@ -91,7 +98,8 @@ class Gateway { this.sendIdentify(this.client.forceNewSession) this.initialized = true } else { - console.log("Calling Resume") + console.log('Calling Resume') + // eslint-disable-next-line @typescript-eslint/no-floating-promises this.sendResume() } break @@ -99,13 +107,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 @@ -113,7 +124,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] @@ -128,11 +139,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 } @@ -141,41 +153,46 @@ class Gateway { } } - private onclose(event: CloseEvent): void { - this.debug("Connection Closed with code: " + event.code) + private onclose (event: CloseEvent): void { + 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() } } @@ -185,20 +202,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.send({ @@ -224,27 +246,29 @@ 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 + 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.send(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 0a9331d..8fc8689 100644 --- a/src/managers/BaseManager.ts +++ b/src/managers/BaseManager.ts @@ -4,29 +4,29 @@ import { Collection } from "../utils/collection.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) } - set(key: string, value: T) { + async set (key: string, value: T): Promise { return this.client.cache.set(this.cacheName, key, value) } - delete(key: string) { + async delete (key: string): Promise { return this.client.cache.delete(this.cacheName, key) } @@ -49,4 +49,4 @@ export class BaseManager { flush() { return this.client.cache.deleteCache(this.cacheName) } -} \ No newline at end of file +} diff --git a/src/managers/ChannelsManager.ts b/src/managers/ChannelsManager.ts index 1d0970b..b998cc4 100644 --- a/src/managers/ChannelsManager.ts +++ b/src/managers/ChannelsManager.ts @@ -48,4 +48,4 @@ export class ChannelsManager extends BaseManager { }).catch(e => rej(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 787b625..82c3f47 100644 --- a/src/managers/GatewayCache.ts +++ b/src/managers/GatewayCache.ts @@ -1,23 +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) { - 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 45a8d4f..39271e3 100644 --- a/src/managers/GuildsManager.ts +++ b/src/managers/GuildsManager.ts @@ -7,8 +7,8 @@ import { BaseManager } from "./BaseManager.ts"; import { MembersManager } from "./MembersManager.ts"; export class GuildManager extends BaseManager { - constructor(client: Client) { - super(client, "guilds", Guild) + constructor (client: Client) { + super(client, 'guilds', Guild) } fetch(id: string) { @@ -25,4 +25,4 @@ export class GuildManager extends BaseManager { }).catch(e => rej(e)) }) } -} \ No newline at end of file +} diff --git a/src/managers/MessagesManager.ts b/src/managers/MessagesManager.ts index 24c1596..d9b9611 100644 --- a/src/managers/MessagesManager.ts +++ b/src/managers/MessagesManager.ts @@ -9,8 +9,8 @@ import { UserPayload } from "../types/user.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) } async get(key: string): Promise { @@ -38,4 +38,4 @@ export class MessagesManager extends BaseManager { }).catch(e => rej(e)) }) } -} \ No newline at end of file +} diff --git a/src/managers/RolesManager.ts b/src/managers/RolesManager.ts index 2f0909f..3cc1889 100644 --- a/src/managers/RolesManager.ts +++ b/src/managers/RolesManager.ts @@ -1,24 +1,27 @@ -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"; +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)) }) } @@ -28,4 +31,4 @@ export class RolesManager extends BaseManager { } return true } -} \ 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 9b2da79..96b2615 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 + array: (cacheName: string) => undefined | any[] | Promise deleteCache: (cacheName: string) => any } @@ -17,34 +21,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() } @@ -59,45 +63,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)) } @@ -105,4 +124,4 @@ export class RedisCacheAdapter implements ICacheAdapter { await this._checkReady() return await this.redis?.del(cacheName) } -} \ No newline at end of file +} diff --git a/src/models/client.ts b/src/models/client.ts index 9059091..e4c9013 100644 --- a/src/models/client.ts +++ b/src/models/client.ts @@ -32,7 +32,6 @@ export class Client extends EventEmitter { cache: ICacheAdapter = new DefaultCacheAdapter(this) intents?: GatewayIntents[] forceNewSession?: boolean - users: UserManager = new UserManager(this) guilds: GuildManager = new GuildManager(this) channels: ChannelsManager = new ChannelsManager(this) @@ -46,11 +45,11 @@ export class Client extends EventEmitter { this.token = options.token this.intents = options.intents this.forceNewSession = options.forceNewSession - if(options.cache) this.cache = options.cache - if(options.presence) this.presence = options.presence instanceof ClientPresence ? options.presence : new ClientPresence(options.presence) + if (options.cache !== undefined) this.cache = options.cache + if (options.presence !== undefined) this.presence = options.presence instanceof ClientPresence ? options.presence : new ClientPresence(options.presence) } - setAdapter(adapter: ICacheAdapter) { + setAdapter (adapter: ICacheAdapter): Client { this.cache = adapter return this } @@ -72,16 +71,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 b87dc80..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,350 +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; - } + constructor (client: Client) { + this.client = client + setTimeout(this.processRateLimitedPaths, 1000) + } - 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; - }); + 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 + }) + } - await delay(1000); - this.processRateLimitedPaths(); - } + 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 - 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]; - } - }); - } + if (this.pathQueues[id] !== undefined) { + this.pathQueues[id].push(request) + } else { + this.pathQueues[id] = [request] + } + } - 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; - } + 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] + } + }) + } - createRequestBody(body: any, method: RequestMethods) { - const headers: { [key: string]: string } = { - Authorization: `Bot ${this.client.token}`, - "User-Agent": - `DiscordBot (discord.deno)`, - }; + 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 - 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(), - }; - } + const rateLimitedURLResetIn = await this.checkRatelimits(request.url) - 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; - } + 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 + }) + } + } + } + }) + ) + } - 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; + 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 + } + + 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) - const response = await fetch(urlToUse, this.createRequestBody(body, method)); - const bucketIDFromHeaders = this.processHeaders(url, response.headers); - // Sometimes Discord returns an empty 204 response that can't be made to JSON. - if (response.status === 204) return resolve(); + if (response.status === 204) return resolve(undefined) - this.handleStatusCode(response, errorStack); - - 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(); - } - }); - } + 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') + } - async logErrors(response: Response, errorStack?: unknown) { - try { - const error = await response.json(); - console.error(error); - } catch { - console.error(response); - } - } + return { + rateLimited: json.retry_after, + beforeFetch: false, + bucketID: bucketIDFromHeaders + } + } + return resolve(json) + } catch (error) { + return reject(error) + } + } - async handleStatusCode(response: Response, errorStack?: unknown) { + 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; - } - - 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. Code: " + status); - case HttpResponseCode.GatewayUnavailable: - throw new Error("Request Server Error. Code: " + status); - } - - // 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 (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; - } + if ( + (status >= 200 && status < 400) || + status === HttpResponseCode.TooManyRequests + ) { + return true + } - get(url: string, body?: unknown) { - return this.runMethod("get", url, body); + // 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') } - post(url: string, body?: unknown) { - return this.runMethod("post", url, body); + 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 } - delete(url: string, body?: unknown) { - return this.runMethod("delete", url, body); + async get (url: string, body?: unknown): Promise { + return await this.runMethod('get', url, body) } - patch(url: string, body?: unknown) { - return this.runMethod("patch", url, body); + async post (url: string, body?: unknown): Promise { + return await this.runMethod('post', url, body) } - put(url: string, body?: unknown) { - return this.runMethod("put", url, body); + async delete (url: string, body?: unknown): Promise { + return await this.runMethod('delete', url, body) } -} \ No newline at end of file + + 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 22a3196..1513216 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 b5484d0..8172df7 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' diff --git a/src/structures/guildVoiceChannel.ts b/src/structures/guildVoiceChannel.ts index 100d395..ed402ff 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 bba6372..33b4fed 100644 --- a/src/structures/message.ts +++ b/src/structures/message.ts @@ -14,7 +14,6 @@ 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" @@ -51,7 +50,13 @@ export class Message extends Base { messageReference?: MessageReference flags?: number - constructor (client: Client, data: MessagePayload, channel: TextChannel, author: User, mentions: MessageMentions) { + constructor ( + client: Client, + data: MessagePayload, + channel: TextChannel, + author: User, + mentions: MessageMentions + ) { super(client) this.data = data this.id = data.id @@ -120,7 +125,7 @@ export class Message extends Base { } edit (text?: string, option?: MessageOption): Promise { - return (this.channel as TextChannel).edit(this.id, text, option) + return this.channel.edit(this.id, text, option) } reply(text: string, options?: MessageOption) { @@ -129,7 +134,7 @@ export class Message extends Base { return this.channel.send(`${this.author.mention}, ${text}`, options) } - 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 43581c5..ffeed93 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 @@ -37,7 +35,7 @@ export class TextChannel extends Channel { allowed_mentions: option?.allowedMention }) - return new Message(this.client, resp as any, this, this.client.user as User, new MessageMentions()) + return new Message(this.client, resp as any, this, this.client.user, new MessageMentions()) } async edit ( @@ -45,21 +43,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 }