From 2f16b77cc9313836245d0e9017b693914b8d9cd8 Mon Sep 17 00:00:00 2001 From: Will Hoskings Date: Sun, 9 Feb 2020 23:00:14 +0000 Subject: [PATCH 01/13] New API draft --- managers/RequestManager.ts | 26 ++++--- mod.ts | 21 ++++- module/Client.ts | 155 ++++++++++++++++++++++++++----------- module/Ratelimiter.ts | 3 + module/gateway.ts | 56 ++++++++++++++ module/utilities.ts | 2 + module/websocket.ts | 4 +- types/discord.ts | 43 +++++++++- types/message-type.ts | 6 ++ types/options.ts | 13 ++++ types/queue.ts | 38 +++++++++ 11 files changed, 303 insertions(+), 64 deletions(-) create mode 100644 module/Ratelimiter.ts create mode 100644 module/gateway.ts create mode 100644 module/utilities.ts create mode 100644 types/message-type.ts create mode 100644 types/options.ts create mode 100644 types/queue.ts diff --git a/managers/RequestManager.ts b/managers/RequestManager.ts index b58f6b38c..6cf220df5 100644 --- a/managers/RequestManager.ts +++ b/managers/RequestManager.ts @@ -1,31 +1,33 @@ import Client from "../module/Client.ts"; class RequestManager { - client: Client - token: string + client: Client; + token: string; + currentRatelimit constructor(client: Client, token: string) { this.client = client this.token = token } - async get(url: string, payload?: unknown) { - // THIS IS IMPORTANT. It keeps clean stack errors in the users own files to better help debug errors. - // const stackHolder = {}; - // TODO: Figure out why this doesnt work - // Error.captureStackTrace(stackHolder) + async get(url: string, payload?: unknown, shouldRatelimit = true) { + if (shouldRatelimit) { - // let attempts = 0 - const headers = { - Authorization: this.token, - "User-Agent": `DiscordBot (https://github.com/skillz4killz/discordeno, 0.0.1)`, } - + const headers = this.getDiscordHeaders(); console.log('payload', payload) const data = await fetch(url, { headers }).then(res => res.json()) return data } + + // The Record type here plays nice with Deno's `fetch.headers` expected type. + getDiscordHeaders (): Record { + return { + Authorization: this.token, + "User-Agent": `DiscordBot (https://github.com/skillz4killz/discordeno, 0.0.1)`, + }; + } } export default RequestManager \ No newline at end of file diff --git a/mod.ts b/mod.ts index 5dfb40389..f09abe28a 100644 --- a/mod.ts +++ b/mod.ts @@ -1,7 +1,22 @@ import Client from "./module/Client.ts" import { configs } from "./configs.ts" +import { StatusType, GatewayOpcode } from "./types/discord.ts"; -const Discordeno = new Client(configs.token) -Discordeno.connect() +(async function () { + console.log({ configs }); + const client = new Client({ + token: configs.token + }); -export default Discordeno \ No newline at end of file + const { gateway, connection } = await client.bootstrap(); + + for await (const message of connection) { + if (message.data?.op === GatewayOpcode.Hello) { + await message.action; + await gateway.updateStatus({ + afk: false, + status: StatusType.DoNotDisturb + }) + } + } +})(); diff --git a/module/Client.ts b/module/Client.ts index 6a60512a7..507c4db98 100644 --- a/module/Client.ts +++ b/module/Client.ts @@ -1,78 +1,141 @@ -import { endpoints } from "../constants/discord.ts"; -import RequestManager from "../managers/RequestManager.ts"; -import { DiscordBotGateway, DiscordPayload, DiscordHeartbeatPayload } from "../types/discord.ts"; -import ShardingManager from "../managers/ShardingManager.ts"; +import { endpoints } from '../constants/discord.ts' +import RequestManager from '../managers/RequestManager.ts' +import { DiscordBotGatewayData, DiscordPayload, DiscordHeartbeatPayload, GatewayOpcode } from '../types/discord.ts' +import ShardingManager from '../managers/ShardingManager.ts' import { connectWebSocket, isWebSocketCloseEvent, isWebSocketPingEvent, isWebSocketPongEvent, WebSocket -} from "https://deno.land/std/ws/mod.ts"; +} from 'https://deno.land/std/ws/mod.ts' // import { encode } from "https://deno.land/std/strings/mod.ts" // import { BufReader } from "https://deno.land/std/io/bufio.ts" // import { TextProtoReader } from "https://deno.land/std/textproto/mod.ts" -import { blue, green, red, yellow } from "https://deno.land/std/fmt/colors.ts" -import { keepDiscordWebsocketAlive } from "./websocket.ts"; +import { blue, green, red, yellow } from 'https://deno.land/std/fmt/colors.ts' +import { keepDiscordWebsocketAlive } from './websocket.ts' +import Gateway from './gateway.ts' +import { ClientOptions, FulfilledClientOptions } from '../types/options.ts' +import { CollectedMessageType } from '../types/message-type.ts' class Client { /** The bot's token. This should never be used by end users. It is meant to be used internally to make requests to the Discord API. */ - token: string; + token: string /** The Rate limit manager to handle all outgoing requests to discord. Not meant to be used by users. */ - RequestManager: RequestManager; + RequestManager: RequestManager /** Creates and handles all the shards necessary for the bot. */ - ShardingManager: ShardingManager; + ShardingManager: ShardingManager - constructor(token: string) { - this.token = `Bot ${token}`; - this.RequestManager = new RequestManager(this, this.token); - this.ShardingManager = new ShardingManager(); + /** The options (with defaults) passed to the `Client` constructor. */ + options: FulfilledClientOptions + + protected authorization: string + + constructor(options: ClientOptions) { + // Assign some defaults to the options to make them fulfilled / not annoying to use. + this.options = Object.assign( + { + properties: { + $os: '...', + $browser: '...', + $device: '...' + }, + compress: false + }, + options + ) + this.token = options.token + this.authorization = `Bot ${this.options.token}` + this.RequestManager = new RequestManager(this, this.authorization) + this.ShardingManager = new ShardingManager() + } + + getGatewayData() { + return this.RequestManager.get(endpoints.GATEWAY_BOT) as Promise + } + + createWebsocketConnection(data: DiscordBotGatewayData) { + console.log({ data }) + return connectWebSocket(data.url) + } + + async bootstrap() { + const data = await this.getGatewayData() + const socket = await this.createWebsocketConnection(data); + const gateway = new Gateway(socket); + const messages = this.collectMessages(gateway); + await gateway.identify(this.options); + return { + data, + socket, + gateway, + messages, + connection: this.connect(gateway, data) + } + } + + async *collectMessages(gateway: Gateway) { + const { socket } = gateway + for await (const message of socket.receive()) { + if (typeof message === 'string') { + yield { + type: CollectedMessageType.Message, + data: JSON.parse(message) + } + } else if (isWebSocketCloseEvent(message)) { + yield { type: CollectedMessageType.Close, ...message } + return + } else if (isWebSocketPingEvent(message)) { + yield { type: CollectedMessageType.Ping } + } else if (isWebSocketPongEvent(message)) { + yield { type: CollectedMessageType.Pong } + } + } } /** Begins initial handshake, creates the websocket with Discord and spawns all necessary shards. */ - async connect() { - const data = (await this.RequestManager.get( - endpoints.GATEWAY_BOT - )) as DiscordBotGateway; - // Open a WS with the url from discord. - const sock = await connectWebSocket(data.url); - console.log(sock) - console.log(green("ws connected! (type 'close' to quit)")); - - for await (const msg of sock.receive()) { - if (typeof msg === "string") { - try { - const json = JSON.parse(msg) - this.handleDiscordPayload(json, sock) - } catch { - console.log(red(`Invalid JSON String send by discord: ${msg}`)) - } - console.log(yellow("< " + msg)); - } else if (isWebSocketPingEvent(msg)) { - console.log(blue("< ping")); - } else if (isWebSocketPongEvent(msg)) { - console.log(blue("< pong")); - } else if (isWebSocketCloseEvent(msg)) { - console.log(red(`closed: code=${msg.code}, reason=${msg.reason}`)); + async *connect(gateway: Gateway, data: DiscordBotGatewayData): AsyncGenerator<{ type: CollectedMessageType, data?: DiscordPayload, action?: Promise }> { + for await (const message of this.collectMessages(gateway)) { + switch (message.type) { + case CollectedMessageType.Ping: + console.log('Ping!') + yield message; + break + case CollectedMessageType.Pong: + console.log('Pong!') + yield message; + break + case CollectedMessageType.Close: + console.log('Close :(', message) + yield message; + break + case CollectedMessageType.Message: + await this.handleDiscordPayload(message.data, gateway); + yield message; + console.log({ yay: true, ...message }); + break } } // Begin spawning all necessary shards - this.spawnShards(data.shards); + this.spawnShards(data.shards) } - handleDiscordPayload(data: DiscordPayload, socket: WebSocket) { + handleDiscordPayload(data: DiscordPayload, gateway: Gateway) { switch (data.op) { - case 10: // Initial Heartbeat - keepDiscordWebsocketAlive(socket, (data.d as DiscordHeartbeatPayload).heartbeat_interval, data.s) - } + case GatewayOpcode.Hello: + console.log('heartbeating...'); + return gateway.sendConstantHeartbeats((data.d as DiscordHeartbeatPayload).heartbeat_interval, data.s); + } + + // Make all code paths return a promise for consistency. + return Promise.resolve(undefined); } spawnShards(total: number, id = 1) { // this.ShardingManager.spawnShard(id); - if (id < total) this.spawnShards(total, id + 1); + if (id < total) this.spawnShards(total, id + 1) } } -export default Client; - +export default Client diff --git a/module/Ratelimiter.ts b/module/Ratelimiter.ts new file mode 100644 index 000000000..c7eb410b3 --- /dev/null +++ b/module/Ratelimiter.ts @@ -0,0 +1,3 @@ +export class Ratelimiter { + +} \ No newline at end of file diff --git a/module/gateway.ts b/module/gateway.ts new file mode 100644 index 000000000..e7dd3ddae --- /dev/null +++ b/module/gateway.ts @@ -0,0 +1,56 @@ +import { + connectWebSocket, + isWebSocketCloseEvent, + isWebSocketPingEvent, + isWebSocketPongEvent, + WebSocket + } from "https://deno.land/std/ws/mod.ts"; +import { GatewayOpcode, Status } from "../types/discord.ts"; +import { FulfilledClientOptions } from "../types/options.ts"; +import { delay } from 'https://deno.land/std/util/async.ts'; + +export default class Gateway { + constructor (public socket: WebSocket) {} + + identify (options: FulfilledClientOptions) { + return this.sendObject({ + op: GatewayOpcode.Identify, + d: { + token: options.token, + // TOOD: Let's get compression working, eh? + compress: false, + properties: options.properties + } + }); + } + + sendHeartbeat (previousSequenceNumber: number | null = null) { + return this.sendObject({ + op: GatewayOpcode.Heartbeat, + d: previousSequenceNumber + }); + } + + updateStatus (status: Status) { + this.sendObject({ + op: GatewayOpcode.StatusUpdate, + d: status + }); + } + + async sendConstantHeartbeats (interval: number, previousSequenceNumber: number | null = null, shouldContinue: () => boolean = () => true): Promise { + await delay(interval); + + if (!shouldContinue()) { + return; + } + + // TODO: If the initial seq num is null, this will make it forever null until a restart. Is this good? + this.sendHeartbeat(previousSequenceNumber === null ? previousSequenceNumber : previousSequenceNumber++); + return this.sendConstantHeartbeats(interval, previousSequenceNumber); + } + + sendObject (object: object) { + return this.socket.send(JSON.stringify(object)); + } +} \ No newline at end of file diff --git a/module/utilities.ts b/module/utilities.ts new file mode 100644 index 000000000..a02f88986 --- /dev/null +++ b/module/utilities.ts @@ -0,0 +1,2 @@ +import { delay } from 'https://deno.land/std/util/async.ts'; + diff --git a/module/websocket.ts b/module/websocket.ts index ac7d51f38..502de1fbb 100644 --- a/module/websocket.ts +++ b/module/websocket.ts @@ -1,13 +1,15 @@ import { WebSocket } from "https://deno.land/std/ws/mod.ts"; +import { GatewayOpcode } from "../types/discord.ts"; export const keepDiscordWebsocketAlive = (socket: WebSocket, millesecondsInterval: number, payload: number | null = null) => { let previousSequenceNumber = payload + let doneInitial = false; setInterval(async () => { const response = await socket.send(JSON.stringify({ op: 1, d: previousSequenceNumber - })) + })); console.log(response) diff --git a/types/discord.ts b/types/discord.ts index a2a85a4ab..e941938c7 100644 --- a/types/discord.ts +++ b/types/discord.ts @@ -9,7 +9,7 @@ export interface DiscordPayload { t?: string } -export interface DiscordBotGateway { +export interface DiscordBotGatewayData { /** The WSS URL that can be used for connecting to the gateway. */ url: string /** The recommended number of shards to use when connecting. */ @@ -35,7 +35,7 @@ export enum GatewayOpcode { Identify, StatusUpdate, VoiceStateUpdate, - Resume, + Resume = 6, Reconnect, RequestGuildMembers, InvalidSession, @@ -154,3 +154,42 @@ export enum JSONErrorCode { ReactionBlocked = 90001, ResourceOverloaded = 130000 } + +export interface Properties { + $os: string; + $browser: string; + $device: string; +} + +export interface Timestamps { + start?: number; + end?: number; +} + +export interface Emoji { + name: string; + id?: string; + animated?: boolean; +} + +export interface Activity { + name: string; + type: number; + url?: string; + created_at: number; + timestamps: Timestamps; + details?: string; +} + +export enum StatusType { + Online = 'online', + DoNotDisturb = 'dnd', + Idle = 'idle', + Invisible = 'invisible', + Offline = 'offline' +} + +export interface Status { + afk: boolean; + status: StatusType; +} \ No newline at end of file diff --git a/types/message-type.ts b/types/message-type.ts new file mode 100644 index 000000000..664478ebb --- /dev/null +++ b/types/message-type.ts @@ -0,0 +1,6 @@ +export enum CollectedMessageType { + Ping, + Pong, + Close, + Message +} diff --git a/types/options.ts b/types/options.ts new file mode 100644 index 000000000..10b108c55 --- /dev/null +++ b/types/options.ts @@ -0,0 +1,13 @@ +import { Properties } from "./discord.ts"; + +export interface FulfilledClientOptions { + token: string; + properties: Properties; + compress: boolean; +} + +export interface ClientOptions { + token: string; + properties?: Properties; + compress?: boolean; +} diff --git a/types/queue.ts b/types/queue.ts new file mode 100644 index 000000000..d5dabbd47 --- /dev/null +++ b/types/queue.ts @@ -0,0 +1,38 @@ +import { DiscordPayload } from "./discord"; +import Gateway from "../module/gateway.ts"; + +export abstract class ActionQueue { + protected actions: Action[] = []; + + push (action: Action) { + if (this.shouldDispatchImmediately(action)) { + this.dispatch(action); + } else { + this.actions.push(action); + } + } + + dispatchAll () { + let index = 0; + for (const action of this.actions) { + this.actions.splice(index, 1); + this.dispatch(action); + index++; + } + } + + abstract dispatch (action: Action): void; + abstract shouldDispatchImmediately (action: Action): boolean; +} + +export class GatewayActionQueue extends ActionQueue { + constructor (protected gateway: Gateway) { + super(); + } + + dispatch (action: DiscordPayload) { + this.gateway.sendObject(action); + } + + shouldDispatchImmediately () +} \ No newline at end of file From e4811fc88b0877982ba132ee5b2e91c360064c20 Mon Sep 17 00:00:00 2001 From: Will Hoskings Date: Mon, 10 Feb 2020 21:13:15 +0000 Subject: [PATCH 02/13] Crank the target right up --- tsconfig.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 19db3c704..19f5941fd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,9 +2,9 @@ "compilerOptions": { /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ - "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ - "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - // "lib": [], /* Specify library files to be included in the compilation. */ + "target": "ESNext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ + "module": "ESNext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + "lib": ["ES7"], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ From 1fbd77833b2fa78eed611618a83010f85046adae Mon Sep 17 00:00:00 2001 From: Will Hoskings Date: Mon, 10 Feb 2020 21:15:44 +0000 Subject: [PATCH 03/13] Remove the token-printing "feature" --- mod.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/mod.ts b/mod.ts index f09abe28a..a34d17415 100644 --- a/mod.ts +++ b/mod.ts @@ -3,7 +3,6 @@ import { configs } from "./configs.ts" import { StatusType, GatewayOpcode } from "./types/discord.ts"; (async function () { - console.log({ configs }); const client = new Client({ token: configs.token }); From db44305b8c9bb0001669a5ef82c508792cd876d4 Mon Sep 17 00:00:00 2001 From: Will Hoskings Date: Mon, 10 Feb 2020 21:17:32 +0000 Subject: [PATCH 04/13] Export the guild interfaces --- structures/guild.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/structures/guild.ts b/structures/guild.ts index 2fbaae1a7..564e5a233 100644 --- a/structures/guild.ts +++ b/structures/guild.ts @@ -2,7 +2,7 @@ import Client from "../module/Client" import { endpoints } from "../constants/discord" import { formatImageURL } from "../utils/cdn" -interface CreateGuildPayload { +export interface CreateGuildPayload { /** The guild id */ id: string /** The guild name 2-100 characters */ @@ -63,7 +63,7 @@ interface CreateGuildPayload { preferred_locale: string } -interface Guild { +export interface Guild { /** The guild id */ id: string /** The guild name 2-100 characters */ From 0172bc1b9454b2824fcd3c25ea1850ec6f2580c6 Mon Sep 17 00:00:00 2001 From: Skillz4Killz <23035000+Skillz4Killz@users.noreply.github.com> Date: Mon, 10 Feb 2020 23:49:56 -0500 Subject: [PATCH 05/13] Create CONTRIBUTING.md --- CONTRIBUTING.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..58ac18338 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,43 @@ +# Fundamental Design Goals + +This document serves to outline the overall design goals of the project. Please see below a list of these fundamentals. + +## Do not allow anything Discord does not permit +Prevent any and all attempts of making user bots. If someone connects and the client.user is not a bot user then throw an error immediately. + +Do not support non-bot features like Group DMs or dm calls etc... + +## Prettier Philosophy Regarding Options + +Avoid options/customizable whenever possible. Always enforce default values. Except in cases like intents where the user should be able to pick which intents to listen for. + +## Security + +Permission checks should be done by the library! We can throw a custom error that shows which permissions are missing in order to run this request and save an API call. This will also prevent bots from getting banned due to Missing Access errors. + +Typescript 3.8 provides **TRUE** private props and methods that no one can access. We will use this to our advantage to truly make a proper API. This isn't a silly `_` to mark it as a private but the user should NEVER be able to access it no matter what. + +## Functional API + +Events emitted by the client, for example the message creation event, should not emit a `Message` class instance, but instead a *POJO* (Plain Ol' JavaScript Object). This will overall make a cleaner and more performant API, while removing the headaches of extending built-in classes, and inheritance. + +Use functions when possible instead of an event emitter to prevent emitter related memory leak issues or a number of other headaches that arise. + +TLDR: Avoid `classes` whenever possible. Avoid `loops` whenever possible(opt for iterations like .forEach, map reduce, some find etc...) + +## Documentation + +Use `/** Description here */` comments above all properties and methods to describe it so that VSC and other good IDE's with intellisense can pick it up and provide the documentation right inside the IDE preventing a developer from needing Discord API docs or even Deno documentation. + +We should have a step by step guide nonetheless but this is a POST v1 launch. +We should have a template repo to creating a boilerplate bot. + +## Backwards Compatibility BS + +Backwards compatibility is the death of code. It causes clutter and uglyness to pile up and makes developers lazier. There will be no such thing as backwards compatibility reasons in Discordeno. We will always support the latest and greatest of JS. The end! Users can fork the lib at any commit to keep older versions until they are ready to update. + +That said, we don't expect many things to be changing drastically after v1. As you can imagine Typescript allows the latest and greatest of JS so we will be ahead of the curve for years to come. + +## Style Guide + +Prettier is our style guide. No discussions around styling ever. The options are set and that is all. When you code let prettier handle the styling. PERIOD! From 0ee59ebbc24222e3ad941d498c4e43e66082c167 Mon Sep 17 00:00:00 2001 From: Will Hoskings Date: Tue, 11 Feb 2020 18:23:25 +0000 Subject: [PATCH 06/13] Add RequestMethod enum --- types/fetch.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 types/fetch.ts diff --git a/types/fetch.ts b/types/fetch.ts new file mode 100644 index 000000000..10e204a7d --- /dev/null +++ b/types/fetch.ts @@ -0,0 +1,8 @@ +export const enum RequestMethod { + Get = 'get', + Post = 'post', + Put = 'put', + Patch = 'patch', + Head = 'head', + Delete = 'delete' +} \ No newline at end of file From c60472916e1311eb259e7bdcfe96a64f71cdec26 Mon Sep 17 00:00:00 2001 From: Will Hoskings Date: Tue, 11 Feb 2020 18:23:39 +0000 Subject: [PATCH 07/13] Remove Activity from discord.ts --- types/discord.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/types/discord.ts b/types/discord.ts index e941938c7..5ac48464a 100644 --- a/types/discord.ts +++ b/types/discord.ts @@ -172,15 +172,6 @@ export interface Emoji { animated?: boolean; } -export interface Activity { - name: string; - type: number; - url?: string; - created_at: number; - timestamps: Timestamps; - details?: string; -} - export enum StatusType { Online = 'online', DoNotDisturb = 'dnd', From f785c42406edd0cfc1d4eda4d06c2c97541a60f4 Mon Sep 17 00:00:00 2001 From: Will Hoskings Date: Tue, 11 Feb 2020 18:23:50 +0000 Subject: [PATCH 08/13] Add DOM to TypeScript lib array --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 19f5941fd..54cdc8971 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ // "incremental": true, /* Enable incremental compilation */ "target": "ESNext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "module": "ESNext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - "lib": ["ES7"], /* Specify library files to be included in the compilation. */ + "lib": ["ES7", "DOM"], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ From 1d989094d523afec5f40bed5894bbf838d022395 Mon Sep 17 00:00:00 2001 From: Will Hoskings Date: Tue, 11 Feb 2020 18:23:59 +0000 Subject: [PATCH 09/13] Add the Emoji payload type --- structures/emoji.ts | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 structures/emoji.ts diff --git a/structures/emoji.ts b/structures/emoji.ts new file mode 100644 index 000000000..cf8ba3123 --- /dev/null +++ b/structures/emoji.ts @@ -0,0 +1,5 @@ +export interface EmojiPayload { + name: string; + id?: string; + animated?: boolean; +} \ No newline at end of file From 655df25d5714921b49c2c90de52e255383f6ef71 Mon Sep 17 00:00:00 2001 From: Will Hoskings Date: Tue, 11 Feb 2020 18:24:10 +0000 Subject: [PATCH 10/13] Add the Presence payload type --- structures/presence.ts | 44 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 structures/presence.ts diff --git a/structures/presence.ts b/structures/presence.ts new file mode 100644 index 000000000..132c8be03 --- /dev/null +++ b/structures/presence.ts @@ -0,0 +1,44 @@ +import { UserPayload } from "./user.ts"; +import { ActivityPayload } from "./activity"; +import { StatusType } from "../types/discord"; + +export type PresencePayload = Partial<{ + /** The user presence is being updated for */ + user: UserPayload; + + /** Roles this user is in */ + roles: string[]; + + /** Null, or the user's current activity */ + game: ActivityPayload; + + /** Id of the guild */ + guild_id: string; + + // This is a deviation from the docs, as it pretty much says `: StatusType`. + /** The updated status */ + status: StatusType; + + /** User's current activities */ + activities: ActivityPayload[]; + + /** User's platform-dependent status */ + client_status: ClientStatusPayload; + + /** When the user used their Nitro boost on the server */ + premium_since: string; + + /** This users guild nickname (if one is set) */ + nick: string; +}> & { id: string }; + +export interface ClientStatusPayload { + /** The user's status set for an active desktop (Windows, Linux, Mac) application session */ + desktop?: StatusType; + + /** The user's status set for an active mobile (iOS, Android) application session */ + mobile?: StatusType; + + /** The user's status set for an active web (browser, bot account) application session */ + web?: StatusType; +} \ No newline at end of file From 269142165e60ec23f2b8bd67718c193191533408 Mon Sep 17 00:00:00 2001 From: Will Hoskings Date: Tue, 11 Feb 2020 18:24:24 +0000 Subject: [PATCH 11/13] (Double check): Add Activity type --- structures/activity.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 structures/activity.ts diff --git a/structures/activity.ts b/structures/activity.ts new file mode 100644 index 000000000..e1a492fc2 --- /dev/null +++ b/structures/activity.ts @@ -0,0 +1,20 @@ +import { Timestamps } from "../types/discord.ts"; + +export interface ActivityPayload { + /** The activity's name */ + name: string; + + /** */ + type: number; + url?: string; + created_at: number; + timestamps: Timestamps; + details?: string; +} + +export enum ActivityType { + Game, + Streaming, + Listening, + Custom = 4 +} \ No newline at end of file From fa1dfc24bd6d94da2d5a5617a45182acd8237a25 Mon Sep 17 00:00:00 2001 From: Will Hoskings Date: Tue, 11 Feb 2020 18:24:36 +0000 Subject: [PATCH 12/13] Add HTTP methods to RequestManager --- managers/RequestManager.ts | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/managers/RequestManager.ts b/managers/RequestManager.ts index 6cf220df5..8785a60b2 100644 --- a/managers/RequestManager.ts +++ b/managers/RequestManager.ts @@ -1,24 +1,38 @@ import Client from "../module/Client.ts"; +import { RequestMethod } from "../types/fetch"; + +type RequestBody = string | Blob | ArrayBufferView | ArrayBuffer | FormData | URLSearchParams | null | undefined; class RequestManager { client: Client; token: string; - currentRatelimit constructor(client: Client, token: string) { this.client = client this.token = token } - async get(url: string, payload?: unknown, shouldRatelimit = true) { - if (shouldRatelimit) { - - } + async get(url: string) { const headers = this.getDiscordHeaders(); - console.log('payload', payload) - - const data = await fetch(url, { headers }).then(res => res.json()) - return data + return fetch(url, { headers }).then(res => res.json()) + } + + async post (url: string, body: RequestBody) { + const headers = this.getDiscordHeaders(); + return fetch(url, { + method: RequestMethod.Post, + headers, + body + }); + } + + async delete (url: string, body: RequestBody) { + const headers = this.getDiscordHeaders(); + return fetch(url, { + method: RequestMethod.Delete, + headers, + body + }); } // The Record type here plays nice with Deno's `fetch.headers` expected type. From 5ef19f90a638d694ad62fd31cab1657d0d595a5f Mon Sep 17 00:00:00 2001 From: Will Hoskings Date: Tue, 11 Feb 2020 18:26:58 +0000 Subject: [PATCH 13/13] Add user object --- structures/user.ts | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 structures/user.ts diff --git a/structures/user.ts b/structures/user.ts new file mode 100644 index 000000000..3231c3333 --- /dev/null +++ b/structures/user.ts @@ -0,0 +1,34 @@ +export interface UserPayload { + /** The user's id */ + id: string; + + /** The user's username, not unique across the platform */ + username: string; + + /** The user's 4-digit discord-tag */ + discriminator: string; + + /** The user's avatar hash */ + avatar: string; + + /** Whether the user belongs to an OAuth2 application */ + bot?: boolean; + + /** Whether the user is an Official Discord System user (part of the urgent message system) */ + system?: boolean; + + /** Whether the user has two factor enabled on their account */ + mfa_enabled?: boolean; + + // Types with "email" scope intentionally left out. + /** The flags on a user's account */ + flags?: number; + + /** The type of Nitro subscription on a user's account */ + premium_type?: PremiumType; +} + +export const enum PremiumType { + NitroClassic = 1, + Nitro +} \ No newline at end of file