Merge branch 'fleet'

This commit is contained in:
Essem 2021-07-08 21:21:54 -05:00
commit e725c4d1eb
No known key found for this signature in database
GPG Key ID: 7D497397CC3A2A8C
28 changed files with 851 additions and 544 deletions

View File

@ -1,6 +1,5 @@
require("dotenv").config();
const os = require("os");
//const { run } = require("../utils/image-runner.js");
const { Worker } = require("worker_threads");
const path = require("path");
const http = require("http");

108
app.js
View File

@ -6,14 +6,54 @@ The bot will continue to run past this message, but keep in mind that it could b
require("dotenv").config();
// main sharding manager
const { Master } = require("eris-sharder");
const { Fleet } = require("eris-fleet");
const { isMaster } = require("cluster");
// some utils
const path = require("path");
const winston = require("winston");
// dbl posting
const TopGG = require("@top-gg/sdk");
const dbl = process.env.NODE_ENV === "production" && process.env.DBL !== "" ? new TopGG.Api(process.env.DBL) : null;
const master = new Master(`Bot ${process.env.TOKEN}`, "/shard.js", {
name: "esmBot",
stats: true,
if (isMaster) {
console.log(`
,*\`$ z\`"v
F zBw\`% A ,W "W
,\` ,EBBBWp"%. ,-=~~==-,+* 4BBE T
M BBBBBBBB* ,w=####Wpw 4BBBBB# 1
F BBBBBBBMwBBBBBBBBBBBBB#wXBBBBBH E
F BBBBBBkBBBBBBBBBBBBBBBBBBBBE4BL k
# BFBBBBBBBBBBBBF" "RBBBW F
V ' 4BBBBBBBBBBM TBBL F
F BBBBBBBBBBF JBB L
F FBBBBBBBEB BBL 4
E [BB4BBBBEBL BBL 4
I #BBBBBBBEB 4BBH *w
A 4BBBBBBBBBEW, ,BBBB W [
.A ,k 4BBBBBBBBBBBEBW####BBBBBBM BF F
k <BBBw BBBBEBBBBBBBBBBBBBBBBBQ4BM #
5, REBBB4BBBBB#BBBBBBBBBBBBP5BFF ,F
*w \`*4BBW\`"FF#F##FFFF"\` , * +"
*+, " F'"'*^~~~^"^\` V+*^
\`"""
esmBot ${require("./package.json").version}, powered by eris-fleet ${require("./node_modules/eris-fleet/package.json").version}
`);
// a bit of a hacky way to get the eris-fleet version
}
const Admiral = new Fleet({
path: path.join(__dirname, "./shard.js"),
token: `Bot ${process.env.TOKEN}`,
startingStatus: {
status: "idle",
game: {
name: "Starting esmBot..."
}
},
whatToLog: {
blacklist: ["stats_update"]
},
clientOptions: {
disableEvents: {
CHANNEL_DELETE: true,
@ -45,16 +85,60 @@ const master = new Master(`Bot ${process.env.TOKEN}`, "/shard.js", {
stats: {
requestTimeout: 30000
}
}
},
services: [
{ name: "prometheus", path: path.join(__dirname, "./utils/services/prometheus.js") },
{ name: "image", path: path.join(__dirname, "./utils/services/image.js")}
]
});
master.on("stats", async (stats) => {
master.broadcast(0, Object.assign(stats, { _eventName: "stat" }));
// dbl posting
if (isMaster) {
const logger = winston.createLogger({
levels: {
error: 0,
warn: 1,
info: 2,
main: 3,
debug: 4
},
transports: [
new winston.transports.Console({ format: winston.format.colorize({ all: true }) }),
new winston.transports.File({ filename: "logs/error.log", level: "error" }),
new winston.transports.File({ filename: "logs/main.log" })
],
level: "main",
format: winston.format.combine(
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
winston.format.printf((info) => {
const {
timestamp, level, message, ...args
} = info;
return `[${timestamp}]: [${level.toUpperCase()}] - ${message} ${Object.keys(args).length ? JSON.stringify(args, null, 2) : ""}`;
}),
)
});
winston.addColors({
info: "green",
main: "gray",
debug: "purple",
warn: "yellow",
error: "red"
});
Admiral.on("log", (m) => logger.main(m));
Admiral.on("info", (m) => logger.info(m));
Admiral.on("debug", (m) => logger.debug(m));
Admiral.on("warn", (m) => logger.warn(m));
Admiral.on("error", (m) => logger.error(m));
if (dbl) {
await dbl.postStats({
serverCount: stats.guilds,
shardCount: await master.calculateShards()
Admiral.on("stats", async (m) => {
await dbl.postStats({
serverCount: m.guilds,
shardCount: m.shardCount
});
});
}
});
}

View File

@ -1,7 +1,8 @@
class Command {
constructor(client, cluster, ipc, message, args, content, specialArgs) {
constructor(client, cluster, worker, ipc, message, args, content, specialArgs) {
this.client = client;
this.cluster = cluster;
this.worker = worker;
this.ipc = ipc;
this.message = message;
this.args = args;

View File

@ -1,5 +1,4 @@
const Command = require("./command.js");
const magick = require("../utils/image.js");
const imageDetect = require("../utils/imagedetect.js");
const collections = require("../utils/collections.js");
const { emotes } = require("../messages.json");
@ -99,12 +98,12 @@ class ImageCommand extends Command {
}
try {
const { buffer, type } = await magick.run(magickParams).catch(e => {
const { buffer, type } = await this.ipc.command("image", { type: "run", obj: magickParams }, true).catch(e => {
throw e;
});
if (type === "nogif" && this.constructor.requiresGIF) return "That isn't a GIF!";
return {
file: buffer,
file: Buffer.from(buffer.data),
name: `${this.constructor.command}.${type}`
};
} catch (e) {

View File

@ -2,8 +2,8 @@ const Command = require("./command.js");
const soundPlayer = require("../utils/soundplayer.js");
class MusicCommand extends Command {
constructor(client, cluster, ipc, message, args, content, specialArgs) {
super(client, cluster, ipc, message, args, content, specialArgs);
constructor(client, cluster, worker, ipc, message, args, content, specialArgs) {
super(client, cluster, worker, ipc, message, args, content, specialArgs);
this.connection = soundPlayer.players.get(message.channel.guild.id);
}

View File

@ -1,21 +1,9 @@
const image = require("../../utils/image.js");
const logger = require("../../utils/logger.js");
const Command = require("../../classes/command.js");
class ImageReloadCommand extends Command {
async run() {
if (this.message.author.id !== process.env.OWNER) return "Only the bot owner can reload the image servers!";
await image.disconnect();
await image.repopulate();
let amount = 0;
for (const server of image.servers) {
try {
await image.connect(server);
amount += 1;
} catch (e) {
logger.error(e);
}
}
const amount = await this.ipc.command("image", { type: "reload" }, true);
if (amount > 0) {
return `Successfully connected to ${amount} image servers.`;
} else {

View File

@ -1,8 +1,8 @@
const image = require("../../utils/image.js");
const Command = require("../../classes/command.js");
class ImageStatsCommand extends Command {
async run() {
const servers = await this.ipc.command("image", { type: "stats" }, true);
const embed = {
embed: {
"author": {
@ -10,11 +10,10 @@ class ImageStatsCommand extends Command {
"icon_url": this.client.user.avatarURL
},
"color": 16711680,
"description": `The bot is currently connected to ${image.connections.size} image server(s).`,
"description": `The bot is currently connected to ${servers.length} image server(s).`,
"fields": []
}
};
const servers = await image.getRunning();
for (let i = 0; i < servers.length; i++) {
embed.embed.fields.push({
name: `Server ${i + 1}`,

View File

@ -1,10 +1,10 @@
const { version } = require("../../package.json");
const collections = require("../../utils/collections.js");
const Command = require("../../classes/command.js");
class InfoCommand extends Command {
async run() {
const owner = await this.ipc.fetchUser(process.env.OWNER);
const stats = await this.ipc.getStats();
return {
"embed": {
"color": 16711680,
@ -23,7 +23,7 @@ class InfoCommand extends Command {
},
{
"name": "💬 Total Servers:",
"value": collections.stats.guilds ? collections.stats.guilds : `${this.client.guilds.size} (for this cluster only)`
"value": stats.guilds ? stats.guilds : `${this.client.guilds.size} (for this cluster only)`
},
{
"name": "✅ Official Server:",

View File

@ -6,7 +6,7 @@ class ReloadCommand extends Command {
return new Promise((resolve) => {
if (this.message.author.id !== process.env.OWNER) resolve("Only the bot owner can reload commands!");
if (this.args.length === 0) resolve("You need to provide a command to reload!");
this.ipc.broadcast("reload", { cmd: this.args[0] });
this.ipc.broadcast("reload", this.args[0]);
this.ipc.register("reloadSuccess", () => {
this.ipc.unregister("reloadSuccess");
this.ipc.unregister("reloadFail");

View File

@ -11,7 +11,8 @@ class RestartCommand extends Command {
for (const command of collections.commands) {
await handler.unload(command);
}
this.ipc.broadcast("restart");
this.ipc.restartAllClusters();
//this.ipc.broadcast("restart");
}
static description = "Restarts me";

View File

@ -10,7 +10,7 @@ class SoundReloadCommand extends Command {
this.ipc.register("soundReloadSuccess", (msg) => {
this.ipc.unregister("soundReloadSuccess");
this.ipc.unregister("soundReloadFail");
resolve(`Successfully connected to ${msg.length} Lavalink node(s).`);
resolve(`Successfully connected to ${msg.msg.length} Lavalink node(s).`);
});
this.ipc.register("soundReloadFail", () => {
this.ipc.unregister("soundReloadSuccess");

View File

@ -1,5 +1,4 @@
const { version } = require("../../package.json");
const collections = require("../../utils/collections.js");
const day = require("dayjs");
day.extend(require("dayjs/plugin/duration"));
const os = require("os");
@ -10,6 +9,7 @@ class StatsCommand extends Command {
const duration = day.duration(this.client.uptime).format(" D [days], H [hrs], m [mins], s [secs]");
const uptime = day.duration(process.uptime(), "seconds").format(" D [days], H [hrs], m [mins], s [secs]");
const owner = await this.ipc.fetchUser(process.env.OWNER);
const stats = await this.ipc.getStats();
return {
embed: {
"author": {
@ -24,12 +24,12 @@ class StatsCommand extends Command {
},
{
"name": "Cluster Memory Usage",
"value": `${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)} MB`,
"value": `${stats.clusters[this.cluster].ram.toFixed(2)} MB`,
"inline": true
},
{
"name": "Total Memory Usage",
"value": collections.stats.totalRam ? `${collections.stats.totalRam.toFixed(2)} MB` : "Unknown",
"value": stats.totalRam ? `${stats.totalRam.toFixed(2)} MB` : "Unknown",
"inline": true
},
{
@ -66,12 +66,12 @@ class StatsCommand extends Command {
},
{
"name": "Servers",
"value": collections.stats.guilds ? collections.stats.guilds : `${this.client.guilds.size} (for this cluster only)`,
"value": stats.guilds ? stats.guilds : `${this.client.guilds.size} (for this cluster only)`,
"inline": true
},
{
"name": "Users (approximation)",
"value": collections.stats.users ? collections.stats.users : `${this.client.users.size} (for this cluster only)`,
"value": stats.users ? stats.users : `${this.client.users.size} (for this cluster only)`,
"inline": true
}
]

View File

@ -2,7 +2,7 @@ const db = require("../utils/database.js");
const logger = require("../utils/logger.js");
// run when the bot is added to a guild
module.exports = async (client, cluster, ipc, guild) => {
logger.log("info", `[GUILD JOIN] ${guild.name} (${guild.id}) added the bot.`);
module.exports = async (client, cluster, worker, ipc, guild) => {
logger.log(`[GUILD JOIN] ${guild.name} (${guild.id}) added the bot.`);
await db.addGuild(guild);
};

View File

@ -1,6 +1,6 @@
const logger = require("../utils/logger.js");
// run when the bot is removed from a guild
module.exports = async (client, cluster, ipc, guild) => {
module.exports = async (client, cluster, worker, ipc, guild) => {
logger.log(`[GUILD LEAVE] ${guild.name} (${guild.id}) removed the bot.`);
};

View File

@ -5,7 +5,7 @@ const collections = require("../utils/collections.js");
const parseCommand = require("../utils/parseCommand.js");
// run when someone sends a message
module.exports = async (client, cluster, ipc, message) => {
module.exports = async (client, cluster, worker, ipc, message) => {
// ignore dms and other bots
if (message.author.bot) return;
@ -88,7 +88,7 @@ module.exports = async (client, cluster, ipc, message) => {
await database.addCount(collections.aliases.has(command) ? collections.aliases.get(command) : command);
const startTime = new Date();
// eslint-disable-next-line no-unused-vars
const commandClass = new cmd(client, cluster, ipc, message, parsed._, message.content.substring(prefix.length).trim().replace(command, "").trim(), (({ _, ...o }) => o)(parsed)); // we also provide the message content as a parameter for cases where we need more accuracy
const commandClass = new cmd(client, cluster, worker, ipc, message, parsed._, message.content.substring(prefix.length).trim().replace(command, "").trim(), (({ _, ...o }) => o)(parsed)); // we also provide the message content as a parameter for cases where we need more accuracy
const result = await commandClass.run();
const endTime = new Date();
if ((endTime - startTime) >= 180000) reference.allowedMentions.repliedUser = true;

View File

@ -1,7 +1,7 @@
const player = require("../utils/soundplayer.js");
// run when a raw packet is sent, used for sending data to lavalink
module.exports = async (client, cluster, ipc, packet) => {
module.exports = async (client, cluster, worker, ipc, packet) => {
if (!player.manager) return;
switch (packet.t) {
case "VOICE_SERVER_UPDATE":

View File

@ -2,7 +2,8 @@ const soundPlayer = require("../utils/soundplayer.js");
const AwaitRejoin = require("../utils/awaitrejoin.js");
const { random } = require("../utils/misc.js");
module.exports = async (client, cluster, ipc, member, oldChannel) => {
module.exports = async (client, cluster, worker, ipc, member, oldChannel) => {
if (!oldChannel) return;
const connection = soundPlayer.players.get(oldChannel.guild.id);
if (connection && connection.type === "music" && oldChannel.id === connection.voiceChannel.id) {
if (oldChannel.voiceMembers.filter((i) => i.id !== client.user.id).length === 0) {

View File

@ -1,5 +1,5 @@
const leaveHandler = require("./voiceChannelLeave.js");
module.exports = async (client, cluster, ipc, member, newChannel, oldChannel) => {
module.exports = async (client, cluster, worker, ipc, member, newChannel, oldChannel) => {
await leaveHandler(client, cluster, ipc, member, oldChannel);
};

431
package-lock.json generated
View File

@ -14,7 +14,8 @@
"dayjs": "^1.10.4",
"dotenv": "^9.0.2",
"emoji-regex": "^9.2.2",
"eris-sharder": "github:esmBot/eris-sharder#eris-dev",
"eris": "^0.15.1",
"eris-fleet": "github:esmBot/eris-fleet",
"file-type": "^16.1.0",
"jsqr": "^1.3.1",
"lavacord": "^1.1.9",
@ -22,7 +23,8 @@
"node-emoji": "^1.10.0",
"node-fetch": "^2.6.1",
"qrcode": "^1.4.4",
"sharp": "^0.28.2"
"sharp": "^0.28.2",
"winston": "^3.3.3"
},
"devDependencies": {
"@babel/eslint-parser": "^7.13.8",
@ -463,6 +465,16 @@
"node": ">=6.9.0"
}
},
"node_modules/@dabh/diagnostics": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz",
"integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==",
"dependencies": {
"colorspace": "1.1.x",
"enabled": "2.0.x",
"kuler": "^2.0.0"
}
},
"node_modules/@eslint/eslintrc": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz",
@ -596,17 +608,6 @@
"node": ">=6"
}
},
"node_modules/ansi-gray": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz",
"integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=",
"dependencies": {
"ansi-wrap": "0.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
@ -626,14 +627,6 @@
"node": ">=4"
}
},
"node_modules/ansi-wrap": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz",
"integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/aproba": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
@ -666,6 +659,11 @@
"node": ">=8"
}
},
"node_modules/async": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz",
"integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw=="
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -990,14 +988,6 @@
"simple-swizzle": "^0.2.2"
}
},
"node_modules/color-support": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
"bin": {
"color-support": "bin.js"
}
},
"node_modules/colorette": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
@ -1013,6 +1003,24 @@
"node": ">=0.1.90"
}
},
"node_modules/colorspace": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz",
"integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==",
"dependencies": {
"color": "3.0.x",
"text-hex": "1.0.x"
}
},
"node_modules/colorspace/node_modules/color": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz",
"integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==",
"dependencies": {
"color-convert": "^1.9.1",
"color-string": "^1.5.2"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -1192,6 +1200,11 @@
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
},
"node_modules/enabled": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
},
"node_modules/end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@ -1213,9 +1226,9 @@
}
},
"node_modules/eris": {
"version": "0.15.2-dev",
"resolved": "git+ssh://git@github.com/abalabahaha/eris.git#ea9d6700c8e3fedf5d966da4babe195c8cd681da",
"license": "MIT",
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/eris/-/eris-0.15.1.tgz",
"integrity": "sha512-IQ3BPW6OjgFoqjdh+irPOa1jFlkotk+WNu2GQQ7QAQfbzQEPZgn+F+hpOxfMUXPHOZMX4sPKLkVDkMHAssBYhw==",
"dependencies": {
"ws": "^7.2.1"
},
@ -1227,17 +1240,12 @@
"tweetnacl": "^1.0.1"
}
},
"node_modules/eris-sharder": {
"version": "1.10.0",
"resolved": "git+ssh://git@github.com/esmBot/eris-sharder.git#3b7366e5d99012ca0e3350e4d81f384b48911c2d",
"node_modules/eris-fleet": {
"version": "0.3.7",
"resolved": "git+ssh://git@github.com/esmBot/eris-fleet.git#7cf997d73f24c3ba3d17df978f0b672adaa021dd",
"license": "MIT",
"dependencies": {
"colors": "^1.1.2",
"eris": "github:abalabahaha/eris#dev",
"fancy-log": "^1.3.0"
},
"engines": {
"node": ">=8.0.0"
"peerDependencies": {
"eris": "^0.15.0"
}
},
"node_modules/erlpack": {
@ -1606,20 +1614,6 @@
"node": ">=6"
}
},
"node_modules/fancy-log": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz",
"integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==",
"dependencies": {
"ansi-gray": "^0.1.1",
"color-support": "^1.1.3",
"parse-node-version": "^1.0.0",
"time-stamp": "^1.0.0"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -1638,6 +1632,16 @@
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true
},
"node_modules/fast-safe-stringify": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz",
"integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA=="
},
"node_modules/fecha": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.1.tgz",
"integrity": "sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q=="
},
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@ -1702,6 +1706,11 @@
"integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==",
"dev": true
},
"node_modules/fn.name": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
},
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@ -2000,6 +2009,14 @@
"node": ">=0.10.0"
}
},
"node_modules/is-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
"integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==",
"engines": {
"node": ">=8"
}
},
"node_modules/isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
@ -2081,6 +2098,11 @@
"integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==",
"optional": true
},
"node_modules/kuler": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="
},
"node_modules/lavacord": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/lavacord/-/lavacord-1.1.9.tgz",
@ -2142,6 +2164,18 @@
"integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=",
"dev": true
},
"node_modules/logform": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz",
"integrity": "sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==",
"dependencies": {
"colors": "^1.2.1",
"fast-safe-stringify": "^2.0.4",
"fecha": "^4.2.0",
"ms": "^2.1.1",
"triple-beam": "^1.3.0"
}
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@ -2367,8 +2401,7 @@
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"devOptional": true
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/nan": {
"version": "2.14.2",
@ -2477,6 +2510,14 @@
"wrappy": "1"
}
},
"node_modules/one-time": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
"integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
"dependencies": {
"fn.name": "1.x.x"
}
},
"node_modules/optional-require": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz",
@ -2560,14 +2601,6 @@
"node": ">=6"
}
},
"node_modules/parse-node-version": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz",
"integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
@ -3218,6 +3251,14 @@
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
"node_modules/stack-trace": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
"integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=",
"engines": {
"node": "*"
}
},
"node_modules/statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
@ -3414,20 +3455,17 @@
"node": ">= 6"
}
},
"node_modules/text-hex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="
},
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
"node_modules/time-stamp": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz",
"integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@ -3461,6 +3499,11 @@
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/triple-beam": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz",
"integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw=="
},
"node_modules/tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@ -3606,6 +3649,50 @@
"node": ">=4"
}
},
"node_modules/winston": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz",
"integrity": "sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==",
"dependencies": {
"@dabh/diagnostics": "^2.0.2",
"async": "^3.1.0",
"is-stream": "^2.0.0",
"logform": "^2.2.0",
"one-time": "^1.0.0",
"readable-stream": "^3.4.0",
"stack-trace": "0.0.x",
"triple-beam": "^1.3.0",
"winston-transport": "^4.4.0"
},
"engines": {
"node": ">= 6.4.0"
}
},
"node_modules/winston-transport": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz",
"integrity": "sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==",
"dependencies": {
"readable-stream": "^2.3.7",
"triple-beam": "^1.2.0"
},
"engines": {
"node": ">= 6.4.0"
}
},
"node_modules/winston/node_modules/readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
@ -4108,6 +4195,16 @@
"to-fast-properties": "^2.0.0"
}
},
"@dabh/diagnostics": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz",
"integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==",
"requires": {
"colorspace": "1.1.x",
"enabled": "2.0.x",
"kuler": "^2.0.0"
}
},
"@eslint/eslintrc": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz",
@ -4219,14 +4316,6 @@
"integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
"dev": true
},
"ansi-gray": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz",
"integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=",
"requires": {
"ansi-wrap": "0.1.0"
}
},
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
@ -4240,11 +4329,6 @@
"color-convert": "^1.9.0"
}
},
"ansi-wrap": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz",
"integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768="
},
"aproba": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
@ -4274,6 +4358,11 @@
"integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
"dev": true
},
"async": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz",
"integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw=="
},
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -4520,11 +4609,6 @@
"simple-swizzle": "^0.2.2"
}
},
"color-support": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg=="
},
"colorette": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
@ -4537,6 +4621,26 @@
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="
},
"colorspace": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz",
"integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==",
"requires": {
"color": "3.0.x",
"text-hex": "1.0.x"
},
"dependencies": {
"color": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz",
"integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==",
"requires": {
"color-convert": "^1.9.1",
"color-string": "^1.5.2"
}
}
}
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -4672,6 +4776,11 @@
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
},
"enabled": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
},
"end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@ -4690,22 +4799,19 @@
}
},
"eris": {
"version": "git+ssh://git@github.com/abalabahaha/eris.git#ea9d6700c8e3fedf5d966da4babe195c8cd681da",
"from": "eris@github:abalabahaha/eris#dev",
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/eris/-/eris-0.15.1.tgz",
"integrity": "sha512-IQ3BPW6OjgFoqjdh+irPOa1jFlkotk+WNu2GQQ7QAQfbzQEPZgn+F+hpOxfMUXPHOZMX4sPKLkVDkMHAssBYhw==",
"requires": {
"opusscript": "^0.0.8",
"tweetnacl": "^1.0.1",
"ws": "^7.2.1"
}
},
"eris-sharder": {
"version": "git+ssh://git@github.com/esmBot/eris-sharder.git#3b7366e5d99012ca0e3350e4d81f384b48911c2d",
"from": "eris-sharder@github:esmBot/eris-sharder#eris-dev",
"requires": {
"colors": "^1.1.2",
"eris": "github:abalabahaha/eris#dev",
"fancy-log": "^1.3.0"
}
"eris-fleet": {
"version": "git+ssh://git@github.com/esmBot/eris-fleet.git#7cf997d73f24c3ba3d17df978f0b672adaa021dd",
"from": "eris-fleet@github:esmBot/eris-fleet",
"requires": {}
},
"erlpack": {
"version": "git+ssh://git@github.com/abalabahaha/erlpack.git#5d0064f9e106841e1eead711a6451f99b0d289fd",
@ -4975,17 +5081,6 @@
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="
},
"fancy-log": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz",
"integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==",
"requires": {
"ansi-gray": "^0.1.1",
"color-support": "^1.1.3",
"parse-node-version": "^1.0.0",
"time-stamp": "^1.0.0"
}
},
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -5004,6 +5099,16 @@
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true
},
"fast-safe-stringify": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz",
"integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA=="
},
"fecha": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.1.tgz",
"integrity": "sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q=="
},
"file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@ -5053,6 +5158,11 @@
"integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==",
"dev": true
},
"fn.name": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
},
"fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@ -5276,6 +5386,11 @@
"is-extglob": "^2.1.1"
}
},
"is-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
"integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw=="
},
"isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
@ -5342,6 +5457,11 @@
"integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==",
"optional": true
},
"kuler": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="
},
"lavacord": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/lavacord/-/lavacord-1.1.9.tgz",
@ -5393,6 +5513,18 @@
"integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=",
"dev": true
},
"logform": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz",
"integrity": "sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==",
"requires": {
"colors": "^1.2.1",
"fast-safe-stringify": "^2.0.4",
"fecha": "^4.2.0",
"ms": "^2.1.1",
"triple-beam": "^1.3.0"
}
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@ -5544,8 +5676,7 @@
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"devOptional": true
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"nan": {
"version": "2.14.2",
@ -5639,6 +5770,14 @@
"wrappy": "1"
}
},
"one-time": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
"integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
"requires": {
"fn.name": "1.x.x"
}
},
"optional-require": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz",
@ -5701,11 +5840,6 @@
"callsites": "^3.0.0"
}
},
"parse-node-version": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz",
"integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA=="
},
"path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
@ -6197,6 +6331,11 @@
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
"stack-trace": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
"integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA="
},
"statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
@ -6360,17 +6499,17 @@
}
}
},
"text-hex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="
},
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
"time-stamp": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz",
"integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM="
},
"to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@ -6391,6 +6530,11 @@
"ieee754": "^1.2.1"
}
},
"triple-beam": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz",
"integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw=="
},
"tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@ -6502,6 +6646,43 @@
}
}
},
"winston": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz",
"integrity": "sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==",
"requires": {
"@dabh/diagnostics": "^2.0.2",
"async": "^3.1.0",
"is-stream": "^2.0.0",
"logform": "^2.2.0",
"one-time": "^1.0.0",
"readable-stream": "^3.4.0",
"stack-trace": "0.0.x",
"triple-beam": "^1.3.0",
"winston-transport": "^4.4.0"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"winston-transport": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz",
"integrity": "sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==",
"requires": {
"readable-stream": "^2.3.7",
"triple-beam": "^1.2.0"
}
},
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",

View File

@ -29,7 +29,8 @@
"dayjs": "^1.10.4",
"dotenv": "^9.0.2",
"emoji-regex": "^9.2.2",
"eris-sharder": "github:esmBot/eris-sharder#eris-dev",
"eris": "^0.15.1",
"eris-fleet": "github:esmBot/eris-fleet",
"file-type": "^16.1.0",
"jsqr": "^1.3.1",
"lavacord": "^1.1.9",
@ -37,7 +38,8 @@
"node-emoji": "^1.10.0",
"node-fetch": "^2.6.1",
"qrcode": "^1.4.4",
"sharp": "^0.28.2"
"sharp": "^0.28.2",
"winston": "^3.3.3"
},
"devDependencies": {
"@babel/eslint-parser": "^7.13.8",

159
shard.js
View File

@ -1,5 +1,5 @@
// shard base
const { Base } = require("eris-sharder");
const { BaseClusterWorker } = require("eris-fleet");
// path stuff
const { readdir } = require("fs").promises;
// fancy loggings
@ -8,8 +8,6 @@ const logger = require("./utils/logger.js");
const handler = require("./utils/handler.js");
// lavalink stuff
const sound = require("./utils/soundplayer.js");
// image processing stuff
const image = require("./utils/image.js");
// database stuff
const database = require("./utils/database.js");
// command collections
@ -21,11 +19,12 @@ const misc = require("./utils/misc.js");
// generate help page
const helpGenerator =
process.env.OUTPUT !== "" ? require("./utils/help.js") : null;
const http = require("http");
class Shard extends Base {
class Shard extends BaseClusterWorker {
constructor(bot) {
super(bot);
this.init();
}
async init() {
@ -48,18 +47,7 @@ class Shard extends Base {
logger.log("log", `Loading event from ${file}...`);
const eventName = file.split(".")[0];
const event = require(`./events/${file}`);
this.bot.on(eventName, event.bind(null, this.bot, this.clusterID, this.ipc));
}
// connect to image api if enabled
if (process.env.API === "true") {
for (const server of image.servers) {
try {
await image.connect(server);
} catch (e) {
logger.error(e);
}
}
this.bot.on(eventName, event.bind(null, this.bot, this.clusterID, this.workerID, this.ipc));
}
// generate docs
@ -71,55 +59,40 @@ class Shard extends Base {
}
}
if (process.env.METRICS !== "" && process.env.METRICS !== undefined) {
const httpServer = http.createServer(async (req, res) => {
if (req.method !== "GET") {
res.statusCode = 405;
return res.end("GET only");
}
res.write(`# HELP connected_workers Number of workers connected
# TYPE connected_workers gauge
connected_workers ${image.connections.size}
# HELP running_jobs Number of running jobs on this worker
# TYPE running_jobs gauge
# HELP queued_jobs Number of queued jobs on this worker
# TYPE queued_jobs gauge
# HELP max_jobs Number of max allowed jobs on this worker
# TYPE max_jobs gauge
# HELP command_count Number of times a command has been run
# TYPE command_count counter
`);
const servers = await image.getRunning();
for (const [i, w] of servers.entries()) {
res.write(`running_jobs{worker="${i}"} ${w.runningJobs}\n`);
res.write(`queued_jobs{worker="${i}"} ${w.queued}\n`);
res.write(`max_jobs{worker="${i}"} ${w.max}\n`);
}
const counts = await database.getCounts();
for (const [i, w] of Object.entries(counts)) {
res.write(`command_count{command="${i}"} ${w}\n`);
}
res.end();
});
httpServer.listen(process.env.METRICS, () => {
logger.log("info", `Serving metrics at ${process.env.METRICS}`);
});
}
// handle process stop
process.on("SIGINT", () => {
logger.log("warn", "SIGINT detected, shutting down...");
this.bot.editStatus("dnd", {
name: "Restarting/shutting down..."
});
for (const command in collections.commands) {
handler.unload(command);
}
this.bot.disconnect();
require("./utils/database.js").stop();
process.exit(0);
this.ipc.register("reload", async (message) => {
const result = await handler.unload(message.msg);
if (result) return this.ipc.broadcast("reloadFail", { result: result });
const result2 = await handler.load(collections.paths.get(message.msg));
if (result2) return this.ipc.broadcast("reloadFail", { result: result2 });
return this.ipc.broadcast("reloadSuccess");
});
return;
this.bot.privateChannels.limit = 0;
this.ipc.register("soundreload", async () => {
const soundStatus = await sound.checkStatus();
if (!soundStatus) {
const length = await sound.connect(this.bot);
return this.ipc.broadcast("soundReloadSuccess", { length });
} else {
return this.ipc.broadcast("soundReloadFail");
}
});
// connect to lavalink
if (!sound.status && !sound.connected) sound.connect(this.bot);
database.setup();
// set activity (a.k.a. the gamer code)
(async function activityChanger() {
this.bot.editStatus("dnd", {
name: `${misc.random(messages)} | @${this.bot.user.username} help`,
});
setTimeout(activityChanger.bind(this), 900000);
}).bind(this)();
logger.log("info", `Started worker ${this.workerID}.`);
}
async* getFiles(dir) {
@ -133,54 +106,16 @@ connected_workers ${image.connections.size}
}
}
async launch() {
await this.init();
this.ipc.register("stat", (message) => {
collections.stats = message;
shutdown(done) {
logger.log("warn", "Shutting down...");
this.bot.editStatus("dnd", {
name: "Restarting/shutting down..."
});
this.ipc.register("restart", async () => {
this.bot.editStatus("dnd", {
name: "esmBot is restarting, please stand by."
});
process.exit(1);
});
this.ipc.register("reload", async (message) => {
const result = await handler.unload(message.cmd);
if (result) return this.ipc.broadcast("reloadFail", { result: result });
const result2 = await handler.load(collections.paths.get(message.cmd));
if (result2) return this.ipc.broadcast("reloadFail", { result: result2 });
return this.ipc.broadcast("reloadSuccess");
});
this.ipc.register("soundreload", async () => {
const soundStatus = await sound.checkStatus();
if (!soundStatus) {
const length = await sound.connect(this.bot);
return this.ipc.broadcast("soundReloadSuccess", { length });
} else {
return this.ipc.broadcast("soundReloadFail");
}
});
// connect to lavalink
if (!sound.status && !sound.connected) await sound.connect(this.bot);
this.bot.privateChannels.limit = 0;
await database.setup();
// set activity (a.k.a. the gamer code)
(async function activityChanger() {
this.bot.editStatus("dnd", {
name: `${misc.random(messages)} | @${this.bot.user.username} help`,
});
setTimeout(activityChanger.bind(this), 900000);
}).bind(this)();
logger.log("info", `Started cluster ${this.clusterID}.`);
for (const command in collections.commands) {
handler.unload(command);
}
require("./utils/database.js").stop();
done();
}
}

View File

@ -27,6 +27,4 @@ class Cache extends Map {
}
exports.prefixCache = new Cache();
exports.disabledCache = new Cache();
exports.stats = {};
exports.disabledCache = new Cache();

View File

@ -1,12 +1,6 @@
const magick = require("../build/Release/image.node");
const { Worker } = require("worker_threads");
const fetch = require("node-fetch");
const fs = require("fs");
const WebSocket = require("ws");
const fileType = require("file-type");
const path = require("path");
const { EventEmitter } = require("events");
const logger = require("./logger.js");
const formats = ["image/jpeg", "image/png", "image/webp", "image/gif", "video/mp4", "video/webm", "video/mov"];
@ -16,183 +10,6 @@ exports.connections = new Map();
exports.servers = JSON.parse(fs.readFileSync("./servers.json", { encoding: "utf8" })).image;
const chooseServer = async (ideal) => {
if (ideal.length === 0) throw "No available servers";
const sorted = ideal.sort((a, b) => {
return b.load - a.load;
});
return sorted[0];
};
exports.repopulate = async () => {
const data = await fs.promises.readFile("./servers.json", { encoding: "utf8" });
this.servers = JSON.parse(data).image;
return;
};
exports.getRunning = async () => {
let serversLeft = this.connections.size;
const statuses = [];
for (const address of this.connections.keys()) {
const connection = this.connections.get(address);
if (connection.readyState !== 0 && connection.readyState !== 1) {
serversLeft--;
continue;
}
const controller = new AbortController(); // eslint-disable-line no-undef
const timeout = setTimeout(() => {
controller.abort();
}, 2000);
try {
const statusRequest = await fetch(`http://${address}:8080/running`, { signal: controller.signal });
clearTimeout(timeout);
const status = await statusRequest.json();
serversLeft--;
statuses.push(status);
} catch (e) {
if (e.name === "AbortError") {
serversLeft--;
continue;
} else if (e.code === "ECONNREFUSED") {
serversLeft--;
continue;
}
throw e;
}
}
if (!serversLeft) {
return statuses;
} else {
throw new Error("Loop ended before all servers could be checked");
}
};
exports.connect = async (server) => {
const connection = new WebSocket(`ws://${server}:8080/sock`);
connection.on("message", async (msg) => {
const opcode = msg.readUint8(0);
const req = msg.slice(37, msg.length);
const uuid = msg.slice(1, 37).toString();
if (opcode === 0x00) { // Job queued
if (this.jobs[req]) {
this.jobs[req].event.emit("uuid", uuid);
}
} else if (opcode === 0x01) { // Job completed successfully
// the image API sends all job responses over the same socket; make sure this is ours
if (this.jobs[uuid]) {
const imageReq = await fetch(`http://${server}:8080/image?id=${uuid}`);
const image = await imageReq.buffer();
// The response data is given as the file extension/ImageMagick type of the image (e.g. "png"), followed
// by a newline, followed by the image data.
this.jobs[uuid].event.emit("image", image, imageReq.headers.get("ext"));
}
} else if (opcode === 0x02) { // Job errored
if (this.jobs[uuid]) {
this.jobs[uuid].event.emit("error", new Error(req));
}
}
});
connection.on("error", (e) => {
logger.error(e.toString());
});
connection.once("close", () => {
for (const uuid of Object.keys(this.jobs)) {
if (this.jobs[uuid].addr === server) {
this.jobs[uuid].event.emit("error", "Job ended prematurely due to a closed connection; please run your image job again");
delete this.jobs[uuid];
}
}
//logger.log(`Lost connection to ${server}, attempting to reconnect...`);
this.connections.delete(server);
});
this.connections.set(server, connection);
};
exports.disconnect = async () => {
for (const connection of this.connections.values()) {
connection.close();
}
for (const uuid of Object.keys(this.jobs)) {
this.jobs[uuid].event.emit("error", "Job ended prematurely (not really an error; just run your image job again)");
delete this.jobs[uuid];
}
this.connections.clear();
return;
};
const getIdeal = async () => {
let serversLeft = this.connections.size;
if (serversLeft < this.servers.length) {
for (const server of this.servers) {
try {
if (!this.connections.has(server)) await this.connect(server);
} catch (e) {
logger.error(e);
}
}
serversLeft = this.connections.size;
}
const idealServers = [];
for (const address of this.connections.keys()) {
const connection = this.connections.get(address);
if (connection.readyState !== 0 && connection.readyState !== 1) {
serversLeft--;
continue;
}
const controller = new AbortController(); // eslint-disable-line no-undef
const timeout = setTimeout(() => {
controller.abort();
}, 5000);
try {
const statusRequest = await fetch(`http://${address}:8080/status`, { signal: controller.signal });
clearTimeout(timeout);
const status = await statusRequest.text();
serversLeft--;
idealServers.push({
addr: address,
load: parseInt(status)
});
} catch (e) {
if (e.name === "AbortError") {
serversLeft--;
continue;
} else if (e.code === "ECONNREFUSED") {
serversLeft--;
continue;
}
throw e;
} finally {
clearTimeout(timeout);
}
}
if (!serversLeft) {
const server = await chooseServer(idealServers);
return { addr: server.addr, sock: this.connections.get(server.addr) };
} else {
throw new Error("Loop ended before all servers could be checked");
}
};
const start = async (object, num) => {
const currentServer = await getIdeal();
const data = Buffer.concat([Buffer.from([0x01 /* queue job */]), Buffer.from(num.length.toString()), Buffer.from(num), Buffer.from(JSON.stringify(object))]);
currentServer.sock.send(data);
const event = new EventEmitter();
this.jobs[num] = { event, addr: currentServer.addr };
const uuid = await new Promise((resolve, reject) => {
event.once("uuid", (uuid) => resolve(uuid));
event.once("error", reject);
});
delete this.jobs[num];
this.jobs[uuid] = { event: event, addr: currentServer.addr };
return { uuid: uuid, event: event };
};
exports.check = (cmd) => {
return magick[cmd] ? true : false;
};
exports.getType = async (image, extraReturnTypes) => {
if (!image.startsWith("http")) {
const imageType = await fileType.fromFile(image);
@ -234,50 +51,3 @@ exports.getType = async (image, extraReturnTypes) => {
}
return type;
};
exports.run = (object) => {
return new Promise((resolve, reject) => {
if (process.env.API === "true") {
// Connect to best image server
const num = Math.floor(Math.random() * 100000).toString().slice(0, 5);
const timeout = setTimeout(() => {
if (this.jobs[num]) delete this.jobs[num];
reject("the image request timed out after 25 seconds. Try uploading your image elsewhere.");
}, 25000);
start(object, num).catch(err => { // incredibly hacky code incoming
clearTimeout(timeout);
if (err instanceof Error) return reject(err);
return err;
}).then((data) => {
clearTimeout(timeout);
if (!data.event) reject("Not connected to image server");
data.event.once("image", (image, type) => {
delete this.jobs[data.uuid];
const payload = {
// Take just the image data
buffer: image,
type: type
};
resolve(payload);
});
data.event.once("error", (err) => {
delete this.jobs[data.uuid];
reject(err);
});
return;
}).catch(err => reject(err));
} else {
// Called from command (not using image API)
const worker = new Worker(path.join(__dirname, "image-runner.js"), {
workerData: object
});
worker.once("message", (data) => {
resolve({
buffer: Buffer.from([...data.buffer]),
type: data.fileExtension
});
});
worker.once("error", reject);
}
});
};

View File

@ -1,4 +1,4 @@
exports.log = (type, content) => content ? process.send({ name: type, msg: content }) : process.send({ name: "info", msg: type });
exports.log = (type, content) => content ? process.send({ op: type, msg: content }) : process.send({ op: "info", msg: type });
exports.error = (...args) => this.log("error", ...args);

View File

@ -39,16 +39,6 @@ module.exports = (input) => {
} else {
args[curr] += `${a} `;
}
} else if (a.startsWith("\"")) {
if (a.endsWith("\"")) {
args.push(a.slice(1).slice(0, -1));
} else {
concated += `${a.slice(1)} `;
}
} else if (a.endsWith("\"")) {
concated += a.slice(0, -1);
args._.push(concated);
concated = "";
} else {
if (concated !== "") {
concated += `${a} `;

286
utils/services/image.js Normal file
View File

@ -0,0 +1,286 @@
const { BaseServiceWorker } = require("eris-fleet");
const logger = require("../logger.js");
const fetch = require("node-fetch");
const WebSocket = require("ws");
const fs = require("fs");
const path = require("path");
const { Worker } = require("worker_threads");
const { EventEmitter } = require("events");
class ImageWorker extends BaseServiceWorker {
constructor(setup) {
super(setup);
if (process.env.API === "true") {
this.jobs = {};
this.connections = new Map();
this.servers = JSON.parse(fs.readFileSync("./servers.json", { encoding: "utf8" })).image;
}
this.begin().then(() => this.serviceReady());
}
async begin() {
// connect to image api if enabled
if (process.env.API === "true") {
for (const server of this.servers) {
try {
await this.connect(server);
} catch (e) {
logger.error(e);
}
}
}
}
async repopulate() {
const data = await fs.promises.readFile("./servers.json", { encoding: "utf8" });
this.servers = JSON.parse(data).image;
return;
}
async getRunning() {
let serversLeft = this.connections.size;
const statuses = [];
for (const address of this.connections.keys()) {
const connection = this.connections.get(address);
if (connection.readyState !== 0 && connection.readyState !== 1) {
serversLeft--;
continue;
}
const controller = new AbortController(); // eslint-disable-line no-undef
const timeout = setTimeout(() => {
controller.abort();
}, 2000);
try {
const statusRequest = await fetch(`http://${address}:8080/running`, { signal: controller.signal });
clearTimeout(timeout);
const status = await statusRequest.json();
serversLeft--;
statuses.push(status);
} catch (e) {
if (e.name === "AbortError") {
serversLeft--;
continue;
} else if (e.code === "ECONNREFUSED") {
serversLeft--;
continue;
}
throw e;
}
}
if (!serversLeft) {
return statuses;
} else {
throw new Error("Loop ended before all servers could be checked");
}
}
async chooseServer(ideal) {
if (ideal.length === 0) throw "No available servers";
const sorted = ideal.sort((a, b) => {
return b.load - a.load;
});
return sorted[0];
}
async getIdeal() {
let serversLeft = this.connections.size;
if (serversLeft < this.servers.length) {
for (const server of this.servers) {
try {
if (!this.connections.has(server)) await this.connect(server);
} catch (e) {
logger.error(e);
}
}
serversLeft = this.connections.size;
}
const idealServers = [];
for (const address of this.connections.keys()) {
const connection = this.connections.get(address);
if (connection.readyState !== 0 && connection.readyState !== 1) {
serversLeft--;
continue;
}
const controller = new AbortController(); // eslint-disable-line no-undef
const timeout = setTimeout(() => {
controller.abort();
}, 5000);
try {
const statusRequest = await fetch(`http://${address}:8080/status`, { signal: controller.signal });
clearTimeout(timeout);
const status = await statusRequest.text();
serversLeft--;
idealServers.push({
addr: address,
load: parseInt(status)
});
} catch (e) {
if (e.name === "AbortError") {
serversLeft--;
continue;
} else if (e.code === "ECONNREFUSED") {
serversLeft--;
continue;
}
throw e;
} finally {
clearTimeout(timeout);
}
}
if (!serversLeft) {
const server = await this.chooseServer(idealServers);
return { addr: server.addr, sock: this.connections.get(server.addr) };
} else {
throw new Error("Loop ended before all servers could be checked");
}
}
async connect(server) {
const connection = new WebSocket(`ws://${server}:8080/sock`);
connection.on("message", async (msg) => {
const opcode = msg.readUint8(0);
const req = msg.slice(37, msg.length);
const uuid = msg.slice(1, 37).toString();
if (opcode === 0x00) { // Job queued
if (this.jobs[req]) {
this.jobs[req].event.emit("uuid", uuid);
}
} else if (opcode === 0x01) { // Job completed successfully
// the image API sends all job responses over the same socket; make sure this is ours
if (this.jobs[uuid]) {
const imageReq = await fetch(`http://${server}:8080/image?id=${uuid}`);
const image = await imageReq.buffer();
// The response data is given as the file extension/ImageMagick type of the image (e.g. "png"), followed
// by a newline, followed by the image data.
this.jobs[uuid].event.emit("image", image, imageReq.headers.get("ext"));
}
} else if (opcode === 0x02) { // Job errored
if (this.jobs[uuid]) {
this.jobs[uuid].event.emit("error", new Error(req));
}
}
});
connection.on("error", (e) => {
logger.error(e.toString());
});
connection.once("close", () => {
for (const uuid of Object.keys(this.jobs)) {
if (this.jobs[uuid].addr === server) {
this.jobs[uuid].event.emit("error", "Job ended prematurely due to a closed connection; please run your image job again");
delete this.jobs[uuid];
}
}
//logger.log(`Lost connection to ${server}, attempting to reconnect...`);
this.connections.delete(server);
});
this.connections.set(server, connection);
}
async disconnect() {
for (const connection of this.connections.values()) {
connection.close();
}
for (const uuid of Object.keys(this.jobs)) {
this.jobs[uuid].event.emit("error", "Job ended prematurely (not really an error; just run your image job again)");
delete this.jobs[uuid];
}
this.connections.clear();
return;
}
async start(object, num) {
const currentServer = await this.getIdeal();
const data = Buffer.concat([Buffer.from([0x01 /* queue job */]), Buffer.from(num.length.toString()), Buffer.from(num), Buffer.from(JSON.stringify(object))]);
currentServer.sock.send(data);
const event = new EventEmitter();
this.jobs[num] = { event, addr: currentServer.addr };
const uuid = await new Promise((resolve, reject) => {
event.once("uuid", (uuid) => resolve(uuid));
event.once("error", reject);
});
delete this.jobs[num];
this.jobs[uuid] = { event: event, addr: currentServer.addr };
return { uuid: uuid, event: event };
}
run(object) {
return new Promise((resolve, reject) => {
if (process.env.API === "true") {
// Connect to best image server
const num = Math.floor(Math.random() * 100000).toString().slice(0, 5);
const timeout = setTimeout(() => {
if (this.jobs[num]) delete this.jobs[num];
reject("the image request timed out after 25 seconds. Try uploading your image elsewhere.");
}, 25000);
this.start(object, num).catch(err => { // incredibly hacky code incoming
clearTimeout(timeout);
if (err instanceof Error) return reject(err);
return err;
}).then((data) => {
clearTimeout(timeout);
if (!data.event) reject("Not connected to image server");
data.event.once("image", (image, type) => {
delete this.jobs[data.uuid];
const payload = {
// Take just the image data
buffer: image,
type: type
};
resolve(payload);
});
data.event.once("error", (err) => {
delete this.jobs[data.uuid];
reject(err);
});
return;
}).catch(err => reject(err));
} else {
// Called from command (not using image API)
const worker = new Worker(path.join(__dirname, "../image-runner.js"), {
workerData: object
});
worker.once("message", (data) => {
resolve({
buffer: Buffer.from([...data.buffer]),
type: data.fileExtension
});
});
worker.once("error", reject);
}
});
}
async handleCommand(data) {
try {
if (data.type === "run") {
return await this.run(data.obj);
} else if (data.type === "reload") {
await this.disconnect();
await this.repopulate();
let amount = 0;
for (const server of this.servers) {
try {
await this.connect(server);
amount += 1;
} catch (e) {
logger.error(e);
}
}
return amount;
} else if (data.type === "stats") {
return await this.getRunning();
}
} catch (err) {
return { err: err.message };
}
}
shutdown(done) {
done();
}
}
module.exports = ImageWorker;

View File

@ -0,0 +1,57 @@
const { BaseServiceWorker } = require("eris-fleet");
const http = require("http");
const logger = require("../logger.js");
const database = require("../database.js");
class PrometheusWorker extends BaseServiceWorker {
constructor(setup) {
super(setup);
if (process.env.METRICS !== "" && process.env.METRICS !== undefined) {
this.httpServer = http.createServer(async (req, res) => {
if (req.method !== "GET") {
res.statusCode = 405;
return res.end("GET only");
}
res.write(`# HELP command_count Number of times a command has been run
# TYPE command_count counter
`);
if (process.env.API === "true") {
const servers = await this.ipc.command("image", { type: "stats" }, true);
res.write(`# HELP connected_workers Number of workers connected
# TYPE connected_workers gauge
connected_workers ${servers.length}
# HELP running_jobs Number of running jobs on this worker
# TYPE running_jobs gauge
# HELP queued_jobs Number of queued jobs on this worker
# TYPE queued_jobs gauge
# HELP max_jobs Number of max allowed jobs on this worker
# TYPE max_jobs gauge
`);
for (const [i, w] of servers.entries()) {
res.write(`running_jobs{worker="${i}"} ${w.runningJobs}\n`);
res.write(`queued_jobs{worker="${i}"} ${w.queued}\n`);
res.write(`max_jobs{worker="${i}"} ${w.max}\n`);
}
}
const counts = await database.getCounts();
for (const [i, w] of Object.entries(counts)) {
res.write(`command_count{command="${i}"} ${w}\n`);
}
res.end();
});
this.httpServer.listen(process.env.METRICS, () => {
logger.log("info", `Serving metrics at ${process.env.METRICS}`);
});
}
this.serviceReady();
}
shutdown(done) {
this.httpServer.close();
done();
}
}
module.exports = PrometheusWorker;

View File

@ -90,11 +90,15 @@ exports.play = async (client, sound, message, music = false) => {
}
};
exports.nextSong = async (client, message, connection, track, info, music, voiceChannel, loop = false, inQueue = false, lastTrack = null, oldPlaying = null) => {
exports.nextSong = async (client, message, connection, track, info, music, voiceChannel, loop = false, inQueue = false, lastTrack = null) => {
const parts = Math.floor((0 / info.length) * 10);
let playingMessage;
if (!music && this.players.get(voiceChannel.guild.id)) {
const playMessage = this.players.get(voiceChannel.guild.id).playMessage;
if (playMessage.channel.messages.get(playMessage.id)) playMessage.delete();
}
if (lastTrack === track) {
playingMessage = oldPlaying;
playingMessage = this.players.get(voiceChannel.guild.id).playMessage;
} else {
playingMessage = await client.createMessage(message.channel.id, !music ? "🔊 Playing sound..." : {
"embed": {
@ -123,10 +127,12 @@ exports.nextSong = async (client, message, connection, track, info, music, voice
});
}
await connection.play(track);
this.players.set(voiceChannel.guild.id, { player: connection, type: music ? "music" : "sound", host: message.author.id, voiceChannel: voiceChannel, originalChannel: message.channel, loop: loop });
this.players.set(voiceChannel.guild.id, { player: connection, type: music ? "music" : "sound", host: message.author.id, voiceChannel: voiceChannel, originalChannel: message.channel, loop: loop, playMessage: playingMessage });
if (inQueue && connection.listeners("error").length === 0) {
connection.on("error", (error) => {
if (playingMessage.channel.messages.get(playingMessage.id)) playingMessage.delete();
const playMessage = this.players.get(voiceChannel.guild.id).playMessage;
if (playMessage.channel.messages.get(playMessage.id)) playMessage.delete();
this.manager.leave(voiceChannel.guild.id);
connection.destroy();
this.players.delete(voiceChannel.guild.id);
@ -138,9 +144,9 @@ exports.nextSong = async (client, message, connection, track, info, music, voice
connection.on("end", async (data) => {
if (data.reason === "REPLACED") return;
const queue = this.queues.get(voiceChannel.guild.id);
const isLooping = this.players.get(voiceChannel.guild.id).loop;
const player = this.players.get(voiceChannel.guild.id);
let newQueue;
if (isLooping) {
if (player.loop) {
queue.push(queue.shift());
newQueue = queue;
} else {
@ -153,11 +159,21 @@ exports.nextSong = async (client, message, connection, track, info, music, voice
this.players.delete(voiceChannel.guild.id);
this.queues.delete(voiceChannel.guild.id);
if (music) await client.createMessage(message.channel.id, "🔊 The current voice channel session has ended.");
if (playingMessage.channel.messages.get(playingMessage.id)) await playingMessage.delete();
try {
if (playingMessage.channel.messages.get(playingMessage.id)) await playingMessage.delete();
if (player.playMessage.channel.messages.get(player.playMessage.id)) await player.playMessage.delete();
} catch {
// no-op
}
} else {
const newTrack = await fetch(`http://${connection.node.host}:${connection.node.port}/decodetrack?track=${encodeURIComponent(newQueue[0])}`, { headers: { Authorization: connection.node.password } }).then(res => res.json());
this.nextSong(client, message, connection, newQueue[0], newTrack, music, voiceChannel, isLooping, true, track, playingMessage);
if (newQueue[0] !== track && playingMessage.channel.messages.get(playingMessage.id)) await playingMessage.delete();
this.nextSong(client, message, connection, newQueue[0], newTrack, music, voiceChannel, player.loop, true, track);
try {
if (newQueue[0] !== track && playingMessage.channel.messages.get(playingMessage.id)) await playingMessage.delete();
if (newQueue[0] !== track && player.playMessage.channel.messages.get(player.playMessage.id)) await player.playMessage.delete();
} catch {
// no-op
}
}
});
}