From 3ef487c4a4d7eb1227091923c4399e004e409cdb Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 30 Mar 2021 21:19:04 -0500 Subject: [PATCH] Reorganized lib/libd functions and Lavalink --- src/commands/admin.ts | 4 +- src/commands/fun/cookie.ts | 2 +- src/commands/fun/neko.ts | 2 +- src/commands/info.ts | 3 +- src/core/command.ts | 2 +- src/core/lib.ts | 165 +++++++++++++++++++++++++++++++++++++ src/core/libd.ts | 164 ------------------------------------ src/core/structures.ts | 2 +- src/index.ts | 56 +------------ src/modules/lavalink.ts | 53 ++++++++++++ 10 files changed, 230 insertions(+), 223 deletions(-) create mode 100644 src/modules/lavalink.ts diff --git a/src/commands/admin.ts b/src/commands/admin.ts index e0c4362..72c5686 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -1,9 +1,9 @@ import Command, {handler} from "../core/command"; -import {botHasPermission, clean} from "../core/libd"; +import {clean} from "../core/lib"; +import {botHasPermission} from "../core/libd"; import {Config, Storage} from "../core/structures"; import {getPermissionLevel, getPermissionName} from "../core/permissions"; import {Permissions} from "discord.js"; -import * as discord from "discord.js"; import {logs} from "../globals"; function getLogBuffer(type: string) { diff --git a/src/commands/fun/cookie.ts b/src/commands/fun/cookie.ts index 39a3948..06c7086 100644 --- a/src/commands/fun/cookie.ts +++ b/src/commands/fun/cookie.ts @@ -1,7 +1,7 @@ import {User} from "discord.js"; import Command from "../../core/command"; import {random} from "../../core/lib"; -import {parseVars} from "../../core/libd"; +import {parseVars} from "../../core/lib"; const cookies = [ `has given %target% a chocolate chip cookie!`, diff --git a/src/commands/fun/neko.ts b/src/commands/fun/neko.ts index 1990bc2..113fb15 100644 --- a/src/commands/fun/neko.ts +++ b/src/commands/fun/neko.ts @@ -1,6 +1,6 @@ import {URL} from "url"; import Command from "../../core/command"; -import {getContent} from "../../core/libd"; +import {getContent} from "../../core/lib"; const endpoints: {sfw: {[key: string]: string}} = { sfw: { diff --git a/src/commands/info.ts b/src/commands/info.ts index 7142ba5..b95f19a 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -2,7 +2,8 @@ import {MessageEmbed, version as djsversion} from "discord.js"; import ms from "ms"; import os from "os"; import Command from "../core/command"; -import {formatBytes, trimArray, getMemberByUsername} from "../core/libd"; +import {formatBytes, trimArray} from "../core/lib"; +import {getMemberByUsername} from "../core/libd"; import {verificationLevels, filterLevels, regions} from "../defs/info"; import moment from "moment"; import utc from "moment"; diff --git a/src/core/command.ts b/src/core/command.ts index c40d5a0..c13b3a3 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -1,4 +1,4 @@ -import {parseVars} from "./libd"; +import {parseVars} from "./lib"; import {Collection} from "discord.js"; import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember} from "discord.js"; import {getPrefix} from "../core/structures"; diff --git a/src/core/lib.ts b/src/core/lib.ts index a0ebcb7..21b4a95 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -1,4 +1,169 @@ // Library for pure functions +import {get} from "https"; +import FileManager from "./storage"; + +/** + * Splits a command by spaces while accounting for quotes which capture string arguments. + * - `\"` = `"` + * - `\\` = `\` + */ +export function parseArgs(line: string): string[] { + let result = []; + let selection = ""; + let inString = false; + let isEscaped = false; + + for (let c of line) { + if (isEscaped) { + if (['"', "\\"].includes(c)) selection += c; + else selection += "\\" + c; + + isEscaped = false; + } else if (c === "\\") isEscaped = true; + else if (c === '"') inString = !inString; + else if (c === " " && !inString) { + result.push(selection); + selection = ""; + } else selection += c; + } + + if (selection.length > 0) result.push(selection); + + return result; +} + +/** + * Allows you to store a template string with variable markers and parse it later. + * - Use `%name%` for variables + * - `%%` = `%` + * - If the invalid token is null/undefined, nothing is changed. + */ +export function parseVars(line: string, definitions: {[key: string]: string}, invalid: string | null = ""): string { + let result = ""; + let inVariable = false; + let token = ""; + + for (const c of line) { + if (c === "%") { + if (inVariable) { + if (token === "") result += "%"; + else { + if (token in definitions) result += definitions[token]; + else if (invalid === null) result += `%${token}%`; + else result += invalid; + + token = ""; + } + } + + inVariable = !inVariable; + } else if (inVariable) token += c; + else result += c; + } + + return result; +} + +export function isType(value: any, type: any): boolean { + if (value === undefined && type === undefined) return true; + else if (value === null && type === null) return true; + else return value !== undefined && value !== null && value.constructor === type; +} + +/** + * Checks a value to see if it matches the fallback's type, otherwise returns the fallback. + * For the purposes of the templates system, this function will only check array types, objects should be checked under their own type (as you'd do anyway with something like a User object). + * If at any point the value doesn't match the data structure provided, the fallback is returned. + * Warning: Type checking is based on the fallback's type. Be sure that the "type" parameter is accurate to this! + */ +export function select(value: any, fallback: T, type: Function, isArray = false): T { + if (isArray && isType(value, Array)) { + for (let item of value) if (!isType(item, type)) return fallback; + return value; + } else { + if (isType(value, type)) return value; + else return fallback; + } +} + +export function clean(text: any) { + if (typeof text === "string") + return text.replace(/`/g, "`" + String.fromCharCode(8203)).replace(/@/g, "@" + String.fromCharCode(8203)); + else return text; +} + +export function trimArray(arr: any, maxLen = 10) { + if (arr.length > maxLen) { + const len = arr.length - maxLen; + arr = arr.slice(0, maxLen); + arr.push(`${len} more...`); + } + return arr; +} + +export function formatBytes(bytes: any) { + if (bytes === 0) return "0 Bytes"; + const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + return `${parseFloat((bytes / Math.pow(1024, i)).toFixed(2))} ${sizes[i]}`; +} + +export function getContent(url: string): Promise<{url: string}> { + return new Promise((resolve, reject) => { + get(url, (res) => { + const {statusCode} = res; + if (statusCode !== 200) { + res.resume(); + reject(`Request failed. Status code: ${statusCode}`); + } + res.setEncoding("utf8"); + let rawData = ""; + res.on("data", (chunk: string) => { + rawData += chunk; + }); + res.on("end", () => { + try { + const parsedData = JSON.parse(rawData); + resolve(parsedData); + } catch (e) { + reject(`Error: ${e.message}`); + } + }); + }).on("error", (err: {message: any}) => { + reject(`Error: ${err.message}`); + }); + }); +} + +export interface GenericJSON { + [key: string]: any; +} + +export abstract class GenericStructure { + private __meta__ = "generic"; + + constructor(tag?: string) { + this.__meta__ = tag || this.__meta__; + } + + public save(asynchronous = true) { + const tag = this.__meta__; + /// @ts-ignore + delete this.__meta__; + FileManager.write(tag, this, asynchronous); + this.__meta__ = tag; + } +} + +// A 50% chance would be "Math.random() < 0.5" because Math.random() can be [0, 1), so to make two equal ranges, you'd need [0, 0.5)U[0.5, 1). +// Similar logic would follow for any other percentage. Math.random() < 1 is always true (100% chance) and Math.random() < 0 is always false (0% chance). +export const Random = { + num: (min: number, max: number) => Math.random() * (max - min) + min, + int: (min: number, max: number) => Math.floor(Random.num(min, max)), + chance: (decimal: number) => Math.random() < decimal, + sign: (number = 1) => number * (Random.chance(0.5) ? -1 : 1), + deviation: (base: number, deviation: number) => Random.num(base - deviation, base + deviation) +}; /** * Pluralises a word and chooses a suffix attached to the root provided. diff --git a/src/core/libd.ts b/src/core/libd.ts index 20a1ab1..ab391cf 100644 --- a/src/core/libd.ts +++ b/src/core/libd.ts @@ -9,7 +9,6 @@ import { NewsChannel, MessageOptions } from "discord.js"; -import {get} from "https"; import FileManager from "./storage"; import {eventListeners} from "../events/messageReactionRemove"; import {client} from "../index"; @@ -274,166 +273,3 @@ export async function callMemberByUsername( else send(`Couldn't find a user by the name of \`${username}\`!`); } else send("You must execute this command in a server!"); } - -/** - * Splits a command by spaces while accounting for quotes which capture string arguments. - * - `\"` = `"` - * - `\\` = `\` - */ -export function parseArgs(line: string): string[] { - let result = []; - let selection = ""; - let inString = false; - let isEscaped = false; - - for (let c of line) { - if (isEscaped) { - if (['"', "\\"].includes(c)) selection += c; - else selection += "\\" + c; - - isEscaped = false; - } else if (c === "\\") isEscaped = true; - else if (c === '"') inString = !inString; - else if (c === " " && !inString) { - result.push(selection); - selection = ""; - } else selection += c; - } - - if (selection.length > 0) result.push(selection); - - return result; -} - -/** - * Allows you to store a template string with variable markers and parse it later. - * - Use `%name%` for variables - * - `%%` = `%` - * - If the invalid token is null/undefined, nothing is changed. - */ -export function parseVars(line: string, definitions: {[key: string]: string}, invalid: string | null = ""): string { - let result = ""; - let inVariable = false; - let token = ""; - - for (const c of line) { - if (c === "%") { - if (inVariable) { - if (token === "") result += "%"; - else { - if (token in definitions) result += definitions[token]; - else if (invalid === null) result += `%${token}%`; - else result += invalid; - - token = ""; - } - } - - inVariable = !inVariable; - } else if (inVariable) token += c; - else result += c; - } - - return result; -} - -export function isType(value: any, type: any): boolean { - if (value === undefined && type === undefined) return true; - else if (value === null && type === null) return true; - else return value !== undefined && value !== null && value.constructor === type; -} - -/** - * Checks a value to see if it matches the fallback's type, otherwise returns the fallback. - * For the purposes of the templates system, this function will only check array types, objects should be checked under their own type (as you'd do anyway with something like a User object). - * If at any point the value doesn't match the data structure provided, the fallback is returned. - * Warning: Type checking is based on the fallback's type. Be sure that the "type" parameter is accurate to this! - */ -export function select(value: any, fallback: T, type: Function, isArray = false): T { - if (isArray && isType(value, Array)) { - for (let item of value) if (!isType(item, type)) return fallback; - return value; - } else { - if (isType(value, type)) return value; - else return fallback; - } -} - -export function clean(text: any) { - if (typeof text === "string") - return text.replace(/`/g, "`" + String.fromCharCode(8203)).replace(/@/g, "@" + String.fromCharCode(8203)); - else return text; -} - -export function trimArray(arr: any, maxLen = 10) { - if (arr.length > maxLen) { - const len = arr.length - maxLen; - arr = arr.slice(0, maxLen); - arr.push(`${len} more...`); - } - return arr; -} - -export function formatBytes(bytes: any) { - if (bytes === 0) return "0 Bytes"; - const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; - const i = Math.floor(Math.log(bytes) / Math.log(1024)); - return `${parseFloat((bytes / Math.pow(1024, i)).toFixed(2))} ${sizes[i]}`; -} - -export function getContent(url: string): Promise<{url: string}> { - return new Promise((resolve, reject) => { - get(url, (res) => { - const {statusCode} = res; - if (statusCode !== 200) { - res.resume(); - reject(`Request failed. Status code: ${statusCode}`); - } - res.setEncoding("utf8"); - let rawData = ""; - res.on("data", (chunk: string) => { - rawData += chunk; - }); - res.on("end", () => { - try { - const parsedData = JSON.parse(rawData); - resolve(parsedData); - } catch (e) { - reject(`Error: ${e.message}`); - } - }); - }).on("error", (err: {message: any}) => { - reject(`Error: ${err.message}`); - }); - }); -} - -export interface GenericJSON { - [key: string]: any; -} - -export abstract class GenericStructure { - private __meta__ = "generic"; - - constructor(tag?: string) { - this.__meta__ = tag || this.__meta__; - } - - public save(asynchronous = true) { - const tag = this.__meta__; - /// @ts-ignore - delete this.__meta__; - FileManager.write(tag, this, asynchronous); - this.__meta__ = tag; - } -} - -// A 50% chance would be "Math.random() < 0.5" because Math.random() can be [0, 1), so to make two equal ranges, you'd need [0, 0.5)U[0.5, 1). -// Similar logic would follow for any other percentage. Math.random() < 1 is always true (100% chance) and Math.random() < 0 is always false (0% chance). -export const Random = { - num: (min: number, max: number) => Math.random() * (max - min) + min, - int: (min: number, max: number) => Math.floor(Random.num(min, max)), - chance: (decimal: number) => Math.random() < decimal, - sign: (number = 1) => number * (Random.chance(0.5) ? -1 : 1), - deviation: (base: number, deviation: number) => Random.num(base - deviation, base + deviation) -}; diff --git a/src/core/structures.ts b/src/core/structures.ts index 07d8f5c..74e5adf 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -1,5 +1,5 @@ import FileManager from "./storage"; -import {select, GenericJSON, GenericStructure} from "./libd"; +import {select, GenericJSON, GenericStructure} from "./lib"; import {watch} from "fs"; import {Guild as DiscordGuild, Snowflake} from "discord.js"; diff --git a/src/index.ts b/src/index.ts index c6320a8..2ccf92c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,62 +1,14 @@ import "./globals"; -import * as discord from "discord.js"; +import {Client} from "discord.js"; import setup from "./setup"; import {Config} from "./core/structures"; import {loadEvents} from "./core/event"; -import "discord.js-lavalink-lib"; -import LavalinkMusic from "discord.js-lavalink-lib"; - -declare module "discord.js" { - interface Presence { - patch(data: any): void; - } -} - -// The terrible hacks were written by none other than The Noble Programmer On The White PC. - -// NOTE: Terrible hack ahead!!! In order to reduce the memory usage of the bot -// we only store the information from presences that we actually end up using, -// which currently is only the (online/idle/dnd/offline/...) status (see -// `src/commands/info.ts`). What data is retrieved from the `data` object -// (which contains the data received from the Gateway) and how can be seen -// here: -// . -const oldPresencePatch = discord.Presence.prototype.patch; -discord.Presence.prototype.patch = function patch(data: any) { - oldPresencePatch.call(this, {status: data.status}); -}; +import {attachToClient} from "./modules/lavalink"; // This is here in order to make it much less of a headache to access the client from other files. // This of course won't actually do anything until the setup process is complete and it logs in. -export const client = new discord.Client(); - -// NOTE: Terrible hack continued!!! Unfortunately we can't receive the presence -// data at all when the GUILD_PRESENCES intent is disabled, so while we do -// waste network bandwidth and the CPU time for decoding the incoming packets, -// the function which handles those packets is NOP-ed out, which, among other -// things, skips the code which caches the referenced users in the packet. See -// . -(client["actions"] as any)["PresenceUpdate"].handle = () => {}; - -(client as any).music = LavalinkMusic(client, { - lavalink: { - restnode: { - host: "localhost", - port: 2333, - password: "youshallnotpass" - }, - nodes: [ - { - host: "localhost", - port: 2333, - password: "youshallnotpass" - } - ] - }, - prefix: Config.prefix, - helpCmd: "mhelp", - admins: ["717352467280691331"] -}); +export const client = new Client(); +attachToClient(client); // Command loading will start as soon as an instance of "core/command" is loaded, which is loaded during "events/message". setup.init().then(() => { diff --git a/src/modules/lavalink.ts b/src/modules/lavalink.ts new file mode 100644 index 0000000..5e0ff90 --- /dev/null +++ b/src/modules/lavalink.ts @@ -0,0 +1,53 @@ +import {Presence, Client} from "discord.js"; +import LavalinkMusic from "discord.js-lavalink-lib"; +import {Config} from "../core/structures"; + +declare module "discord.js" { + interface Presence { + patch(data: any): void; + } +} + +// The terrible hacks were written by none other than The Noble Programmer On The White PC. + +// NOTE: Terrible hack ahead!!! In order to reduce the memory usage of the bot +// we only store the information from presences that we actually end up using, +// which currently is only the (online/idle/dnd/offline/...) status (see +// `src/commands/info.ts`). What data is retrieved from the `data` object +// (which contains the data received from the Gateway) and how can be seen +// here: +// . +const oldPresencePatch = Presence.prototype.patch; +Presence.prototype.patch = function patch(data: any) { + oldPresencePatch.call(this, {status: data.status}); +}; + +// NOTE: Terrible hack continued!!! Unfortunately we can't receive the presence +// data at all when the GUILD_PRESENCES intent is disabled, so while we do +// waste network bandwidth and the CPU time for decoding the incoming packets, +// the function which handles those packets is NOP-ed out, which, among other +// things, skips the code which caches the referenced users in the packet. See +// . +export function attachToClient(client: Client) { + (client["actions"] as any)["PresenceUpdate"].handle = () => {}; + + (client as any).music = LavalinkMusic(client, { + lavalink: { + restnode: { + host: "localhost", + port: 2333, + password: "youshallnotpass" + }, + nodes: [ + { + host: "localhost", + port: 2333, + password: "youshallnotpass" + } + ] + }, + prefix: Config.prefix, + helpCmd: "mhelp", + admins: ["717352467280691331"] + }); +}