Reorganized lib/libd functions and Lavalink
This commit is contained in:
parent
86ccb74ac2
commit
3ef487c4a4
|
@ -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) {
|
||||
|
|
|
@ -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!`,
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
165
src/core/lib.ts
165
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<T>(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.
|
||||
|
|
164
src/core/libd.ts
164
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<T>(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)
|
||||
};
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
56
src/index.ts
56
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:
|
||||
// <https://github.com/discordjs/discord.js/blob/cee6cf70ce76e9b06dc7f25bfd77498e18d7c8d4/src/structures/Presence.js#L81-L110>.
|
||||
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
|
||||
// <https://github.com/discordjs/discord.js/blob/cee6cf70ce76e9b06dc7f25bfd77498e18d7c8d4/src/client/actions/PresenceUpdate.js#L7-L41>.
|
||||
(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(() => {
|
||||
|
|
|
@ -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:
|
||||
// <https://github.com/discordjs/discord.js/blob/cee6cf70ce76e9b06dc7f25bfd77498e18d7c8d4/src/structures/Presence.js#L81-L110>.
|
||||
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
|
||||
// <https://github.com/discordjs/discord.js/blob/cee6cf70ce76e9b06dc7f25bfd77498e18d7c8d4/src/client/actions/PresenceUpdate.js#L7-L41>.
|
||||
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"]
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue