From bcf8c3002131b43fda2992444314ac25561351b8 Mon Sep 17 00:00:00 2001 From: Skillz Date: Tue, 25 Feb 2020 22:56:15 -0500 Subject: [PATCH] even more fixes --- module/client.ts | 73 ++++++++++++----------- module/discord-request-manager.ts | 8 +-- module/gateway.ts | 98 +++++++++++++++---------------- structures/channel.ts | 55 ++++------------- structures/guild.ts | 33 ++++++----- structures/role.ts | 2 +- types/options.ts | 15 ++--- utils/cache.ts | 5 +- utils/cdn.ts | 4 +- 9 files changed, 130 insertions(+), 163 deletions(-) diff --git a/module/client.ts b/module/client.ts index dd673b525..9218cbc17 100644 --- a/module/client.ts +++ b/module/client.ts @@ -1,16 +1,16 @@ -import { endpoints } from '../constants/discord' -import DiscordRequestManager from '../managers/DiscordRequestManager.ts' -import { DiscordBotGatewayData, DiscordPayload, DiscordHeartbeatPayload, GatewayOpcode } from '../types/discord.ts' -import ShardingManager from '../managers/ShardingManager.ts' +import { endpoints } from "../constants/discord.ts" +import DiscordRequestManager from "./discord-request-manager.ts" +import { DiscordBotGatewayData, DiscordPayload, DiscordHeartbeatPayload, GatewayOpcode } from "../types/discord.ts" +import ShardingManager from "./sharding-manager.ts" import { connectWebSocket, isWebSocketCloseEvent, isWebSocketPingEvent, isWebSocketPongEvent -} from 'https://deno.land/std/ws/mod.ts' -import Gateway from './gateway.ts' -import { ClientOptions, FulfilledClientOptions } from '../types/options.ts' -import { CollectedMessageType } from '../types/message-type.ts' +} from "https://deno.land/std/ws/mod.ts" +import Gateway from "./gateway.ts" +import { ClientOptions, FulfilledClientOptions } from "../types/options.ts" +import { CollectedMessageType } from "../types/message-type.ts" class Client { bot_id: string @@ -31,9 +31,9 @@ class Client { this.options = Object.assign( { properties: { - $os: '...', - $browser: '...', - $device: '...' + $os: "...", + $browser: "...", + $device: "..." }, compress: false }, @@ -57,15 +57,15 @@ class Client { 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); + 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, + gateway, + messages, connection: this.connect(gateway, data) } } @@ -73,7 +73,7 @@ class Client { async *collectMessages(gateway: Gateway) { const { socket } = gateway for await (const message of socket.receive()) { - if (typeof message === 'string') { + if (typeof message === "string") { yield { type: CollectedMessageType.Message, data: JSON.parse(message) @@ -90,25 +90,28 @@ class Client { } /** 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 }> { + 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; + console.log("Ping!") + yield message break case CollectedMessageType.Pong: - console.log('Pong!') - yield message; + console.log("Pong!") + yield message break case CollectedMessageType.Close: - console.log('Close :(', message) - yield message; + console.log("Close :(", message) + yield message break case CollectedMessageType.Message: - await this.handleDiscordPayload(message.data, gateway); - yield message; - console.log({ yay: true, ...message }); + await this.handleDiscordPayload(message.data, gateway) + yield message + console.log({ yay: true, ...message }) break } } @@ -117,20 +120,20 @@ class Client { this.spawnShards(data.shards) } - handleDiscordPayload(data: DiscordPayload, gateway: Gateway) { + handleDiscordPayload(data: DiscordPayload, gateway: Gateway) { switch (data.op) { case GatewayOpcode.Hello: - console.log('heartbeating...'); - return gateway.sendConstantHeartbeats((data.d as DiscordHeartbeatPayload).heartbeat_interval, data.s); - } + 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); + // 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) } } diff --git a/module/discord-request-manager.ts b/module/discord-request-manager.ts index c82ba63c6..8e32c0640 100644 --- a/module/discord-request-manager.ts +++ b/module/discord-request-manager.ts @@ -1,5 +1,5 @@ import Client from "../module/Client.ts"; -import { RequestMethod } from "../types/fetch"; +import { RequestMethod } from "../types/fetch.ts"; type RequestBody = string | Blob | ArrayBufferView | ArrayBuffer | FormData | URLSearchParams | null | undefined; @@ -12,9 +12,9 @@ export default class DiscordDiscordRequestManager { this.token = token } - async get(url: string) { + async get(url: string, body?: RequestBody) { const headers = this.getDiscordHeaders(); - return fetch(url, { headers }).then(res => res.json()) + return fetch(url, { headers, body }).then(res => res.json()) } async post (url: string, body: RequestBody) { @@ -26,7 +26,7 @@ export default class DiscordDiscordRequestManager { }); } - async delete (url: string, body: RequestBody) { + async delete (url: string, body?: RequestBody) { const headers = this.getDiscordHeaders(); return fetch(url, { method: RequestMethod.Delete, diff --git a/module/gateway.ts b/module/gateway.ts index e7dd3ddae..94f94c0b6 100644 --- a/module/gateway.ts +++ b/module/gateway.ts @@ -1,56 +1,54 @@ -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'; +import { 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) {} + 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 - } - }); + 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 } - sendHeartbeat (previousSequenceNumber: number | null = null) { - return this.sendObject({ - op: GatewayOpcode.Heartbeat, - d: previousSequenceNumber - }); - } + // 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) + } - 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 + sendObject(object: object) { + return this.socket.send(JSON.stringify(object)) + } +} diff --git a/structures/channel.ts b/structures/channel.ts index 6b30a974d..e3dc58383 100644 --- a/structures/channel.ts +++ b/structures/channel.ts @@ -7,15 +7,13 @@ import { Get_Messages_Before, MessageContent, Create_Invite_Options -} from '../types/channel.ts' -import Client from '../module/client.ts' -import { endpoints } from '../constants/discord.ts' -import { create_message, Message } from './message.ts' -import { Message_Create_Options } from '../types/message.ts' -import { Permission, Permissions } from '../types/permission.ts' -import { Guild } from '../types/return-type.ts' +} from "../types/channel.ts" +import Client from "../module/client.ts" +import { endpoints } from "../constants/discord.ts" +import { create_message, Message } from "./message.ts" +import { Message_Create_Options } from "../types/message.ts" -export const create_channel = (data: Channel_Create_Options, guild: Guild, client: Client) => { +export const create_channel = (data: Channel_Create_Options, client: Client) => { const base_channel = { /** The unique id of the channel */ id: data.id, @@ -60,7 +58,7 @@ export const create_channel = (data: Channel_Create_Options, guild: Guild, clien // TODO: check if the bot has SEND_MESSAGES permission } - if (typeof content === 'string') content = { content } + if (typeof content === "string") content = { content } if (content.tts) { // TODO: check if the bot has SEND_TTS_MESSAGE } @@ -91,33 +89,7 @@ export const create_channel = (data: Channel_Create_Options, guild: Guild, clien parent_id: () => data.parent_id, // TODO: fix this from being number on allow and deny to being array of strings /** Fetch the permission overwrites */ - permission_overwrites: () => data.permission_overwrites, - /** Check whether a member has certain permissions in this channel. */ - has_permissions: (id: string, permissions: Permission[]) => { - if (id === guild.owner_id()) return true - - const member = guild.members.get(id) - if (!member) { - throw 'Invalid member id provided. This member was not found in the cache. Please fetch them with getMember on guild.' - } - - let permissionBits = member.roles().reduce((bits, role_id) => { - const role = guild.roles.get(role_id) - if (!role) return bits - - bits |= role.permissions() - - return bits - }, 0) - - data.permission_overwrites?.forEach(overwrite => { - permissionBits = (permissionBits & ~overwrite.deny) | overwrite.allow - }) - - if (permissionBits & Permissions.ADMINISTRATOR) return true - - return permissions.every(permission => permissionBits & Permissions[permission]) - } + permission_overwrites: () => data.permission_overwrites } // Guild Text Channel @@ -133,7 +105,7 @@ export const create_channel = (data: Channel_Create_Options, guild: Guild, clien delete_messages: (ids: string[], reason?: string) => { // TODO: Requires the MANAGE_MESSAGES permission. if (ids.length < 2) { - throw 'This endpoint will only accept 2-100 message ids.' + throw "This endpoint will only accept 2-100 message ids." } if (ids.length > 100) { console.warn( @@ -163,14 +135,7 @@ export const create_channel = (data: Channel_Create_Options, guild: Guild, clien } } - if (data.type === Channel_Types.GUILD_CATEGORY) { - return { - ...base_guild_channel, - /** Gets an array of all the channels ids that are the children of this category. */ - children_ids: () => - Object.keys(guild.channels).filter(channel => guild.channels.get(channel).parent_id === data.id) - } - } + if (data.type === Channel_Types.GUILD_CATEGORY) return base_guild_channel if (data.type === Channel_Types.GUILD_VOICE) { return { diff --git a/structures/guild.ts b/structures/guild.ts index ace16d00b..6a540de91 100644 --- a/structures/guild.ts +++ b/structures/guild.ts @@ -1,6 +1,6 @@ -import Client from '../module/client.ts' -import { endpoints } from '../constants/discord.ts' -import { format_image_url } from '../utils/cdn.ts' +import Client from "../module/client.ts" +import { endpoints } from "../constants/discord.ts" +import { format_image_url } from "../utils/cdn.ts" import { Create_Guild_Payload, ChannelTypes, @@ -13,13 +13,13 @@ import { Create_Emojis_Options, Edit_Emojis_Options, Create_Role_Options -} from '../types/guild.ts' -import { create_role } from './role.ts' -import { create_member } from './member.ts' -import { create_channel } from './channel.ts' -import { Channel_Create_Options } from '../types/channel.ts' -import { Image_Size, Image_Formats } from '../types/cdn.ts' -import { Permissions, Permission } from '../types/permission.ts' +} from "../types/guild.ts" +import { create_role } from "./role.ts" +import { create_member } from "./member.ts" +import { create_channel } from "./channel.ts" +import { Channel_Create_Options } from "../types/channel.ts" +import { Image_Size, Image_Formats } from "../types/cdn.ts" +import { Permissions, Permission } from "../types/permission.ts" export const create_guild = (data: Create_Guild_Payload, client: Client) => { const guild = { @@ -66,7 +66,7 @@ export const create_guild = (data: Create_Guild_Payload, client: Client) => { /** The users in this guild. */ members: new Map(data.members.map(m => [m.user.id, create_member(m, data.id, data.roles, data.owner_id, client)])), /** The channels in the guild */ - channels: new Map(data.channels.map(c => [c.id, create_channel(c, client)])), + channels: new Map(data.channels.map(c => [c.id, create_channel(c, guild, client)])), /** The presences of all the users in the guild. */ presences: new Map(data.presences.map(p => [p.user.id, p])), /** The maximum amount of presences for the guild(the default value, currently 5000 is in effect when null is returned.) */ @@ -85,6 +85,8 @@ export const create_guild = (data: Create_Guild_Payload, client: Client) => { premium_subscription_count: () => data.premium_subscription_count, /** The preferred locale of this guild only set if the guild has the DISCOVERABLE feature, defaults to en-US */ preferred_locale: () => data.preferred_locale, + /** Gets an array of all the channels ids that are the children of this category. */ + category_children_ids: (id: string) => data.channels.filter(c => c.parent_id === id).map(c => c.id), /** The full URL of the icon from Discords CDN. Undefined when no icon is set. */ icon_url: (size: Image_Size = 128, format?: Image_Formats) => data.icon ? format_image_url(endpoints.GUILD_ICON(data.id, data.icon), size, format) : undefined, @@ -120,7 +122,7 @@ export const create_guild = (data: Create_Guild_Payload, client: Client) => { /** Modify the positions of channels on the guild. Requires MANAGE_CHANNELS permisison. */ swap_channels: (channel_positions: Position_Swap[]) => { if (channel_positions.length < 2) { - throw 'You must provide atleast two channels to be swapped.' + throw "You must provide atleast two channels to be swapped." } return client.discordRequestManager.patch(endpoints.GUILD_CHANNELS(data.id), channel_positions) }, @@ -268,12 +270,12 @@ export const create_guild = (data: Create_Guild_Payload, client: Client) => { const member = guild.members.get(member_id) if (!member) { - throw 'Invalid member id provided. This member was not found in the cache. Please fetch them with getMember on guild.' + throw "Invalid member id provided. This member was not found in the cache. Please fetch them with getMember on guild." } const channel = guild.channels.get(channel_id) if (!channel) { - throw 'Invalid channel id provided. This channel was not found in the cache.' + throw "Invalid channel id provided. This channel was not found in the cache." } let permissionBits = member.roles().reduce((bits, role_id) => { @@ -285,7 +287,7 @@ export const create_guild = (data: Create_Guild_Payload, client: Client) => { return bits }, 0) - data.permission_overwrites?.forEach(overwrite => { + channel.permission_overwrites?.forEach(overwrite => { permissionBits = (permissionBits & ~overwrite.deny) | overwrite.allow }) @@ -318,6 +320,5 @@ export const create_guild = (data: Create_Guild_Payload, client: Client) => { } } - return guild } diff --git a/structures/role.ts b/structures/role.ts index d2e217b6a..030050379 100644 --- a/structures/role.ts +++ b/structures/role.ts @@ -1,4 +1,4 @@ -import { Role_Data } from '../types/role' +import { Role_Data } from '../types/role.ts' export const create_role = (data: Role_Data) => ({ /** The entire raw Role data */ diff --git a/types/options.ts b/types/options.ts index 10b108c55..4aa185de5 100644 --- a/types/options.ts +++ b/types/options.ts @@ -1,13 +1,14 @@ -import { Properties } from "./discord.ts"; +import { Properties } from "./discord.ts" export interface FulfilledClientOptions { - token: string; - properties: Properties; - compress: boolean; + token: string + properties: Properties + compress: boolean } export interface ClientOptions { - token: string; - properties?: Properties; - compress?: boolean; + token: string + properties?: Properties + compress?: boolean + bot_id: string } diff --git a/utils/cache.ts b/utils/cache.ts index 1098d2785..f0d243c42 100644 --- a/utils/cache.ts +++ b/utils/cache.ts @@ -1,6 +1,5 @@ -import { User } from "../structures/user"; -import { Guild } from "../types/guild"; -import { Channel } from "../types/channel"; +import { User } from "../structures/user.ts"; +import { Guild, Channel } from "../types/return-type.ts"; export const cache = { guilds: new Map(), diff --git a/utils/cdn.ts b/utils/cdn.ts index 0e8ab4425..1251142a2 100644 --- a/utils/cdn.ts +++ b/utils/cdn.ts @@ -1,5 +1,5 @@ -import { ImageSize, ImageFormats } from '../structures/guild' +import { Image_Size, Image_Formats } from "../types/cdn.ts" -export const format_image_url = (url: string, size: ImageSize = 128, format?: ImageFormats) => { +export const format_image_url = (url: string, size: Image_Size = 128, format?: Image_Formats) => { return `${url}.${format || url.includes('/a_') ? 'gif' : 'jpg'}/?size=${size}` }