Merge pull request #9 from Skillz4Killz/origin/new-api-draft

Done.
This commit is contained in:
Resynth
2020-02-11 18:30:59 +00:00
committed by GitHub
17 changed files with 617 additions and 350 deletions

View File

@@ -1,31 +1,47 @@
import Client from '../module/Client.ts'
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
client: Client;
token: string;
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) {
const headers = this.getDiscordHeaders();
return fetch(url, { headers }).then(res => res.json())
}
// let attempts = 0
const headers = {
Authorization: this.token,
'User-Agent': `DiscordBot (https://github.com/skillz4killz/discordeno, 0.0.1)`
}
async post (url: string, body: RequestBody) {
const headers = this.getDiscordHeaders();
return fetch(url, {
method: RequestMethod.Post,
headers,
body
});
}
console.log('payload', payload)
async delete (url: string, body: RequestBody) {
const headers = this.getDiscordHeaders();
return fetch(url, {
method: RequestMethod.Delete,
headers,
body
});
}
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<string, string> {
return {
Authorization: this.token,
"User-Agent": `DiscordBot (https://github.com/skillz4killz/discordeno, 0.0.1)`,
};
}
}
export default RequestManager

24
mod.ts
View File

@@ -1,7 +1,21 @@
import Client from './module/Client.ts'
import { configs } from './configs.ts'
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 () {
const client = new Client({
token: configs.token
});
export default Discordeno
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
})
}
}
})();

View File

@@ -1,6 +1,6 @@
import { endpoints } from '../constants/discord.ts'
import RequestManager from '../managers/RequestManager.ts'
import { DiscordBotGateway, DiscordPayload, DiscordHeartbeatPayload } from '../types/discord.ts'
import { DiscordBotGatewayData, DiscordPayload, DiscordHeartbeatPayload, GatewayOpcode } from '../types/discord.ts'
import ShardingManager from '../managers/ShardingManager.ts'
import {
connectWebSocket,
@@ -9,11 +9,9 @@ import {
isWebSocketPongEvent,
WebSocket
} 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 { keepDiscordWebsocketAlive, updatePreviousSequenceNumber } from './websocket.ts'
import { logGreen, logRed, logYellow, logBlue } from '../utils/logger.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. */
@@ -23,35 +21,94 @@ class Client {
/** Creates and handles all the shards necessary for the bot. */
ShardingManager: ShardingManager
constructor(token: string) {
this.token = `Bot ${token}`
this.RequestManager = new RequestManager(this, this.token)
/** 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()
}
/** 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)
logGreen("ws connected! (type 'close' to quit)")
getGatewayData() {
return this.RequestManager.get(endpoints.GATEWAY_BOT) as Promise<DiscordBotGatewayData>
}
for await (const msg of sock.receive()) {
if (typeof msg === 'string') {
try {
const json = JSON.parse(msg)
this.handleDiscordPayload(json, sock)
} catch {
logRed(`Invalid JSON String send by discord: ${msg}`)
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)
}
logYellow('< ' + msg)
} else if (isWebSocketPingEvent(msg)) {
logBlue('< ping')
} else if (isWebSocketPongEvent(msg)) {
logBlue('< pong')
} else if (isWebSocketCloseEvent(msg)) {
logRed(`closed: code=${msg.code}, reason=${msg.reason}`)
} 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(gateway: Gateway, data: DiscordBotGatewayData): AsyncGenerator<{ type: CollectedMessageType, data?: DiscordPayload, action?: Promise<void> }> {
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
}
}
@@ -59,15 +116,15 @@ class Client {
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)
break
case 11:
updatePreviousSequenceNumber(data.s)
break
}
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) {

3
module/Ratelimiter.ts Normal file
View File

@@ -0,0 +1,3 @@
export class Ratelimiter {
}

56
module/gateway.ts Normal file
View File

@@ -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<void> {
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));
}
}

View File

@@ -1,24 +0,0 @@
import { WebSocket } from 'https://deno.land/std/ws/mod.ts'
let previousSequenceNumber: number | null = null
export const keepDiscordWebsocketAlive = (
socket: WebSocket,
millesecondsInterval: number,
payload: number | null = null
) => {
previousSequenceNumber = payload
setInterval(() => {
socket.send(
JSON.stringify({
op: 1,
d: previousSequenceNumber
})
)
}, millesecondsInterval)
}
export const updatePreviousSequenceNumber = (sequence: number | null = null) => {
previousSequenceNumber = sequence
}

20
structures/activity.ts Normal file
View File

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

View File

@@ -1,3 +1,9 @@
export interface EmojiPayload {
name: string;
id?: string;
animated?: boolean;
}
export const createEmoji = (data: unknown) => {
console.log(data)
}

View File

@@ -8,239 +8,132 @@ import { createMember } from './member'
import { createChannel } from './channel'
import { createPresence } from './presence'
interface CreateGuildPayload {
/** The guild id */
id: string
/** The guild name 2-100 characters */
name: string
/** The guild icon image hash */
icon: string | null
/** The guild splash image hash */
splash: string | null
/** The id of the owner */
owner_id: string
/** The voice region id for the guild */
region: string
/** The afk channel id */
afk_channel_id: string | null
/** AFK Timeout in seconds. */
afk_timeout: number
/** The verification level required for the guild */
verification_level: number
/** The roles in the guild */
roles: Role[]
/** The custom guild emojis */
emojis: Emoji[]
/** Enabled guild features */
features: GuildFeatures[]
/** Required MFA level for the guild */
mfa_level: number
/** The id of the channel to which system mesages are sent */
system_channel_id: string | null
/** When this guild was joined at */
joined_at: string
/** Whether this is considered a large guild */
large: boolean
/** Whether this guild is unavailable */
unavailable: boolean
/** Total number of members in this guild */
member_count: number
voice_states: VoiceState[]
/** Users in the guild */
members: Member[]
/** Channels in the guild */
channels: Channel[]
presences: Presence[]
/** The maximum amount of presences for the guild(the default value, currently 5000 is in effect when null is returned.) */
max_presences?: number | null
/** The maximum amount of members for the guild */
max_members?: number
/** The vanity url code for the guild */
vanity_url_code: string | null
/** The description for the guild */
description: string | null
/** The banner hash */
banner: string | null
/** The premium tier */
premium_tier: number
/** The total number of users currently boosting this server. */
premium_subscription_count: number
/** The preferred local of this guild only set if guild has the DISCOVERABLE feature, defaults to en-US */
preferred_locale: string
export interface CreateGuildPayload {
/** The guild id */
id: string
/** The guild name 2-100 characters */
name: string
/** The guild icon image hash */
icon: string | null
/** The guild splash image hash */
splash: string | null
/** The id of the owner */
owner_id: string
/** The voice region id for the guild */
region: string
/** The afk channel id */
afk_channel_id: string | null
/** AFK Timeout in seconds. */
afk_timeout: number
/** The verification level required for the guild */
verification_level: number
/** The roles in the guild */
roles: Role[]
/** The custom guild emojis */
emojis: Emoji[]
/** Enabled guild features */
features: GuildFeatures[]
/** Required MFA level for the guild */
mfa_level: number
/** The id of the channel to which system mesages are sent */
system_channel_id: string | null
/** When this guild was joined at */
joined_at: string
/** Whether this is considered a large guild */
large: boolean
/** Whether this guild is unavailable */
unavailable: boolean
/** Total number of members in this guild */
member_count: number
voice_states: VoiceState[]
/** Users in the guild */
members: Member[]
/** Channels in the guild */
channels: Channel[]
presences: Presence[]
/** The maximum amount of presences for the guild(the default value, currently 5000 is in effect when null is returned.) */
max_presences?: number | null
/** The maximum amount of members for the guild */
max_members?: number
/** The vanity url code for the guild */
vanity_url_code: string | null
/** The description for the guild */
description: string | null
/** The banner hash */
banner: string | null
/** The premium tier */
premium_tier: number
/** The total number of users currently boosting this server. */
premium_subscription_count: number
/** The preferred local of this guild only set if guild has the DISCOVERABLE feature, defaults to en-US */
preferred_locale: string
}
interface Guild {
/** The guild id */
id: string
/** The guild name 2-100 characters */
name: string
/** The guild icon image hash */
icon: string | null
/** The guild splash image hash */
splash: string | null
/** The id of the owner */
ownerID: string
/** The voice region id for the guild */
region: string
/** The afk channel id */
afkChannelID: string | null
/** AFK Timeout in seconds. */
afkTimeout: number
/** The verification level required for the guild */
verificationLevel: number
/** The roles in the guild */
roles: Role[]
/** The custom guild emojis */
emojis: Emoji[]
/** Enabled guild features */
features: GuildFeatures[]
/** Required MFA level for the guild */
mfaLevel: number
/** The id of the channel to which system mesages are sent */
systemChannelID: string | null
/** When this guild was joined at */
joinedAt: number
/** Whether this is considered a large guild */
large: boolean
/** Whether this guild is unavailable */
unavailable: boolean
/** Total number of members in this guild */
memberCount: number
voiceStates: VoiceState[]
/** Users in the guild */
members: Member[]
/** Channels in the guild */
channels: Channel[]
presences: Presence[]
/** The maximum amount of presences for the guild(the default value, currently 5000 is in effect when null is returned.) */
maxPresences?: number | null
/** The maximum amount of members for the guild */
maxMembers?: number
/** The vanity url code for the guild */
vanityURLCode: string | null
/** The description for the guild */
description: string | null
/** The banner hash */
banner: string | null
/** The premium tier */
premiumTier: number
/** The total number of users currently boosting this server. */
premiumSubscriptionCount: number
/** The preferred local of this guild only set if guild has the DISCOVERABLE feature, defaults to en-US */
preferredLocale: string
/** The full URL of the icon from Discords CDN. Undefined when no icon is set. */
iconURL(size?: ImageSize, format?: ImageFormats): string | undefined
/** The full URL of the splash from Discords CDN. Undefined if no splash is set. */
splashURL(size?: ImageSize, format?: ImageFormats): string | undefined
/** The full URL of the banner from Discords CDN. Undefined if no banner is set. */
bannerURL(size?: ImageSize, format?: ImageFormats): string | undefined
/** Create a channel in your server. Bot needs MANAGE_CHANNEL permissions in the server. */
createChannel(name: string, options: ChannelCreateOptions): Promise<Channel>
/** Create an emoji in the server. Emojis and animated emojis have a maximum file size of 256kb. Attempting to upload an emoji larger than this limit will fail and return 400 Bad Request and an error message, but not a JSON status code. */
createEmoji(name: string, image: string, options: CreateEmojisOptions): Promise<Emoji>
/** Modify the given emoji. Requires the MANAGE_EMOJIS permission. */
editEmoji(id: string, options: EditEmojisOptions): Promise<Emoji>
/** Delete the given emoji. Requires the MANAGE_EMOJIS permission. Returns 204 No Content on success. */
deleteEmoji(id: string, reason?: string): Promise<void>
/** Create a new role for the guild. Requires the MANAGE_ROLES permission. */
createRole(options: CreateRoleOptions): Promise<Role>
/** Edi a guild role. Requires the MANAGE_ROLES permission. */
editRole(id: string, options: CreateRoleOptions): Promise<Role>
/** Delete a guild role. Requires the MANAGE_ROLES permission. */
deleteRole(id: string): Promise<void>
/** Check how many members would be removed from the server in a prune operation. Requires the KICK_MEMBERS permission */
getPruneCount(days: number): Promise<number>
/** Begin pruning all members in the given time period */
pruneMembers(days: number): Promise<void>
getAuditLogs(options: GetAuditLogsOptions): Promise<AuditLog>
leaveVoiceChannel(): Promise<void>
}
export interface GetAuditLogsOptions {
/** Filter the logs for actions made by this user. */
user_id?: string
/** The type of audit log. */
action_type?: AuditLogType
/** Filter the logs before a certain log entry. */
before?: string
/** How many entries are returned. Between 1-100. Default 50. */
limit?: number
}
export type AuditLogType =
| `GUILD_UPDATE`
| `CHANNEL_CREATE`
| `CHANNEL_UPDATE`
| `CHANNEL_DELETE`
| `CHANNEL_OVERWRITE_CREATE`
| `CHANNEL_OVERWRITE_UPDATE`
| `CHANNEL_OVERWRITE_DELETE`
| `MEMBER_KICK`
| `MEMBER_PRUNE`
| `MEMBER_BAN_ADD`
| `MEMBER_BAN_REMOVE`
| `MEMBER_UPDATE`
| `MEMBER_ROLE_UPDATE`
| `MEMBER_MOVE`
| `MEMBER_DISCONNECT`
| `BOT_ADD`
| `ROLE_CREATE`
| `ROLE_UPDATE`
| `ROLE_DELETE`
| `INVITE_CREATE`
| `INVITE_UPDATE`
| `INVITE_DELETE`
| `WEBHOOK_CREATE`
| `WEBHOOK_UPDATE`
| `WEBHOOK_DELETE`
| `EMOJI_CREATE`
| `EMOJI_UPDATE`
| `EMOJI_DELETE`
| `MESSAGE_DELETE`
| `MESSAGE_BULK_DELETE`
| `MESSAGE_PIN`
| `MESSAGE_UNPIN`
| `INTEGRATION_CREATE`
| `INTEGRATION_UPDATE`
| `INTEGRATION_DELETE`
export enum AuditLogs {
GUILD_UPDATE = 1,
CHANNEL_CREATE = 10,
CHANNEL_UPDATE,
CHANNEL_DELETE,
CHANNEL_OVERWRITE_CREATE,
CHANNEL_OVERWRITE_UPDATE,
CHANNEL_OVERWRITE_DELETE,
MEMBER_KICK = 20,
MEMBER_PRUNE,
MEMBER_BAN_ADD,
MEMBER_BAN_REMOVE,
MEMBER_UPDATE,
MEMBER_ROLE_UPDATE,
MEMBER_MOVE,
MEMBER_DISCONNECT,
BOT_ADD,
ROLE_CREATE = 30,
ROLE_UPDATE,
ROLE_DELETE,
INVITE_CREATE = 40,
INVITE_UPDATE,
INVITE_DELETE,
WEBHOOK_CREATE = 50,
WEBHOOK_UPDATE,
WEBHOOK_DELETE,
EMOJI_CREATE = 60,
EMOJI_UPDATE,
EMOJI_DELETE,
MESSAGE_DELETE = 72,
MESSAGE_BULK_DELETE,
MESSAGE_PIN,
MESSAGE_UNPIN,
INTEGRATION_CREATE = 80,
INTEGRATION_UPDATE,
INTEGRATION_DELETE
export interface Guild {
/** The guild id */
id: string
/** The guild name 2-100 characters */
name: string
/** The guild icon image hash */
icon: string | null
/** The guild splash image hash */
splash: string | null
/** The id of the owner */
ownerID: string
/** The voice region id for the guild */
region: string
/** The afk channel id */
afkChannelID: string | null
/** AFK Timeout in seconds. */
afkTimeout: number
/** The verification level required for the guild */
verificationLevel: number
/** The roles in the guild */
roles: Role[]
/** The custom guild emojis */
emojis: Emoji[]
/** Enabled guild features */
features: GuildFeatures[]
/** Required MFA level for the guild */
mfaLevel: number
/** The id of the channel to which system mesages are sent */
systemChannelID: string | null
/** When this guild was joined at */
joinedAt: number
/** Whether this is considered a large guild */
large: boolean
/** Whether this guild is unavailable */
unavailable: boolean
/** Total number of members in this guild */
memberCount: number
voiceStates: VoiceState[]
/** Users in the guild */
members: Member[]
/** Channels in the guild */
channels: Channel[]
presences: Presence[]
/** The maximum amount of presences for the guild(the default value, currently 5000 is in effect when null is returned.) */
maxPresences?: number | null
/** The maximum amount of members for the guild */
maxMembers?: number
/** The vanity url code for the guild */
vanityURLCode: string | null
/** The description for the guild */
description: string | null
/** The banner hash */
banner: string | null
/** The premium tier */
premiumTier: number
/** The total number of users currently boosting this server. */
premiumSubscriptionCount: number
/** The preferred local of this guild only set if guild has the DISCOVERABLE feature, defaults to en-US */
preferredLocale: string
/** The full URL of the icon from Discords CDN. Undefined when no icon is set. */
iconURL(): string | undefined
/** The full URL of the splash from Discords CDN. Undefined if no splash is set. */
splashURL(): string | undefined
/** The full URL of the banner from Discords CDN. Undefined if no banner is set. */
bannerURL(): string | undefined
}
export type ImageSize = 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048

View File

@@ -1,3 +1,48 @@
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;
}
export const createPresence = (data: unknown) => {
console.log(data)
}

34
structures/user.ts Normal file
View File

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

View File

@@ -1,14 +1,66 @@
{
"compilerOptions": {
"target": "esnext",
"strict": true /* Enable all strict type-checking options. */,
"noUnusedLocals": true /* Report errors on unused locals. */,
"noUnusedParameters": true /* Report errors on unused parameters. */,
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
"experimentalDecorators": true /* Enables experimental support for ES7 decorators. */,
"emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
/* Basic Options */
// "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", "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'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
"downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
"noUnusedLocals": true, /* Report errors on unused locals. */
"noUnusedParameters": true, /* Report errors on unused parameters. */
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// Hack to stop VSCode from suggesting imports without ./ or ../ as a prefix.
"baseUrl": "../../", /* Base directory to resolve non-absolute module names. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}

View File

@@ -9,20 +9,20 @@ export interface DiscordPayload {
t?: string
}
export interface DiscordBotGateway {
/** The WSS URL that can be used for connecting to the gateway. */
url: string
/** The recommended number of shards to use when connecting. */
shards: number
/** Info on the current start limit. */
session_start_limit: {
/** The total number of session starts the current user is allowed. */
total: number
/** The remaining number of session starts the current user is allowed. */
remaining: number
/** Milliseconds left until limit is reset. */
reset_after: number
}
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. */
shards: number
/** Info on the current start limit. */
session_start_limit: {
/** The total number of session starts the current user is allowed. */
total: number
/** The remaining number of session starts the current user is allowed. */
remaining: number
/** Milliseconds left until limit is reset. */
reset_after: number
}
}
export interface DiscordHeartbeatPayload {
@@ -30,17 +30,17 @@ export interface DiscordHeartbeatPayload {
}
export enum GatewayOpcode {
Dispatch = 0,
Heartbeat,
Identify,
StatusUpdate,
VoiceStateUpdate,
Resume,
Reconnect,
RequestGuildMembers,
InvalidSession,
Hello,
HeartbeatACK
Dispatch = 0,
Heartbeat,
Identify,
StatusUpdate,
VoiceStateUpdate,
Resume = 6,
Reconnect,
RequestGuildMembers,
InvalidSession,
Hello,
HeartbeatACK
}
export enum GatewayCloseEventCode {
@@ -154,3 +154,33 @@ 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 enum StatusType {
Online = 'online',
DoNotDisturb = 'dnd',
Idle = 'idle',
Invisible = 'invisible',
Offline = 'offline'
}
export interface Status {
afk: boolean;
status: StatusType;
}

8
types/fetch.ts Normal file
View File

@@ -0,0 +1,8 @@
export const enum RequestMethod {
Get = 'get',
Post = 'post',
Put = 'put',
Patch = 'patch',
Head = 'head',
Delete = 'delete'
}

6
types/message-type.ts Normal file
View File

@@ -0,0 +1,6 @@
export enum CollectedMessageType {
Ping,
Pong,
Close,
Message
}

13
types/options.ts Normal file
View File

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

38
types/queue.ts Normal file
View File

@@ -0,0 +1,38 @@
import { DiscordPayload } from "./discord";
import Gateway from "../module/gateway.ts";
export abstract class ActionQueue<Action> {
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<DiscordPayload> {
constructor (protected gateway: Gateway) {
super();
}
dispatch (action: DiscordPayload) {
this.gateway.sendObject(action);
}
shouldDispatchImmediately ()
}