From 2f16b77cc9313836245d0e9017b693914b8d9cd8 Mon Sep 17 00:00:00 2001 From: Will Hoskings Date: Sun, 9 Feb 2020 23:00:14 +0000 Subject: [PATCH 01/12] 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/12] 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/12] 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/12] 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 0ee59ebbc24222e3ad941d498c4e43e66082c167 Mon Sep 17 00:00:00 2001 From: Will Hoskings Date: Tue, 11 Feb 2020 18:23:25 +0000 Subject: [PATCH 05/12] 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 06/12] 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 07/12] 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 08/12] 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 09/12] 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 10/12] (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 11/12] 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 12/12] 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