Merge branch 'main' into main

This commit is contained in:
Helloyunho 2020-11-03 01:31:10 +09:00 committed by GitHub
commit 2af8e15452
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 813 additions and 1080 deletions

5
.gitignore vendored
View file

@ -109,4 +109,7 @@ yarn.lock
# PRIVACY XDDDD # PRIVACY XDDDD
src/test/config.ts src/test/config.ts
.vscode .vscode
# macOS is shit xD
**/.DS_Store

33
CONTRIBUTING.md Normal file
View file

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

View file

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

View file

@ -1,9 +1,10 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import { Gateway, GatewayEventHandler } from '../index.ts'
import getChannelByType from '../../utils/getChannelByType.ts' import getChannelByType from '../../utils/getChannelByType.ts'
import { ChannelPayload } from '../../types/channel.ts'
export const channelCreate: GatewayEventHandler = async ( export const channelCreate: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,
d: any d: ChannelPayload
) => { ) => {
const channel = getChannelByType(gateway.client, d) const channel = getChannelByType(gateway.client, d)
if (channel !== undefined) { if (channel !== undefined) {

View file

@ -1,9 +1,10 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import { Gateway, GatewayEventHandler } from '../index.ts'
import { Channel } from '../../structures/channel.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, gateway: Gateway,
d: any d: ChannelPayload
) => { ) => {
const channel: Channel | void = await gateway.client.channels.get(d.id) const channel: Channel | void = await gateway.client.channels.get(d.id)
if (channel !== undefined) { if (channel !== undefined) {

View file

@ -1,19 +1,21 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import { Gateway, GatewayEventHandler } from '../index.ts'
import cache from '../../models/cache.ts'
import { TextChannel } from '../../structures/textChannel.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, gateway: Gateway,
d: any d: ChannelPinsUpdatePayload
) => { ) => {
const after: TextChannel | void = await gateway.client.channels.get<TextChannel>(d.channel_id) const after: TextChannel | void = await gateway.client.channels.get<TextChannel>(d.channel_id)
if (after !== undefined) { if (after !== undefined) {
const before = after.refreshFromData({ const before = after.refreshFromData({
last_pin_timestamp: d.last_pin_timestamp last_pin_timestamp: d.last_pin_timestamp
}) })
const raw = await gateway.client.channels._get(d.channel_id) ; 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 })) await gateway.client.channels.set(
after.id,
Object.assign(raw, { last_pin_timestamp: d.last_pin_timestamp })
)
gateway.client.emit('channelPinsUpdate', before, after) gateway.client.emit('channelPinsUpdate', before, after)
} }
} }

View file

@ -1,11 +1,12 @@
import { Channel } from '../../structures/channel.ts' import { Channel } from '../../structures/channel.ts'
import { Guild } from "../../structures/guild.ts" import { Guild } from "../../structures/guild.ts"
import { ChannelPayload } from '../../types/channel.ts'
import getChannelByType from '../../utils/getChannelByType.ts' import getChannelByType from '../../utils/getChannelByType.ts'
import { Gateway, GatewayEventHandler } from '../index.ts' import { Gateway, GatewayEventHandler } from '../index.ts'
export const channelUpdate: GatewayEventHandler = async ( export const channelUpdate: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,
d: any d: ChannelPayload
) => { ) => {
const oldChannel: Channel | void = await gateway.client.channels.get(d.id) const oldChannel: Channel | void = await gateway.client.channels.get(d.id)

View file

@ -1,14 +1,19 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import { Gateway, GatewayEventHandler } from '../index.ts'
import cache from '../../models/cache.ts'
import { Guild } from '../../structures/guild.ts' import { Guild } from '../../structures/guild.ts'
import { User } from '../../structures/user.ts' import { User } from '../../structures/user.ts'
import { GuildBanAddPayload } from '../../types/gateway.ts'
export const guildBanAdd: GatewayEventHandler = (gateway: Gateway, d: any) => { export const guildBanAdd: GatewayEventHandler = async (
const guild: Guild = cache.get('guild', d.guild_id) gateway: Gateway,
d: GuildBanAddPayload
) => {
const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id)
const user: User = 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) { if (guild !== undefined) {
guild.members = guild.members?.filter(member => member.id !== d.user.id)
gateway.client.emit('guildBanAdd', guild, user) gateway.client.emit('guildBanAdd', guild, user)
} }
} }

View file

@ -2,10 +2,11 @@ import { Gateway, GatewayEventHandler } from '../index.ts'
import cache from '../../models/cache.ts' import cache from '../../models/cache.ts'
import { Guild } from '../../structures/guild.ts' import { Guild } from '../../structures/guild.ts'
import { User } from '../../structures/user.ts' import { User } from '../../structures/user.ts'
import { GuildBanRemovePayload } from '../../types/gateway.ts'
export const guildBanRemove: GatewayEventHandler = ( export const guildBanRemove: GatewayEventHandler = (
gateway: Gateway, gateway: Gateway,
d: any d: GuildBanRemovePayload
) => { ) => {
const guild: Guild = cache.get('guild', d.guild_id) const guild: Guild = cache.get('guild', d.guild_id)
const user: User = const user: User =

View file

@ -7,7 +7,7 @@ import { RolePayload } from "../../types/role.ts"
import { RolesManager } from "../../managers/RolesManager.ts" import { RolesManager } from "../../managers/RolesManager.ts"
export const guildCreate: GatewayEventHandler = async(gateway: Gateway, d: GuildPayload) => { 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) { 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 // 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) await gateway.client.guilds.set(d.id, d)
@ -47,6 +47,7 @@ export const guildCreate: GatewayEventHandler = async(gateway: Gateway, d: Guild
guild.roles = roles guild.roles = roles
} }
await guild.roles.fromPayload(d.roles) await guild.roles.fromPayload(d.roles)
guild = new Guild(gateway.client, d)
gateway.client.emit('guildCreate', guild) gateway.client.emit('guildCreate', guild)
} }
} }

View file

@ -1,8 +1,12 @@
import { Guild } from '../../structures/guild.ts' import { Guild } from '../../structures/guild.ts'
import { GuildPayload } from '../../types/guild.ts'
import { Gateway, GatewayEventHandler } from '../index.ts' import { Gateway, GatewayEventHandler } from '../index.ts'
export const guildDelte: GatewayEventHandler = async (gateway: Gateway, d: any) => { export const guildDelte: GatewayEventHandler = async (
const guild: Guild | void = await gateway.client.guilds.get(d.id) gateway: Gateway,
d: GuildPayload
) => {
const guild: Guild | undefined = await gateway.client.guilds.get(d.id)
if (guild !== undefined) { if (guild !== undefined) {
guild.refreshFromData(d) guild.refreshFromData(d)

View file

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

View file

@ -1,10 +1,14 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import { Gateway, GatewayEventHandler } from '../index.ts'
import { Guild } from '../../structures/guild.ts' import { Guild } from '../../structures/guild.ts'
import { GuildPayload } from '../../types/guild.ts'
export const guildUpdate: GatewayEventHandler = async(gateway: Gateway, d: any) => { export const guildUpdate: GatewayEventHandler = async (
const before: Guild | void = await gateway.client.guilds.get(d.id) gateway: Gateway,
if(!before) return 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) 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) gateway.client.emit('guildUpdate', before, after)
} }

View file

@ -1,19 +1,20 @@
import { Channel } from "../../structures/channel.ts" import { Channel } from '../../structures/channel.ts'
import { Message } from "../../structures/message.ts" import { Message } from '../../structures/message.ts'
import { MessageMentions } from "../../structures/MessageMentions.ts" import { MessageMentions } from '../../structures/MessageMentions.ts'
import { TextChannel } from "../../structures/textChannel.ts" import { TextChannel } from '../../structures/textChannel.ts'
import { User } from "../../structures/user.ts" import { User } from '../../structures/user.ts'
import { MessagePayload } from "../../types/channel.ts" import { MessagePayload } from '../../types/channel.ts'
import { Gateway, GatewayEventHandler } from '../index.ts' import { Gateway, GatewayEventHandler } from '../index.ts'
export const messageCreate: GatewayEventHandler = async( export const messageCreate: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,
d: MessagePayload d: MessagePayload
) => { ) => {
let channel = await gateway.client.channels.get<TextChannel>(d.channel_id) let channel = await gateway.client.channels.get<TextChannel>(d.channel_id)
// Fetch the channel if not cached // Fetch the channel if not cached
if(!channel) channel = (await gateway.client.channels.fetch(d.channel_id) as any) as TextChannel if (channel === undefined)
let user = new User(gateway.client, d.author) 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) await gateway.client.users.set(d.author.id, d.author)
let guild let guild
if(d.guild_id) { if(d.guild_id) {

View file

@ -5,7 +5,11 @@ import {
DISCORD_API_VERSION DISCORD_API_VERSION
} from '../consts/urlsAndVersions.ts' } from '../consts/urlsAndVersions.ts'
import { GatewayResponse } from '../types/gatewayResponse.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 { gatewayHandlers } from './handlers/index.ts'
import { GATEWAY_BOT } from '../types/endpoint.ts' import { GATEWAY_BOT } from '../types/endpoint.ts'
import { GatewayBotPayload } from "../types/gatewayBot.ts" import { GatewayBotPayload } from "../types/gatewayBot.ts"
@ -52,7 +56,7 @@ class Gateway {
private onopen(): void { private onopen(): void {
this.connected = true this.connected = true
this.debug("Connected to Gateway!") this.debug('Connected to Gateway!')
} }
private async onmessage(event: MessageEvent): Promise<void> { private async onmessage(event: MessageEvent): Promise<void> {
@ -70,12 +74,15 @@ class Gateway {
switch (op) { switch (op) {
case GatewayOpcodes.HELLO: case GatewayOpcodes.HELLO:
this.heartbeatInterval = d.heartbeat_interval this.heartbeatInterval = d.heartbeat_interval
this.debug(`Received HELLO. Heartbeat Interval: ${this.heartbeatInterval}`) this.debug(
`Received HELLO. Heartbeat Interval: ${this.heartbeatInterval}`
)
this.heartbeatIntervalID = setInterval(() => { this.heartbeatIntervalID = setInterval(() => {
if (this.heartbeatServerResponded) { if (this.heartbeatServerResponded) {
this.heartbeatServerResponded = false this.heartbeatServerResponded = false
} else { } else {
clearInterval(this.heartbeatIntervalID) clearInterval(this.heartbeatIntervalID)
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.reconnect() this.reconnect()
return return
} }
@ -91,7 +98,8 @@ class Gateway {
this.sendIdentify(this.client.forceNewSession) this.sendIdentify(this.client.forceNewSession)
this.initialized = true this.initialized = true
} else { } else {
console.log("Calling Resume") console.log('Calling Resume')
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.sendResume() this.sendResume()
} }
break break
@ -99,13 +107,16 @@ class Gateway {
case GatewayOpcodes.HEARTBEAT_ACK: case GatewayOpcodes.HEARTBEAT_ACK:
this.heartbeatServerResponded = true this.heartbeatServerResponded = true
this.client.ping = Date.now() - this.lastPingTimestamp 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 break
case GatewayOpcodes.INVALID_SESSION: case GatewayOpcodes.INVALID_SESSION:
// Because we know this gonna be bool // Because we know this gonna be bool
this.debug(`Invalid Session! Identifying with forced new session`) this.debug(`Invalid Session! Identifying with forced new session`)
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
// eslint-disable-next-line @typescript-eslint/promise-function-async
setTimeout(() => this.sendIdentify(true), 3000) setTimeout(() => this.sendIdentify(true), 3000)
break break
@ -113,7 +124,7 @@ class Gateway {
this.heartbeatServerResponded = true this.heartbeatServerResponded = true
if (s !== null) { if (s !== null) {
this.sequenceID = s this.sequenceID = s
await this.cache.set("seq", s) await this.cache.set('seq', s)
} }
if (t !== null && t !== undefined) { if (t !== null && t !== undefined) {
const handler = gatewayHandlers[t] const handler = gatewayHandlers[t]
@ -128,11 +139,12 @@ class Gateway {
// this.token = d.token // this.token = d.token
this.sessionID = d.session_id this.sessionID = d.session_id
this.sequenceID = d.seq this.sequenceID = d.seq
await this.cache.set("seq", d.seq) await this.cache.set('seq', d.seq)
await this.cache.set("session_id", this.sessionID) await this.cache.set('session_id', this.sessionID)
break break
} }
case GatewayOpcodes.RECONNECT: { case GatewayOpcodes.RECONNECT: {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.reconnect() this.reconnect()
break break
} }
@ -141,41 +153,46 @@ class Gateway {
} }
} }
private onclose(event: CloseEvent): void { 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) { if (event.code === GatewayCloseCodes.UNKNOWN_ERROR) {
this.debug("API has encountered Unknown Error. Reconnecting...") this.debug('API has encountered Unknown Error. Reconnecting...')
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.reconnect() 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!") 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!") throw new Error("Invalid Payload was sent. This shouldn't happen!")
} else if (event.code == GatewayCloseCodes.NOT_AUTHENTICATED) { } else if (event.code === GatewayCloseCodes.NOT_AUTHENTICATED) {
throw new Error("Not Authorized: Payload was sent before Identifying.") throw new Error('Not Authorized: Payload was sent before Identifying.')
} else if (event.code == GatewayCloseCodes.AUTHENTICATION_FAILED) { } else if (event.code === GatewayCloseCodes.AUTHENTICATION_FAILED) {
throw new Error("Invalid Token provided!") throw new Error('Invalid Token provided!')
} else if (event.code == GatewayCloseCodes.INVALID_SEQ) { } else if (event.code === GatewayCloseCodes.INVALID_SEQ) {
this.debug("Invalid Seq was sent. Reconnecting.") this.debug('Invalid Seq was sent. Reconnecting.')
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.reconnect() this.reconnect()
} else if (event.code == GatewayCloseCodes.RATE_LIMITED) { } else if (event.code === GatewayCloseCodes.RATE_LIMITED) {
throw new Error("You're ratelimited. Calm down.") throw new Error("You're ratelimited. Calm down.")
} else if (event.code == GatewayCloseCodes.SESSION_TIMED_OUT) { } else if (event.code === GatewayCloseCodes.SESSION_TIMED_OUT) {
this.debug("Session Timeout. Reconnecting.") this.debug('Session Timeout. Reconnecting.')
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.reconnect(true) this.reconnect(true)
} else if (event.code == GatewayCloseCodes.INVALID_SHARD) { } else if (event.code === GatewayCloseCodes.INVALID_SHARD) {
this.debug("Invalid Shard was sent. Reconnecting.") this.debug('Invalid Shard was sent. Reconnecting.')
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.reconnect() 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!") 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!") throw new Error("Invalid API Version was used. This shouldn't happen!")
} else if (event.code == GatewayCloseCodes.INVALID_INTENTS) { } else if (event.code === GatewayCloseCodes.INVALID_INTENTS) {
throw new Error("Invalid Intents") throw new Error('Invalid Intents')
} else if (event.code == GatewayCloseCodes.DISALLOWED_INTENTS) { } else if (event.code === GatewayCloseCodes.DISALLOWED_INTENTS) {
throw new Error("Given Intents aren't allowed") throw new Error("Given Intents aren't allowed")
} else { } 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() this.reconnect()
} }
} }
@ -185,20 +202,25 @@ class Gateway {
console.log(eventError) console.log(eventError)
} }
private async sendIdentify(forceNewSession?: boolean) { private async sendIdentify (forceNewSession?: boolean): Promise<void> {
this.debug("Fetching /gateway/bot...") this.debug('Fetching /gateway/bot...')
const info = await this.client.rest.get(GATEWAY_BOT()) as GatewayBotPayload 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") if (info.session_start_limit.remaining === 0)
this.debug("Recommended Shards: " + info.shards) throw new Error(
this.debug("=== Session Limit Info ===") `Session Limit Reached. Retry After ${info.session_start_limit.reset_after}ms`
this.debug(`Remaining: ${info.session_start_limit.remaining}/${info.session_start_limit.total}`) )
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`) this.debug(`Reset After: ${info.session_start_limit.reset_after}ms`)
if (!forceNewSession) { if (forceNewSession === undefined || !forceNewSession) {
let sessionIDCached = await this.cache.get("session_id") const sessionIDCached = await this.cache.get('session_id')
if (sessionIDCached) { if (sessionIDCached !== undefined) {
this.debug("Found Cached SessionID: " + sessionIDCached) this.debug(`Found Cached SessionID: ${sessionIDCached}`)
this.sessionID = sessionIDCached this.sessionID = sessionIDCached
return this.sendResume() return await this.sendResume()
} }
} }
this.send({ this.send({
@ -224,27 +246,29 @@ class Gateway {
private async sendResume(): Promise<void> { private async sendResume(): Promise<void> {
this.debug(`Preparing to resume with Session: ${this.sessionID}`) this.debug(`Preparing to resume with Session: ${this.sessionID}`)
if (this.sequenceID === undefined) { if (this.sequenceID === undefined) {
let cached = await this.cache.get("seq") const cached = await this.cache.get('seq')
if (cached) this.sequenceID = typeof cached == "string" ? parseInt(cached) : cached if (cached !== undefined)
this.sequenceID = typeof cached === 'string' ? parseInt(cached) : cached
} }
const resumePayload = { const resumePayload = {
op: GatewayOpcodes.RESUME, op: GatewayOpcodes.RESUME,
d: { d: {
token: this.token, token: this.token,
session_id: this.sessionID, session_id: this.sessionID,
seq: this.sequenceID || null seq: this.sequenceID ?? null
} }
} }
this.send(resumePayload) this.send(resumePayload)
} }
debug(msg: string) { debug (msg: string): void {
this.client.debug("Gateway", msg) this.client.debug('Gateway', msg)
} }
async reconnect(forceNew?: boolean) { async reconnect (forceNew?: boolean): Promise<void> {
clearInterval(this.heartbeatIntervalID) clearInterval(this.heartbeatIntervalID)
if (forceNew) await this.cache.delete("session_id") if (forceNew === undefined || !forceNew)
await this.cache.delete('session_id')
this.close() this.close()
this.initWebsocket() this.initWebsocket()
} }

View file

@ -4,29 +4,29 @@ import { Collection } from "../utils/collection.ts";
export class BaseManager<T, T2> { export class BaseManager<T, T2> {
client: Client client: Client
cacheName: string cacheName: string
dataType: any DataType: any
constructor(client: Client, cacheName: string, dataType: any) { constructor (client: Client, cacheName: string, DataType: any) {
this.client = client this.client = client
this.cacheName = cacheName this.cacheName = cacheName
this.dataType = dataType this.DataType = DataType
} }
_get(key: string): Promise<T> { async _get (key: string): Promise<T | undefined> {
return this.client.cache.get(this.cacheName, key) as Promise<T> return this.client.cache.get(this.cacheName, key)
} }
async get(key: string): Promise<T2 | void> { async get (key: string): Promise<T2 | undefined> {
const raw = await this._get(key) const raw = await this._get(key)
if(!raw) return if (raw === undefined) return
return new this.dataType(this.client, raw) as any return new this.DataType(this.client, raw)
} }
set(key: string, value: T) { async set (key: string, value: T): Promise<any> {
return this.client.cache.set(this.cacheName, key, value) return this.client.cache.set(this.cacheName, key, value)
} }
delete(key: string) { async delete (key: string): Promise<boolean> {
return this.client.cache.delete(this.cacheName, key) return this.client.cache.delete(this.cacheName, key)
} }
@ -49,4 +49,4 @@ export class BaseManager<T, T2> {
flush() { flush() {
return this.client.cache.deleteCache(this.cacheName) return this.client.cache.deleteCache(this.cacheName)
} }
} }

View file

@ -48,4 +48,4 @@ export class ChannelsManager extends BaseManager<ChannelPayload, Channel> {
}).catch(e => rej(e)) }).catch(e => rej(e))
}) })
} }
} }

View file

@ -1,20 +1,23 @@
import { Client } from "../models/client.ts"; import { Client } from '../models/client.ts'
import { Emoji } from "../structures/emoji.ts"; import { Emoji } from '../structures/emoji.ts'
import { EmojiPayload } from "../types/emoji.ts"; import { EmojiPayload } from '../types/emoji.ts'
import { CHANNEL } from "../types/endpoint.ts"; import { CHANNEL } from '../types/endpoint.ts'
import { BaseManager } from "./BaseManager.ts"; import { BaseManager } from './BaseManager.ts'
export class EmojisManager extends BaseManager<EmojiPayload, Emoji> { export class EmojisManager extends BaseManager<EmojiPayload, Emoji> {
constructor(client: Client) { constructor (client: Client) {
super(client, "emojis", Emoji) super(client, 'emojis', Emoji)
} }
fetch(id: string) { async fetch (id: string): Promise<Emoji> {
return new Promise((res, rej) => { return await new Promise((resolve, reject) => {
this.client.rest.get(CHANNEL(id)).then(data => { this.client.rest
this.set(id, data as EmojiPayload) .get(CHANNEL(id))
res(new Emoji(this.client, data as EmojiPayload)) .then(data => {
}).catch(e => rej(e)) this.set(id, data as EmojiPayload)
resolve(new Emoji(this.client, data as EmojiPayload))
})
.catch(e => reject(e))
}) })
} }
} }

View file

@ -1,23 +1,27 @@
import { Client } from "../models/client.ts"; import { Client } from '../models/client.ts'
export class GatewayCache { export class GatewayCache {
client: Client client: Client
cacheName: string = "discord_gateway_cache" cacheName: string = 'discord_gateway_cache'
constructor(client: Client, cacheName?: string) {
this.client = client
if(cacheName) this.cacheName = cacheName
}
get(key: string) { constructor (client: Client, cacheName?: string) {
return this.client.cache.get(this.cacheName, key) this.client = client
} if (cacheName !== undefined) this.cacheName = cacheName
}
set(key: string, value: any) { async get (key: string): Promise<undefined | any> {
return this.client.cache.set(this.cacheName, key, value) const result = await this.client.cache.get(this.cacheName, key)
} return result
}
delete(key: string) { async set (key: string, value: any): Promise<any> {
return this.client.cache.delete(this.cacheName, key) const result = await this.client.cache.set(this.cacheName, key, value)
} return result
} }
async delete (key: string): Promise<boolean> {
console.log(`[GatewayCache] DEL ${key}`)
const result = await this.client.cache.delete(this.cacheName, key)
return result
}
}

View file

@ -7,8 +7,8 @@ import { BaseManager } from "./BaseManager.ts";
import { MembersManager } from "./MembersManager.ts"; import { MembersManager } from "./MembersManager.ts";
export class GuildManager extends BaseManager<GuildPayload, Guild> { export class GuildManager extends BaseManager<GuildPayload, Guild> {
constructor(client: Client) { constructor (client: Client) {
super(client, "guilds", Guild) super(client, 'guilds', Guild)
} }
fetch(id: string) { fetch(id: string) {
@ -25,4 +25,4 @@ export class GuildManager extends BaseManager<GuildPayload, Guild> {
}).catch(e => rej(e)) }).catch(e => rej(e))
}) })
} }
} }

View file

@ -9,8 +9,8 @@ import { UserPayload } from "../types/user.ts";
import { BaseManager } from "./BaseManager.ts"; import { BaseManager } from "./BaseManager.ts";
export class MessagesManager extends BaseManager<MessagePayload, Message> { export class MessagesManager extends BaseManager<MessagePayload, Message> {
constructor(client: Client) { constructor (client: Client) {
super(client, "messages", Message) super(client, 'messages', Message)
} }
async get(key: string): Promise<Message | void> { async get(key: string): Promise<Message | void> {
@ -38,4 +38,4 @@ export class MessagesManager extends BaseManager<MessagePayload, Message> {
}).catch(e => rej(e)) }).catch(e => rej(e))
}) })
} }
} }

View file

@ -1,24 +1,27 @@
import { Client } from "../models/client.ts"; import { Client } from '../models/client.ts'
import { Guild } from "../structures/guild.ts"; import { Guild } from '../structures/guild.ts'
import { Role } from "../structures/role.ts"; import { Role } from '../structures/role.ts'
import { GUILD_ROLE } from "../types/endpoint.ts"; import { GUILD_ROLE } from '../types/endpoint.ts'
import { RolePayload } from "../types/role.ts"; import { RolePayload } from '../types/role.ts'
import { BaseManager } from "./BaseManager.ts"; import { BaseManager } from './BaseManager.ts'
export class RolesManager extends BaseManager<RolePayload, Role> { export class RolesManager extends BaseManager<RolePayload, Role> {
guild: Guild guild: Guild
constructor(client: Client, guild: Guild) { constructor (client: Client, guild: Guild) {
super(client, "roles:" + guild.id, Role) super(client, `roles:${guild.id}`, Role)
this.guild = guild this.guild = guild
} }
fetch(id: string) { async fetch (id: string): Promise<Role> {
return new Promise((res, rej) => { return await new Promise((resolve, reject) => {
this.client.rest.get(GUILD_ROLE(this.guild.id, id)).then(data => { this.client.rest
this.set(id, data as RolePayload) .get(GUILD_ROLE(this.guild.id, id))
res(new Role(this.client, data as RolePayload)) .then(data => {
}).catch(e => rej(e)) 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<RolePayload, Role> {
} }
return true return true
} }
} }

View file

@ -1,20 +1,23 @@
import { Client } from "../models/client.ts"; import { Client } from '../models/client.ts'
import { User } from "../structures/user.ts"; import { User } from '../structures/user.ts'
import { USER } from "../types/endpoint.ts"; import { USER } from '../types/endpoint.ts'
import { UserPayload } from "../types/user.ts"; import { UserPayload } from '../types/user.ts'
import { BaseManager } from "./BaseManager.ts"; import { BaseManager } from './BaseManager.ts'
export class UserManager extends BaseManager<UserPayload, User> { export class UserManager extends BaseManager<UserPayload, User> {
constructor(client: Client) { constructor (client: Client) {
super(client, "users", User) super(client, 'users', User)
} }
fetch(id: string) { async fetch (id: string): Promise<User> {
return new Promise((res, rej) => { return await new Promise((resolve, reject) => {
this.client.rest.get(USER(id)).then(data => { this.client.rest
this.set(id, data as UserPayload) .get(USER(id))
res(new User(this.client, data as UserPayload)) .then(data => {
}).catch(e => rej(e)) this.set(id, data as UserPayload)
resolve(new User(this.client, data as UserPayload))
})
.catch(e => reject(e))
}) })
} }
} }

View file

@ -1,13 +1,17 @@
import { Collection } from "../utils/collection.ts"; import { Collection } from '../utils/collection.ts'
import { Client } from "./client.ts"; import { Client } from './client.ts'
import { connect, Redis, RedisConnectOptions } from "https://denopkg.com/keroxp/deno-redis/mod.ts"; import {
connect,
Redis,
RedisConnectOptions
} from 'https://denopkg.com/keroxp/deno-redis/mod.ts'
export interface ICacheAdapter { export interface ICacheAdapter {
client: Client client: Client
get: (cacheName: string, key: string) => Promise<any> | any get: (cacheName: string, key: string) => Promise<any> | any
set: (cacheName: string, key: string, value: any) => Promise<any> | any set: (cacheName: string, key: string, value: any) => Promise<any> | any
delete: (cacheName: string, key: string) => Promise<boolean> | boolean delete: (cacheName: string, key: string) => Promise<boolean> | boolean
array: (cacheName: string) => void | any[] | Promise<any[] | void> array: (cacheName: string) => undefined | any[] | Promise<any[] | undefined>
deleteCache: (cacheName: string) => any deleteCache: (cacheName: string) => any
} }
@ -17,34 +21,34 @@ export class DefaultCacheAdapter implements ICacheAdapter {
[name: string]: Collection<string, any> [name: string]: Collection<string, any>
} = {} } = {}
constructor(client: Client) { constructor (client: Client) {
this.client = client this.client = client
} }
async get(cacheName: string, key: string) { async get (cacheName: string, key: string): Promise<undefined | any> {
const cache = this.data[cacheName] const cache = this.data[cacheName]
if (!cache) return; if (cache === undefined) return
return cache.get(key) return cache.get(key)
} }
async set(cacheName: string, key: string, value: any) { async set (cacheName: string, key: string, value: any): Promise<any> {
let cache = this.data[cacheName] let cache = this.data[cacheName]
if (!cache) { if (cache === undefined) {
this.data[cacheName] = new Collection() this.data[cacheName] = new Collection()
cache = this.data[cacheName] 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<boolean> {
const cache = this.data[cacheName] const cache = this.data[cacheName]
if (!cache) return false if (cache === undefined) return false
return cache.delete(key) return cache.delete(key)
} }
async array(cacheName: string) { async array (cacheName: string): Promise<any[] | undefined> {
const cache = this.data[cacheName] const cache = this.data[cacheName]
if (!cache) return [] if (cache === undefined) return
return cache.array() return cache.array()
} }
@ -59,45 +63,60 @@ export class RedisCacheAdapter implements ICacheAdapter {
redis?: Redis redis?: Redis
ready: boolean = false ready: boolean = false
constructor(client: Client, options: RedisConnectOptions) { constructor (client: Client, options: RedisConnectOptions) {
this.client = client this.client = client
this._redis = connect(options) this._redis = connect(options)
this._redis.then(redis => { this._redis.then(
this.redis = redis redis => {
this.ready = true this.redis = redis
}) this.ready = true
},
() => {
// TODO: Make error for this
}
)
} }
async _checkReady() { async _checkReady (): Promise<void> {
if(!this.ready) return await this._redis; if (!this.ready) await this._redis
else return;
} }
async get(cacheName: string, key: string) { async get (cacheName: string, key: string): Promise<string | undefined> {
await this._checkReady() await this._checkReady()
let cache = await this.redis?.hget(cacheName, key) const cache = await this.redis?.hget(cacheName, key)
if(!cache) return if (cache === undefined) return
try { try {
return JSON.parse(cache as string) return JSON.parse(cache)
} catch(e) { return cache } } catch (e) {
return cache
}
} }
async set(cacheName: string, key: string, value: any) { async set (
cacheName: string,
key: string,
value: any
): Promise<number | undefined> {
await this._checkReady() 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<boolean> {
await this._checkReady() await this._checkReady()
let exists = await this.redis?.hexists(cacheName, key) const exists = await this.redis?.hexists(cacheName, key)
if(!exists) return false if (exists === 0) return false
await this.redis?.hdel(cacheName, key) await this.redis?.hdel(cacheName, key)
return true return true
} }
async array(cacheName: string) { async array (cacheName: string): Promise<any[] | undefined> {
await this._checkReady() 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)) return data?.map((e: string) => JSON.parse(e))
} }
@ -105,4 +124,4 @@ export class RedisCacheAdapter implements ICacheAdapter {
await this._checkReady() await this._checkReady()
return await this.redis?.del(cacheName) return await this.redis?.del(cacheName)
} }
} }

View file

@ -32,7 +32,6 @@ export class Client extends EventEmitter {
cache: ICacheAdapter = new DefaultCacheAdapter(this) cache: ICacheAdapter = new DefaultCacheAdapter(this)
intents?: GatewayIntents[] intents?: GatewayIntents[]
forceNewSession?: boolean forceNewSession?: boolean
users: UserManager = new UserManager(this) users: UserManager = new UserManager(this)
guilds: GuildManager = new GuildManager(this) guilds: GuildManager = new GuildManager(this)
channels: ChannelsManager = new ChannelsManager(this) channels: ChannelsManager = new ChannelsManager(this)
@ -46,11 +45,11 @@ export class Client extends EventEmitter {
this.token = options.token this.token = options.token
this.intents = options.intents this.intents = options.intents
this.forceNewSession = options.forceNewSession this.forceNewSession = options.forceNewSession
if(options.cache) this.cache = options.cache if (options.cache !== undefined) this.cache = options.cache
if(options.presence) this.presence = options.presence instanceof ClientPresence ? options.presence : new ClientPresence(options.presence) 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 this.cache = adapter
return this return this
} }
@ -72,16 +71,15 @@ export class Client extends EventEmitter {
* @param intents Gateway intents in array. This is required. * @param intents Gateway intents in array. This is required.
*/ */
connect (token?: string, intents?: GatewayIntents[]): void { connect (token?: string, intents?: GatewayIntents[]): void {
if(!token && this.token) token = this.token if (token === undefined && this.token !== undefined) token = this.token
else if(!this.token && token) { else if (this.token === undefined && token !== undefined) {
this.token = token this.token = token
} } else throw new Error('No Token Provided')
else throw new Error("No Token Provided") if (intents === undefined && this.intents !== undefined)
if(!intents && this.intents) intents = this.intents intents = this.intents
else if(intents && !this.intents) { else if (intents !== undefined && this.intents === undefined) {
this.intents = intents 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) this.gateway = new Gateway(this, token, intents)
} }
} }

View file

@ -1,6 +1,6 @@
import { delay } from "../utils/index.ts"; import { delay } from '../utils/index.ts'
import * as baseEndpoints from "../consts/urlsAndVersions.ts"; import * as baseEndpoints from '../consts/urlsAndVersions.ts'
import { Client } from "./client.ts"; import { Client } from './client.ts'
export enum HttpResponseCode { export enum HttpResponseCode {
Ok = 200, Ok = 200,
@ -17,350 +17,376 @@ export enum HttpResponseCode {
} }
export type RequestMethods = export type RequestMethods =
| "get" | 'get'
| "post" | 'post'
| "put" | 'put'
| "patch" | 'patch'
| "head" | 'head'
| "delete"; | 'delete'
export interface QueuedRequest { export interface QueuedRequest {
callback: () => Promise< callback: () => Promise<
void | { | {
rateLimited: any; rateLimited: any
beforeFetch: boolean; beforeFetch: boolean
bucketID?: string | null; bucketID?: string | null
} }
>; | undefined
bucketID?: string | null; >
url: string; bucketID?: string | null
url: string
} }
export interface RateLimitedPath { export interface RateLimitedPath {
url: string; url: string
resetTimestamp: number; resetTimestamp: number
bucketID: string | null; bucketID: string | null
} }
export class RESTManager { export class RESTManager {
client: Client; client: Client
globallyRateLimited: boolean = false; globallyRateLimited: boolean = false
queueInProcess: boolean = false; queueInProcess: boolean = false
pathQueues: { [key: string]: QueuedRequest[] } = {}; pathQueues: { [key: string]: QueuedRequest[] } = {}
ratelimitedPaths = new Map<string, RateLimitedPath>(); ratelimitedPaths = new Map<string, RateLimitedPath>()
constructor(client: Client) { constructor (client: Client) {
this.client = client; this.client = client
} setTimeout(this.processRateLimitedPaths, 1000)
}
async processRateLimitedPaths() { async processRateLimitedPaths (): Promise<void> {
const now = Date.now(); const now = Date.now()
this.ratelimitedPaths.forEach((value, key) => { this.ratelimitedPaths.forEach((value, key) => {
if (value.resetTimestamp > now) return; if (value.resetTimestamp > now) return
this.ratelimitedPaths.delete(key); this.ratelimitedPaths.delete(key)
if (key === "global") this.globallyRateLimited = false; if (key === 'global') this.globallyRateLimited = false
}); })
}
await delay(1000); addToQueue (request: QueuedRequest): void {
this.processRateLimitedPaths(); 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) { if (this.pathQueues[id] !== undefined) {
const route = request.url.substring(baseEndpoints.DISCORD_API_URL.length + 1); this.pathQueues[id].push(request)
const parts = route.split("/"); } else {
// Remove the major param this.pathQueues[id] = [request]
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() { async cleanupQueues (): Promise<void> {
if ( Object.entries(this.pathQueues).forEach(([key, value]) => {
(Object.keys(this.pathQueues).length) && !this.globallyRateLimited if (value.length === 0) {
) { // Remove it entirely
await Promise.allSettled( // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
Object.values(this.pathQueues).map(async (pathQueue) => { delete this.pathQueues[key]
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) { async processQueue (): Promise<void> {
const headers: { [key: string]: string } = { if (
Authorization: `Bot ${this.client.token}`, Object.keys(this.pathQueues).length !== 0 &&
"User-Agent": !this.globallyRateLimited
`DiscordBot (discord.deno)`, ) {
}; 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; const rateLimitedURLResetIn = await this.checkRatelimits(request.url)
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) { if (typeof request.bucketID === 'string') {
const ratelimited = this.ratelimitedPaths.get(url); const rateLimitResetIn = await this.checkRatelimits(
const global = this.ratelimitedPaths.get("global"); request.bucketID
const now = Date.now(); )
if (rateLimitResetIn !== false) {
if (ratelimited && now < ratelimited.resetTimestamp) { // This request is still rate limited read to queue
return ratelimited.resetTimestamp - now; this.addToQueue(request)
} } else {
if (global && now < global.resetTimestamp) { // This request is not rate limited so it should be run
return global.resetTimestamp - now; const result = await request.callback()
} if (result?.rateLimited !== undefined) {
this.addToQueue({
return false; ...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( if (Object.keys(this.pathQueues).length !== 0) {
method: RequestMethods, await delay(1000)
url: string, // eslint-disable-next-line @typescript-eslint/no-floating-promises
body?: unknown, this.processQueue()
retryCount = 0, // eslint-disable-next-line @typescript-eslint/no-floating-promises
bucketID?: string | null, this.cleanupQueues()
) { } else this.queueInProcess = false
const errorStack = new Error("Location In Your Files:"); }
Error.captureStackTrace(errorStack);
createRequestBody (
return await new Promise((resolve, reject) => { body: any,
const callback = async () => { method: RequestMethods
try { ): { [key: string]: any } {
const rateLimitResetIn = await this.checkRatelimits(url); const headers: { [key: string]: string } = {
if (rateLimitResetIn) { Authorization: `Bot ${this.client.token}`,
return { rateLimited: rateLimitResetIn, beforeFetch: true, bucketID }; 'User-Agent': `DiscordBot (discord.deno)`
} }
const query = method === "get" && body if (this.client.token !== undefined) delete headers.Authorization
? Object.entries(body as any).map(([key, value]) =>
`${encodeURIComponent(key)}=${encodeURIComponent(value as any)}` if (method === 'get') body = undefined
)
.join("&") if (body?.reason !== undefined) {
: ""; headers['X-Audit-Log-Reason'] = encodeURIComponent(body.reason)
const urlToUse = method === "get" && query ? `${url}?${query}` : url; }
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<number | false> {
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<any> {
const errorStack = new Error('Location In Your Files:')
Error.captureStackTrace(errorStack)
return await new Promise((resolve, reject) => {
const callback = async (): Promise<undefined | any> => {
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. // 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 (
const json = await response.json(); json.retry_after !== undefined ||
if ( json.message === 'You are being rate limited.'
json.retry_after || ) {
json.message === "You are being rate limited." if (retryCount > 10) {
) { throw new Error('Max RateLimit Retries hit')
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) { return {
try { rateLimited: json.retry_after,
const error = await response.json(); beforeFetch: false,
console.error(error); bucketID: bucketIDFromHeaders
} catch { }
console.error(response); }
} 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<void> {
try {
const error = await response.json()
console.error(error)
} catch {
console.error(response)
}
}
handleStatusCode (
response: Response,
errorStack?: unknown
): undefined | boolean {
const status = response.status 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) { if (
let ratelimited = false; (status >= 200 && status < 400) ||
status === HttpResponseCode.TooManyRequests
// Get all useful headers ) {
const remaining = headers.get("x-ratelimit-remaining"); return true
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;
}
get(url: string, body?: unknown) { // eslint-disable-next-line @typescript-eslint/no-floating-promises
return this.runMethod("get", url, body); 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) { processHeaders (url: string, headers: Headers): string | null | undefined {
return this.runMethod("post", url, body); 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) { async get (url: string, body?: unknown): Promise<any> {
return this.runMethod("delete", url, body); return await this.runMethod('get', url, body)
} }
patch(url: string, body?: unknown) { async post (url: string, body?: unknown): Promise<any> {
return this.runMethod("patch", url, body); return await this.runMethod('post', url, body)
} }
put(url: string, body?: unknown) { async delete (url: string, body?: unknown): Promise<any> {
return this.runMethod("put", url, body); return await this.runMethod('delete', url, body)
} }
}
async patch (url: string, body?: unknown): Promise<any> {
return await this.runMethod('patch', url, body)
}
async put (url: string, body?: unknown): Promise<any> {
return await this.runMethod('put', url, body)
}
}

View file

@ -1,4 +1,3 @@
import cache from '../models/cache.ts'
import { Client } from '../models/client.ts' import { Client } from '../models/client.ts'
import { ChannelPayload, ChannelTypes } from '../types/channel.ts' import { ChannelPayload, ChannelTypes } from '../types/channel.ts'
import { Base } from './base.ts' import { Base } from './base.ts'

View file

@ -1,4 +1,3 @@
import cache from '../models/cache.ts'
import { Client } from '../models/client.ts' import { Client } from '../models/client.ts'
import { DMChannelPayload } from '../types/channel.ts' import { DMChannelPayload } from '../types/channel.ts'
import { UserPayload } from '../types/user.ts' import { UserPayload } from '../types/user.ts'

View file

@ -1,4 +1,3 @@
import cache from '../models/cache.ts'
import { Client } from '../models/client.ts' import { Client } from '../models/client.ts'
import { GroupDMChannelPayload } from '../types/channel.ts' import { GroupDMChannelPayload } from '../types/channel.ts'
import { Channel } from './channel.ts' import { Channel } from './channel.ts'

View file

@ -1,6 +1,6 @@
import { Client } from '../models/client.ts' import { Client } from '../models/client.ts'
import { GuildFeatures, GuildPayload } from '../types/guild.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 { Base } from './base.ts'
import { Channel } from './channel.ts' import { Channel } from './channel.ts'
import { Emoji } from './emoji.ts' import { Emoji } from './emoji.ts'

View file

@ -1,4 +1,3 @@
import cache from '../models/cache.ts'
import { Client } from '../models/client.ts' import { Client } from '../models/client.ts'
import { GuildVoiceChannelPayload, Overwrite } from '../types/channel.ts' import { GuildVoiceChannelPayload, Overwrite } from '../types/channel.ts'
import { Channel } from './channel.ts' import { Channel } from './channel.ts'

View file

@ -14,7 +14,6 @@ import { User } from './user.ts'
import { Member } from './member.ts' import { Member } from './member.ts'
import { Embed } from './embed.ts' import { Embed } from './embed.ts'
import { CHANNEL_MESSAGE } from '../types/endpoint.ts' import { CHANNEL_MESSAGE } from '../types/endpoint.ts'
import cache from '../models/cache.ts'
import { Channel } from "./channel.ts" import { Channel } from "./channel.ts"
import { MessageMentions } from "./MessageMentions.ts" import { MessageMentions } from "./MessageMentions.ts"
import { TextChannel } from "./textChannel.ts" import { TextChannel } from "./textChannel.ts"
@ -51,7 +50,13 @@ export class Message extends Base {
messageReference?: MessageReference messageReference?: MessageReference
flags?: number 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) super(client)
this.data = data this.data = data
this.id = data.id this.id = data.id
@ -120,7 +125,7 @@ export class Message extends Base {
} }
edit (text?: string, option?: MessageOption): Promise<Message> { edit (text?: string, option?: MessageOption): Promise<Message> {
return (this.channel as TextChannel).edit(this.id, text, option) return this.channel.edit(this.id, text, option)
} }
reply(text: string, options?: MessageOption) { reply(text: string, options?: MessageOption) {
@ -129,7 +134,7 @@ export class Message extends Base {
return this.channel.send(`${this.author.mention}, ${text}`, options) return this.channel.send(`${this.author.mention}, ${text}`, options)
} }
delete (): Promise<void> { async delete (): Promise<void> {
return this.client.rest.delete(CHANNEL_MESSAGE(this.channelID, this.id)) as any return this.client.rest.delete(CHANNEL_MESSAGE(this.channelID, this.id))
} }
} }

View file

@ -1,4 +1,3 @@
import cache from '../models/cache.ts'
import { Client } from '../models/client.ts' import { Client } from '../models/client.ts'
import { Base } from './base.ts' import { Base } from './base.ts'
import { RolePayload } from '../types/role.ts' import { RolePayload } from '../types/role.ts'

View file

@ -1,11 +1,9 @@
import cache from '../models/cache.ts'
import { Client } from '../models/client.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_MESSAGE, CHANNEL_MESSAGES } from '../types/endpoint.ts'
import { Channel } from './channel.ts' import { Channel } from './channel.ts'
import { Message } from './message.ts' import { Message } from './message.ts'
import { MessageMentions } from "./MessageMentions.ts" import { MessageMentions } from './MessageMentions.ts'
import { User } from "./user.ts"
export class TextChannel extends Channel { export class TextChannel extends Channel {
lastMessageID?: string lastMessageID?: string
@ -37,7 +35,7 @@ export class TextChannel extends Channel {
allowed_mentions: option?.allowedMention 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 ( async edit (
@ -45,21 +43,31 @@ export class TextChannel extends Channel {
text?: string, text?: string,
option?: MessageOption option?: MessageOption
): Promise<Message> { ): Promise<Message> {
if (text !== undefined && option !== undefined) { if (text === undefined && option === undefined) {
throw new Error('Either text or option is necessary.') 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), { if (this.client.user === undefined) {
content: text, throw new Error('Client user has not initialized.')
embed: option?.embed.toJSON(), }
file: option?.file,
tts: option?.tts, const newMsg = await this.client.rest.patch(
allowed_mentions: option?.allowedMention CHANNEL_MESSAGE(
}) as MessagePayload 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 // 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)
} }
} }

View file

@ -1,4 +1,3 @@
import cache from '../models/cache.ts'
import { Client } from '../models/client.ts' import { Client } from '../models/client.ts'
import { UserPayload } from '../types/user.ts' import { UserPayload } from '../types/user.ts'
import { Base } from './base.ts' import { Base } from './base.ts'
@ -18,8 +17,8 @@ export class User extends Base {
premiumType?: 0 | 1 | 2 premiumType?: 0 | 1 | 2
publicFlags?: number publicFlags?: number
get tag(): string { get tag (): string {
return `${this.username}#${this.discriminator}`; return `${this.username}#${this.discriminator}`
} }
get nickMention (): string { get nickMention (): string {
@ -65,7 +64,7 @@ export class User extends Base {
this.publicFlags = data.public_flags ?? this.publicFlags this.publicFlags = data.public_flags ?? this.publicFlags
} }
toString() { toString (): string {
return this.mention; return this.mention
} }
} }

View file

@ -1,4 +1,3 @@
import cache from '../models/cache.ts'
import { Client } from '../models/client.ts' import { Client } from '../models/client.ts'
import { MemberPayload } from '../types/guild.ts' import { MemberPayload } from '../types/guild.ts'
import { VoiceStatePayload } from '../types/voice.ts' import { VoiceStatePayload } from '../types/voice.ts'

View file

@ -2,7 +2,7 @@
// https://discord.com/developers/docs/topics/gateway#commands-and-events-gateway-events // https://discord.com/developers/docs/topics/gateway#commands-and-events-gateway-events
import { EmojiPayload } from './emoji.ts' import { EmojiPayload } from './emoji.ts'
import { MemberPayload } from './guild.ts' import { MemberPayload } from './guild.ts'
import { ActivityPayload, PresenceUpdatePayload } from './presence.ts' import { ActivityPayload } from './presence.ts'
import { RolePayload } from './role.ts' import { RolePayload } from './role.ts'
import { UserPayload } from './user.ts' import { UserPayload } from './user.ts'
@ -100,7 +100,7 @@ enum GatewayEvents {
Webhooks_Update = 'WEBHOOKS_UPDATE' Webhooks_Update = 'WEBHOOKS_UPDATE'
} }
interface IdentityPayload { export interface IdentityPayload {
token: string token: string
properties: IdentityConnection properties: IdentityConnection
compress?: boolean compress?: boolean
@ -119,19 +119,19 @@ enum UpdateStatus {
offline = 'offline' offline = 'offline'
} }
interface IdentityConnection { export interface IdentityConnection {
$os: 'darwin' | 'windows' | 'linux' | 'custom os' $os: 'darwin' | 'windows' | 'linux' | 'custom os'
$browser: 'discord.deno' $browser: 'discord.deno'
$device: 'discord.deno' $device: 'discord.deno'
} }
interface Resume { export interface Resume {
token: string token: string
session_id: string session_id: string
seq: number seq: number
} }
interface GuildRequestMembers { export interface GuildRequestMembers {
guild_id: string | string[] guild_id: string | string[]
query?: string query?: string
limit: number limit: number
@ -140,25 +140,25 @@ interface GuildRequestMembers {
nonce?: string nonce?: string
} }
interface GatewayVoiceStateUpdate { export interface GatewayVoiceStateUpdate {
guild_id: string guild_id: string
channel_id: string channel_id: string
self_mute: boolean self_mute: boolean
self_deaf: boolean self_deaf: boolean
} }
interface GatewayStatusUpdate { export interface GatewayStatusUpdate {
since: number | undefined since: number | undefined
activities: ActivityPayload[] activities: ActivityPayload[]
status: string status: string
afk: boolean afk: boolean
} }
interface Hello { export interface Hello {
heartbeat_interval: number heartbeat_interval: number
} }
interface ReadyEvent { export interface ReadyEvent {
v: number v: number
user: UserPayload user: UserPayload
privateChannels: [] privateChannels: []
@ -167,40 +167,40 @@ interface ReadyEvent {
shard?: number[] shard?: number[]
} }
interface ChannelPinsUpdate { export interface ChannelPinsUpdatePayload {
guild_id?: string guild_id?: string
channel_id: string channel_id: string
last_pin_timestamp?: string last_pin_timestamp?: string
} }
interface GuildBanAdd { export interface GuildBanAddPayload {
guild_id: string guild_id: string
user: UserPayload user: UserPayload
} }
interface GuildBanRemove { export interface GuildBanRemovePayload {
guild_id: string guild_id: string
user: UserPayload user: UserPayload
} }
interface GuildEmojiUpdate { export interface GuildEmojiUpdatePayload {
guild_id: string guild_id: string
emojis: [] emojis: []
} }
interface GuildIntegrationsUpdate { export interface GuildIntegrationsUpdatePayload {
guild_id: string guild_id: string
} }
interface GuildMemberAddExtra { export interface GuildMemberAddPayload {
guild_id: string guild_id: string
} }
interface GuildMemberRemove { export interface GuildMemberRemovePayload {
guild_id: string guild_id: string
user: UserPayload user: UserPayload
} }
interface GuildMemberUpdate { export interface GuildMemberUpdatePayload {
guild_id: string guild_id: string
roles: string[] roles: string[]
user: UserPayload user: UserPayload
@ -209,7 +209,7 @@ interface GuildMemberUpdate {
premium_since?: string | undefined premium_since?: string | undefined
} }
interface GuildMemberChunk { export interface GuildMemberChunkPayload {
guild_id: string guild_id: string
members: MemberPayload[] members: MemberPayload[]
chunk_index: number chunk_index: number
@ -219,22 +219,22 @@ interface GuildMemberChunk {
nonce?: string nonce?: string
} }
interface GuildRoleCreate { export interface GuildRoleCreatePayload {
guild_id: string guild_id: string
role: RolePayload role: RolePayload
} }
interface GuildRoleUpdate { export interface GuildRoleUpdatePayload {
guild_id: string guild_id: string
role: RolePayload role: RolePayload
} }
interface GuildRoleDelete { export interface GuildRoleDeletePayload {
guild_id: string guild_id: string
role_id: string role_id: string
} }
interface InviteCreate { export interface InviteCreatePayload {
channel_id: string channel_id: string
code: string code: string
created_at: string created_at: string
@ -248,25 +248,25 @@ interface InviteCreate {
uses: number uses: number
} }
interface InviteDelete { export interface InviteDeletePayload {
channel_id: string channel_id: string
guild_id?: string guild_id?: string
code: string code: string
} }
interface MessageDelete { export interface MessageDeletePayload {
id: string id: string
channel_id: string channel_id: string
guild_id?: string guild_id?: string
} }
interface MessageDeleteBulk { export interface MessageDeleteBulkPayload {
ids: string[] ids: string[]
channel_id: string channel_id: string
guild_id: string guild_id: string
} }
interface MessageReactionAdd { export interface MessageReactionAddPayload {
user_id: string user_id: string
channel_id: string channel_id: string
message_id: string message_id: string
@ -274,7 +274,7 @@ interface MessageReactionAdd {
emoji: EmojiPayload emoji: EmojiPayload
} }
interface MessageReactionRemove { export interface MessageReactionRemovePayload {
user_id: string user_id: string
channel_id: string channel_id: string
message_id: string message_id: string
@ -282,21 +282,13 @@ interface MessageReactionRemove {
emoji: EmojiPayload emoji: EmojiPayload
} }
interface MessageReactionRemoveAll { export interface MessageReactionRemoveAllPayload {
channel_id: string channel_id: string
guild_id?: string guild_id?: string
message_id: string message_id: string
emoji: EmojiPayload
} }
interface MessageReactionRemove { export interface PresenceUpdatePayload {
channel_id: string
guild_id?: string
message_id: string
emoji: EmojiPayload
}
interface PresenceUpdate {
user: UserPayload user: UserPayload
guild_id: string guild_id: string
status: string status: string
@ -304,76 +296,7 @@ interface PresenceUpdate {
client_status: UpdateStatus[] client_status: UpdateStatus[]
} }
interface CilentStatus { export interface TypeStart {
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 channel_id: string
guild_id?: string guild_id?: string
user_id: string user_id: string
@ -381,16 +304,22 @@ interface TypeStart {
member?: MemberPayload member?: MemberPayload
} }
interface VoiceServerUpdate { export interface VoiceServerUpdatePayload {
token: string token: string
guild_id: string guild_id: string
endpoint: string endpoint: string
} }
interface WebhooksUpdate { export interface WebhooksUpdatePayload {
guild_id: string guild_id: string
channel_id: string channel_id: string
} }
// https://discord.com/developers/docs/topics/gateway#typing-start-typing-start-event-fields // https://discord.com/developers/docs/topics/gateway#typing-start-typing-start-event-fields
export { GatewayCloseCodes, GatewayOpcodes, GatewayIntents, GatewayEvents } export {
GatewayCloseCodes,
GatewayOpcodes,
GatewayIntents,
GatewayEvents,
UpdateStatus
}

View file

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

View file

@ -39,3 +39,5 @@ enum PermissionFlags {
MANAGE_WEBHOOKS = 0x20000000, MANAGE_WEBHOOKS = 0x20000000,
MANAGE_EMOJIS = 0x40000000 MANAGE_EMOJIS = 0x40000000
} }
export { PermissionFlags }

View file

@ -1,14 +1,4 @@
import { UserPayload } from './user.ts' export interface ClientStatus {
export interface PresenceUpdatePayload {
user: UserPayload
guild_id: string
status: string
activities: ActivityPayload
client_status: ClientStatus
}
interface ClientStatus {
desktop?: string desktop?: string
mobile?: string mobile?: string
web?: string web?: string
@ -31,30 +21,30 @@ export interface ActivityPayload {
flags?: number flags?: number
} }
interface ActivityTimestamps { export interface ActivityTimestamps {
start?: number start?: number
end?: number end?: number
} }
interface ActivityEmoji { export interface ActivityEmoji {
name: string name: string
id?: string id?: string
animated?: boolean animated?: boolean
} }
interface ActivityParty { export interface ActivityParty {
id?: string id?: string
size?: number[] size?: number[]
} }
interface ActivityAssets { export interface ActivityAssets {
large_image?: string large_image?: string
large_text?: string large_text?: string
small_image?: string small_image?: string
small_text?: string small_text?: string
} }
interface ActivitySecrets { export interface ActivitySecrets {
join?: string join?: string
spectate?: string spectate?: string
match?: string match?: string
@ -68,3 +58,5 @@ enum ActivityFlags {
SYNC = 1 << 4, SYNC = 1 << 4,
PLAY = 1 << 5 PLAY = 1 << 5
} }
export { ActivityFlags }