From db0decf71af2e34fc45156bc76e56e7874f8b509 Mon Sep 17 00:00:00 2001 From: Essem Date: Wed, 21 Sep 2022 00:05:03 -0500 Subject: [PATCH] Replace eris-fleet with a pm2-based cluster system, overhaul image handling, removed azure image api --- .env.example | 5 - .gitignore | 13 +- api/IMPLEMENTATION.md | 12 +- api/index.js | 13 +- app.js | 329 +++++----- classes/command.js | 7 +- classes/imageCommand.js | 28 +- classes/musicCommand.js | 4 +- commands/general/avatar.js | 31 +- commands/general/banner.js | 28 +- commands/general/broadcast.js | 54 +- commands/general/imagereload.js | 16 +- commands/general/imagestats.js | 13 +- commands/general/info.js | 9 +- commands/general/reload.js | 42 +- commands/general/restart.js | 3 +- commands/general/soundreload.js | 44 +- commands/general/stats.js | 26 +- commands/general/userinfo.js | 2 +- commands/music/host.js | 2 +- commands/music/music.js | 2 +- commands/tags/tags.js | 2 +- docs/config.md | 4 +- docs/custom-commands.md | 3 - ecosystem.config.cjs | 17 + events/debug.js | 2 +- events/error.js | 5 + events/guildCreate.js | 2 +- events/guildDelete.js | 2 +- events/interactionCreate.js | 6 +- events/messageCreate.js | 6 +- events/voiceChannelLeave.js | 2 +- events/voiceChannelSwitch.js | 4 +- events/warn.js | 5 + package.json | 2 +- pnpm-lock.yaml | 1017 +++++++++++++++++++++++++++++-- shard.js | 178 ------ utils/help.js | 4 - utils/image.js | 113 +++- utils/imageConnection.js | 40 +- utils/logger.js | 39 +- utils/misc.js | 65 ++ utils/pm2/ext.js | 125 ++++ utils/services/image.js | 268 -------- utils/tempimages.js | 40 +- 45 files changed, 1777 insertions(+), 857 deletions(-) create mode 100644 ecosystem.config.cjs create mode 100644 events/error.js create mode 100644 events/warn.js delete mode 100644 shard.js create mode 100644 utils/pm2/ext.js delete mode 100644 utils/services/image.js diff --git a/.env.example b/.env.example index f445ed5..ab29147 100644 --- a/.env.example +++ b/.env.example @@ -48,12 +48,7 @@ METRICS= # The image API type to be used # Set this to `none` to process all images locally # Set this to `ws` if you want to use the external image API script, located in api/index.js -# Set this to `azure` to use the Azure Functions API API_TYPE=none -# If API_TYPE is `azure`, set this to your Azure webhook URL -AZURE_URL= -# If API_TYPE is `azure`, set an optional password for webhook responses -AZURE_PASS= # Put ID of server to limit owner-only commands to ADMIN_SERVER= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 956ff9a..4744b6c 100644 --- a/.gitignore +++ b/.gitignore @@ -117,15 +117,4 @@ libvips/ # Databases data/ -*.sqlite - -# Azure Functions artifacts -bin -obj -appsettings.json -local.settings.json - -# Azurite artifacts -__blobstorage__ -__queuestorage__ -__azurite_db*__.json +*.sqlite \ No newline at end of file diff --git a/api/IMPLEMENTATION.md b/api/IMPLEMENTATION.md index 6b8a226..82344f4 100644 --- a/api/IMPLEMENTATION.md +++ b/api/IMPLEMENTATION.md @@ -6,6 +6,9 @@ The esmBot image API is a combined HTTP and WebSocket API. The default port to a ### GET `/image/?id=` Get image data after job is finished running. The Content-Type header is properly set. +### GET `/count` +Get the current amount of running jobs. Response is a plaintext number value. + ## WebSockets A client sends *requests* (T-messages) to a server, which subsequently *replies* (R-messages) to the client. ### Message IDs @@ -24,11 +27,11 @@ A client sends *requests* (T-messages) to a server, which subsequently *replies* [j] means JSON data that goes until the end of the message. `tag` is used to identify a request/response pair, like `lock` in the original API. `jid` is used to identify a job. `job` is a job object. - Rerror tag[2] error[s] -- Tqueue tag[2] jid[4] job[j] +- Tqueue tag[2] jid[8] job[j] - Rqueue tag[2] -- Tcancel tag[2] jid[4] +- Tcancel tag[2] jid[8] - Rcancel tag[2] -- Twait tag[2] jid[4] +- Twait tag[2] jid[8] - Rwait tag[2] - Rinit tag[2] max_jobs[2] running_jobs[2] formats[j] @@ -42,6 +45,7 @@ The job object is formatted like this: "params": { // content varies depending on the command, some common parameters are listed here "type": string, // mime type of output, should usually be the same as input ... - } + }, + "name": string // filename of the image, without extension } ``` diff --git a/api/index.js b/api/index.js index d305889..867f4cd 100644 --- a/api/index.js +++ b/api/index.js @@ -107,8 +107,8 @@ wss.on("connection", (ws, request) => { const tag = msg.slice(1, 3); const req = msg.toString().slice(3); if (opcode == Tqueue) { - const id = msg.readUInt32LE(3); - const obj = msg.slice(7); + const id = msg.readBigInt64LE(3); + const obj = msg.slice(11); const job = { msg: obj, num: jobAmount, verifyEvent: new EventEmitter() }; jobs.set(id, job); queue.push(id); @@ -128,7 +128,7 @@ wss.on("connection", (ws, request) => { const cancelResponse = Buffer.concat([Buffer.from([Rcancel]), tag]); ws.send(cancelResponse); } else if (opcode == Twait) { - const id = msg.readUInt32LE(3); + const id = msg.readBigUInt64LE(3); const job = jobs.get(id); if (!job) { const errorResponse = Buffer.concat([Buffer.from([Rerror]), tag, Buffer.from("Invalid job ID")]); @@ -178,7 +178,7 @@ httpServer.on("request", async (req, res) => { res.statusCode = 400; return res.end("400 Bad Request"); } - const id = parseInt(reqUrl.searchParams.get("id")); + const id = BigInt(reqUrl.searchParams.get("id")); if (!jobs.has(id)) { res.statusCode = 410; return res.end("410 Gone"); @@ -208,6 +208,11 @@ httpServer.on("request", async (req, res) => { return res.end(data, (err) => { if (err) error(err); }); + } else if (reqUrl.pathname === "/count" && req.method === "GET") { + log(`Sending job count to ${req.socket.remoteAddress}:${req.socket.remotePort} via HTTP`); + return res.end(jobAmount.toString(), (err) => { + if (err) error(err); + }); } else { res.statusCode = 404; return res.end("404 Not Found"); diff --git a/app.js b/app.js index 06a2ec1..51fa68f 100644 --- a/app.js +++ b/app.js @@ -1,12 +1,15 @@ -if (process.platform === "win32") console.error("\x1b[1m\x1b[31m\x1b[40m" + `WIN32 IS NOT OFFICIALLY SUPPORTED! -Although there's a (very) slim chance of it working, multiple aspects of the bot are built with UNIX-like systems in mind and could break on Win32-based systems. If you want to run the bot on Windows, using Windows Subsystem for Linux is highly recommended. -The bot will continue to run past this message, but keep in mind that it could break at any time. Continue running at your own risk; alternatively, stop the bot using Ctrl+C and install WSL.` + "\x1b[0m"); -if (process.versions.node.split(".")[0] < 15) { +if (process.versions.node.split(".")[0] < 16) { console.error(`You are currently running Node.js version ${process.version}. -esmBot requires Node.js version 15 or above. +esmBot requires Node.js version 16 or above. Please refer to step 3 of the setup guide.`); process.exit(1); } +if (process.platform === "win32") { + console.error("\x1b[1m\x1b[31m\x1b[40m" + `WINDOWS IS NOT OFFICIALLY SUPPORTED! +Although there's a (very) slim chance of it working, multiple aspects of the bot are built with UNIX-like systems in mind and could break on Win32-based systems. If you want to run the bot on Windows, using Windows Subsystem for Linux is highly recommended. +The bot will continue to run past this message in 5 seconds, but keep in mind that it could break at any time. Continue running at your own risk; alternatively, stop the bot using Ctrl+C and install WSL.` + "\x1b[0m"); + Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5000); +} // load config from .env file import { resolve, dirname } from "path"; @@ -14,61 +17,58 @@ import { fileURLToPath } from "url"; import { config } from "dotenv"; config({ path: resolve(dirname(fileURLToPath(import.meta.url)), ".env") }); -// main sharding manager -import { Fleet } from "eris-fleet"; -import { isMaster } from "cluster"; +import { generateList, createPage } from "./utils/help.js"; +import { reloadImageConnections } from "./utils/image.js"; + // main services -import Shard from "./shard.js"; -import ImageWorker from "./utils/services/image.js"; -import PrometheusWorker from "./utils/services/prometheus.js"; +import Eris from "eris"; +import pm2 from "pm2"; // some utils import { promises, readFileSync } from "fs"; -import winston from "winston"; -import "winston-daily-rotate-file"; +import { logger } from "./utils/logger.js"; import { exec as baseExec } from "child_process"; import { promisify } from "util"; - const exec = promisify(baseExec); +// initialize command loader +import { load, send } from "./utils/handler.js"; +// command collections +import { paths } from "./utils/collections.js"; // database stuff import database from "./utils/database.js"; -// dbl posting -import { Api } from "@top-gg/sdk"; -const dbl = process.env.NODE_ENV === "production" && process.env.DBL ? new Api(process.env.DBL) : null; +// lavalink stuff +import { checkStatus, connect, reload, status, connected } from "./utils/soundplayer.js"; +// events +import { endBroadcast, startBroadcast, activityChanger, checkBroadcast } from "./utils/misc.js"; +import { parseThreshold } from "./utils/tempimages.js"; const { types } = JSON.parse(readFileSync(new URL("./config/commands.json", import.meta.url))); -if (isMaster) { - const esmBotVersion = JSON.parse(readFileSync(new URL("./package.json", import.meta.url))).version; - const erisFleetVersion = JSON.parse(readFileSync(new URL("./node_modules/eris-fleet/package.json", import.meta.url))).version; // a bit of a hacky way to get the eris-fleet version +const esmBotVersion = JSON.parse(readFileSync(new URL("./package.json", import.meta.url))).version; +exec("git rev-parse HEAD").then(output => output.stdout.substring(0, 7), () => "unknown commit").then(o => process.env.GIT_REV = o).then(() => { 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 [ + ,*\`$ 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 output.stdout.substring(0, 7), () => "unknown commit"))}), powered by eris-fleet ${erisFleetVersion} +5, REBBB4BBBBB#BBBBBBBBBBBBP5BFF ,F +*w \`*4BBW\`"FF#F##FFFF"\` , * +" + *+, " F'"'*^~~~^"^\` V+*^ + \`""" + +esmBot ${esmBotVersion} (${process.env.GIT_REV}) `); -} - -const services = [ - { name: "image", ServiceWorker: ImageWorker } -]; -if (process.env.METRICS && process.env.METRICS !== "") services.push({ name: "prometheus", ServiceWorker: PrometheusWorker }); +}); const intents = [ "guildVoiceStates", @@ -80,115 +80,148 @@ if (types.classic) { intents.push("messageContent"); } -const Admiral = new Fleet({ - BotWorker: Shard, - token: `Bot ${process.env.TOKEN}`, - fetchTimeout: 900000, - maxConcurrencyOverride: 1, - startingStatus: { - status: "idle", - game: { - name: "Starting esmBot..." +// PM2-specific handling +if (process.env.PM2_USAGE) { + pm2.launchBus((err, pm2Bus) => { + if (err) { + logger.error(err); + return; } - }, - whatToLog: { - blacklist: ["stats_update"] - }, - clientOptions: { - allowedMentions: { - everyone: false, - roles: false, - users: true, - repliedUser: true - }, - restMode: true, - messageLimit: 50, - intents, - stats: { - requestTimeout: 30000 - }, - connectionTimeout: 30000 - }, - useCentralRequestHandler: process.env.DEBUG_LOG ? false : true, // workaround for eris-fleet weirdness - services + + pm2Bus.on("process:msg", async (packet) => { + switch (packet.data?.type) { + case "reload": + var path = paths.get(packet.data.message); + await load(bot, path, await checkStatus(), true); + break; + case "soundreload": + var soundStatus = await checkStatus(); + if (!soundStatus) { + reload(); + } + break; + case "imagereload": + await reloadImageConnections(); + break; + case "broadcastStart": + startBroadcast(bot, packet.data.message); + break; + case "broadcastEnd": + endBroadcast(bot); + break; + case "serverCounts": + pm2.sendDataToProcessId(0, { + id: 0, + type: "process:msg", + data: { + type: "serverCounts", + guilds: bot.guilds.size, + shards: bot.shards.size + }, + topic: true + }, (err) => { + if (err) logger.error(err); + }); + break; + } + }); + }); +} + +database.upgrade(logger).then(result => { + if (result === 1) return process.exit(1); }); -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 }), stderrLevels: ["error", "warn"] }), - new winston.transports.DailyRotateFile({ filename: "logs/error-%DATE%.log", level: "error", zippedArchive: true, maxSize: 4194304, maxFiles: 8 }), - new winston.transports.DailyRotateFile({ filename: "logs/main-%DATE%.log", zippedArchive: true, maxSize: 4194304, maxFiles: 8 }) - ], - level: process.env.DEBUG_LOG ? "debug" : "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; +// process the threshold into bytes early +if (process.env.TEMPDIR && process.env.THRESHOLD) { + parseThreshold(); +} - return `[${timestamp}]: [${level.toUpperCase()}] - ${message} ${Object.keys(args).length ? JSON.stringify(args, null, 2) : ""}`; - }), - ) - }); +if (!types.classic && !types.application) { + logger.error("Both classic and application commands are disabled! Please enable at least one command type in config/commands.json."); + process.exit(1); +} - winston.addColors({ - info: "green", - main: "gray", - debug: "magenta", - warn: "yellow", - error: "red" - }); +const bot = new Eris(`Bot ${process.env.TOKEN}`, { + allowedMentions: { + everyone: false, + roles: false, + users: true, + repliedUser: true + }, + restMode: true, + maxShards: "auto", + messageLimit: 50, + intents, + connectionTimeout: 30000 +}); - database.upgrade(logger).then(result => { - if (result === 1) return process.exit(1); - }); - - 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) { - Admiral.on("stats", async (m) => { - await dbl.postStats({ - serverCount: m.guilds, - shardCount: m.shardCount - }); - }); - } - - // process the threshold into bytes early - if (process.env.TEMPDIR && process.env.THRESHOLD) { - const matched = process.env.THRESHOLD.match(/(\d+)([KMGT])/); - const sizes = { - K: 1024, - M: 1048576, - G: 1073741824, - T: 1099511627776 - }; - if (matched && matched[1] && matched[2]) { - process.env.THRESHOLD = matched[1] * sizes[matched[2]]; - } else { - logger.error("Invalid THRESHOLD config."); - process.env.THRESHOLD = undefined; +bot.once("ready", async () => { + // register commands and their info + const soundStatus = await checkStatus(); + logger.log("info", "Attempting to load commands..."); + for await (const commandFile of getFiles(resolve(dirname(fileURLToPath(import.meta.url)), "./commands/"))) { + logger.log("main", `Loading command from ${commandFile}...`); + try { + await load(bot, commandFile, soundStatus); + } catch (e) { + logger.error(`Failed to register command from ${commandFile}: ${e}`); + } + } + if (types.application) { + try { + await send(bot); + } catch (e) { + logger.log("error", e); + logger.log("error", "Failed to send command data to Discord, slash/message commands may be unavailable."); + } + } + logger.log("info", "Finished loading commands."); + + if (process.env.API_TYPE === "ws") await reloadImageConnections(); + await database.setup(); + + // register events + logger.log("info", "Attempting to load events..."); + for await (const file of getFiles(resolve(dirname(fileURLToPath(import.meta.url)), "./events/"))) { + logger.log("main", `Loading event from ${file}...`); + const eventArray = file.split("/"); + const eventName = eventArray[eventArray.length - 1].split(".")[0]; + if (eventName === "interactionCreate" && !types.application) { + logger.log("warn", `Skipped loading event from ${file} because application commands are disabled`); + continue; + } + const { default: event } = await import(file); + bot.on(eventName, event.bind(null, bot)); + } + logger.log("info", "Finished loading events."); + + // generate docs + if (process.env.OUTPUT && process.env.OUTPUT !== "") { + generateList(); + await createPage(process.env.OUTPUT); + logger.log("info", "The help docs have been generated."); + } + + // connect to lavalink + if (!status && !connected) connect(bot); + + checkBroadcast(bot); + activityChanger(bot); + + logger.log("info", "Started esmBot."); +}); + +async function* getFiles(dir) { + const dirents = await promises.readdir(dir, { withFileTypes: true }); + for (const dirent of dirents) { + const name = dir + (dir.charAt(dir.length - 1) !== "/" ? "/" : "") + dirent.name; + if (dirent.isDirectory()) { + yield* getFiles(name); + } else if (dirent.name.endsWith(".js")) { + yield name; } - const dirstat = (await promises.readdir(process.env.TEMPDIR)).map((file) => { - return promises.stat(`${process.env.TEMPDIR}/${file}`).then((stats) => stats.size); - }); - const size = await Promise.all(dirstat); - const reduced = size.reduce((a, b) => { - return a + b; - }, 0); - Admiral.centralStore.set("dirSizeCache", reduced); } } + +bot.connect(); \ No newline at end of file diff --git a/classes/command.js b/classes/command.js index 39647da..f5703fa 100644 --- a/classes/command.js +++ b/classes/command.js @@ -1,10 +1,7 @@ class Command { success = true; - constructor(client, cluster, worker, ipc, options) { + constructor(client, options) { this.client = client; - this.cluster = cluster; - this.worker = worker; - this.ipc = ipc; this.origOptions = options; this.type = options.type; this.args = options.args; @@ -50,7 +47,7 @@ class Command { async acknowledge() { if (this.type === "classic") { await this.client.sendChannelTyping(this.channel.id); - } else { + } else if (!this.interaction.acknowledged) { await this.interaction.acknowledge(); } } diff --git a/classes/imageCommand.js b/classes/imageCommand.js index 0985709..4f48714 100644 --- a/classes/imageCommand.js +++ b/classes/imageCommand.js @@ -1,5 +1,6 @@ import Command from "./command.js"; import imageDetect from "../utils/imagedetect.js"; +import { runImageJob } from "../utils/image.js"; import { runningCommands } from "../utils/collections.js"; import { readFileSync } from "fs"; const { emotes } = JSON.parse(readFileSync(new URL("../config/messages.json", import.meta.url))); @@ -22,7 +23,7 @@ class ImageCommand extends Command { // before awaiting the command result, add this command to the set of running commands runningCommands.set(this.author.id, timestamp); - const magickParams = { + const imageParams = { cmd: this.constructor.command, params: {} }; @@ -36,7 +37,7 @@ class ImageCommand extends Command { if (selection) selectedImages.delete(this.author.id); if (image === undefined) { runningCommands.delete(this.author.id); - return this.constructor.noImage; + return `${this.constructor.noImage} (Tip: try right-clicking/holding on a message and press Apps -> Select Image, then try again.)`; } else if (image.type === "large") { runningCommands.delete(this.author.id); return "That image is too large (>= 25MB)! Try using a smaller image."; @@ -44,11 +45,12 @@ class ImageCommand extends Command { runningCommands.delete(this.author.id); return "I've been rate-limited by Tenor. Please try uploading your GIF elsewhere."; } - magickParams.path = image.path; - magickParams.params.type = image.type; - magickParams.url = image.url; // technically not required but can be useful for text filtering - magickParams.name = image.name; - if (this.constructor.requiresGIF) magickParams.onlyGIF = true; + imageParams.path = image.path; + imageParams.params.type = image.type; + imageParams.url = image.url; // technically not required but can be useful for text filtering + imageParams.name = image.name; + imageParams.id = (this.interaction ?? this.message).id; + if (this.constructor.requiresGIF) imageParams.onlyGIF = true; } catch (e) { runningCommands.delete(this.author.id); throw e; @@ -57,31 +59,31 @@ class ImageCommand extends Command { if (this.constructor.requiresText) { const text = this.options.text ?? this.args.join(" ").trim(); - if (text.length === 0 || !await this.criteria(text, magickParams.url)) { + if (text.length === 0 || !await this.criteria(text, imageParams.url)) { runningCommands.delete(this.author.id); return this.constructor.noText; } } if (typeof this.params === "function") { - Object.assign(magickParams.params, this.params(magickParams.url, magickParams.name)); + Object.assign(imageParams.params, this.params(imageParams.url, imageParams.name)); } else if (typeof this.params === "object") { - Object.assign(magickParams.params, this.params); + Object.assign(imageParams.params, this.params); } let status; - if (magickParams.params.type === "image/gif" && this.type === "classic") { + if (imageParams.params.type === "image/gif" && this.type === "classic") { status = await this.processMessage(this.message); } try { - const { buffer, type } = await this.ipc.serviceCommand("image", { type: "run", obj: magickParams }, true, 9000000); + const { arrayBuffer, type } = await runImageJob(imageParams); if (type === "nogif" && this.constructor.requiresGIF) { return "That isn't a GIF!"; } this.success = true; return { - file: Buffer.from(buffer.data), + file: Buffer.from(arrayBuffer), name: `${this.constructor.command}.${type}` }; } catch (e) { diff --git a/classes/musicCommand.js b/classes/musicCommand.js index d4d228a..ea071ae 100644 --- a/classes/musicCommand.js +++ b/classes/musicCommand.js @@ -2,8 +2,8 @@ import Command from "./command.js"; import { players, queues } from "../utils/soundplayer.js"; class MusicCommand extends Command { - constructor(client, cluster, worker, ipc, options) { - super(client, cluster, worker, ipc, options); + constructor(client, options) { + super(client, options); if (this.channel.guild) { this.connection = players.get(this.channel.guild.id); this.queue = queues.get(this.channel.guild.id); diff --git a/commands/general/avatar.js b/commands/general/avatar.js index 06b8b9d..a39b99f 100644 --- a/commands/general/avatar.js +++ b/commands/general/avatar.js @@ -7,20 +7,23 @@ class AvatarCommand extends Command { const self = await this.client.getRESTUser(this.author.id); if (this.type === "classic" && this.message.mentions[0]) { return this.message.mentions[0].dynamicAvatarURL(null, 512); - } else if (await this.ipc.fetchUser(member)) { - let user = await this.ipc.fetchUser(member); - if (!user) user = await this.client.getRESTUser(member); - return user?.avatar ? this.client._formatImage(`/avatars/${user.id}/${user.avatar}`, null, 512) : `https://cdn.discordapp.com/embed/avatars/${user.discriminator % 5}.png`; // hacky "solution" - } else if (mentionRegex.test(member)) { - const id = member.match(mentionRegex)[1]; - if (id < 21154535154122752n) { - this.success = false; - return "That's not a valid mention!"; - } - try { - const user = await this.client.getRESTUser(id); - return user.avatar ? this.client._formatImage(`/avatars/${user.id}/${user.avatar}`, null, 512) : `https://cdn.discordapp.com/embed/avatars/${user.discriminator % 5}.png`; // repeat of hacky "solution" from above - } catch { + } else if (member) { + const user = await this.client.getRESTUser(member); + if (user) { + return user?.avatar ? this.client._formatImage(`/avatars/${user.id}/${user.avatar}`, null, 512) : `https://cdn.discordapp.com/embed/avatars/${user.discriminator % 5}.png`; // hacky "solution" + } else if (mentionRegex.text(member)) { + const id = member.match(mentionRegex)[1]; + if (id < 21154535154122752n) { + this.success = false; + return "That's not a valid mention!"; + } + try { + const user = await this.client.getRESTUser(id); + return user.avatar ? this.client._formatImage(`/avatars/${user.id}/${user.avatar}`, null, 512) : `https://cdn.discordapp.com/embed/avatars/${user.discriminator % 5}.png`; // repeat of hacky "solution" from above + } catch { + return self.dynamicAvatarURL(null, 512); + } + } else { return self.dynamicAvatarURL(null, 512); } } else if (this.args.join(" ") !== "" && this.channel.guild) { diff --git a/commands/general/banner.js b/commands/general/banner.js index bc1e4ed..49fc46e 100644 --- a/commands/general/banner.js +++ b/commands/general/banner.js @@ -7,20 +7,24 @@ class BannerCommand extends Command { const self = await this.client.getRESTUser(this.author.id); if (this.type === "classic" && this.message.mentions[0]) { return this.message.mentions[0].dynamicBannerURL(null, 512) ?? "This user doesn't have a banner!"; - } else if (await this.ipc.fetchUser(member)) { + } else if (member) { const user = await this.client.getRESTUser(member); - return user.dynamicBannerURL(null, 512) ?? "This user doesn't have a banner!"; - } else if (mentionRegex.test(member)) { - const id = member.match(mentionRegex)[1]; - if (id < 21154535154122752n) { - this.success = false; - return "That's not a valid mention!"; - } - try { - const user = await this.client.getRESTUser(id); + if (user) { return user.dynamicBannerURL(null, 512) ?? "This user doesn't have a banner!"; - } catch { - return self.dynamicBannerURL(null, 512) ?? "You don't have a banner!"; + } else if (mentionRegex.text(member)) { + const id = member.match(mentionRegex)[1]; + if (id < 21154535154122752n) { + this.success = false; + return "That's not a valid mention!"; + } + try { + const user = await this.client.getRESTUser(id); + return user.dynamicBannerURL(null, 512) ?? "This user doesn't have a banner!"; + } catch { + return self.dynamicBannerURL(null, 512) ?? "You don't have a banner!"; + } + } else { + return "This user doesn't have a banner!"; } } else if (this.args.join(" ") !== "" && this.channel.guild) { const searched = await this.channel.guild.searchMembers(this.args.join(" ")); diff --git a/commands/general/broadcast.js b/commands/general/broadcast.js index 955166b..cb66b22 100644 --- a/commands/general/broadcast.js +++ b/commands/general/broadcast.js @@ -1,32 +1,38 @@ import Command from "../../classes/command.js"; +import { endBroadcast, startBroadcast } from "../../utils/misc.js"; class BroadcastCommand extends Command { - // yet another very hacky command - run() { - return new Promise((resolve) => { - const owners = process.env.OWNER.split(","); - if (!owners.includes(this.author.id)) { - this.success = false; - resolve("Only the bot owner can broadcast messages!"); - return; - } - const message = this.options.message ?? this.args.join(" "); - if (message?.trim()) { - this.ipc.centralStore.set("broadcast", message); - this.ipc.broadcast("playbroadcast", message); - this.ipc.register("broadcastSuccess", () => { - this.ipc.unregister("broadcastSuccess"); - resolve("Successfully broadcasted message."); - }); - } else { - this.ipc.centralStore.delete("broadcast"); - this.ipc.broadcast("broadcastend"); - this.ipc.register("broadcastEnd", () => { - this.ipc.unregister("broadcastEnd"); - resolve("Successfully ended broadcast."); + async run() { + const owners = process.env.OWNER.split(","); + if (!owners.includes(this.author.id)) { + this.success = false; + return "Only the bot owner can broadcast messages!"; + } + const message = this.options.message ?? this.args.join(" "); + if (message?.trim()) { + startBroadcast(this.client, message); + if (process.env.PM2_USAGE) { + process.send({ + type: "process:msg", + data: { + type: "broadcastStart", + message + } }); } - }); + return "Started broadcast."; + } else { + endBroadcast(this.client); + if (process.env.PM2_USAGE) { + process.send({ + type: "process:msg", + data: { + type: "broadcastEnd" + } + }); + } + return "Ended broadcast."; + } } static flags = [{ diff --git a/commands/general/imagereload.js b/commands/general/imagereload.js index 262e639..1d625f9 100644 --- a/commands/general/imagereload.js +++ b/commands/general/imagereload.js @@ -1,4 +1,5 @@ import Command from "../../classes/command.js"; +import { reloadImageConnections } from "../../utils/image.js"; class ImageReloadCommand extends Command { async run() { @@ -7,9 +8,18 @@ class ImageReloadCommand extends Command { this.success = false; return "Only the bot owner can reload the image servers!"; } - const amount = await this.ipc.serviceCommand("image", { type: "reload" }, true); - if (amount > 0) { - return `Successfully connected to ${amount} image servers.`; + await this.acknowledge(); + const length = await reloadImageConnections(); + if (!length) { + if (process.env.PM2_USAGE) { + process.send({ + type: "process:msg", + data: { + type: "imagereload" + } + }); + } + return `Successfully connected to ${length} image server(s).`; } else { return "I couldn't connect to any image servers!"; } diff --git a/commands/general/imagestats.js b/commands/general/imagestats.js index 7296659..74e15b2 100644 --- a/commands/general/imagestats.js +++ b/commands/general/imagestats.js @@ -1,9 +1,9 @@ import Command from "../../classes/command.js"; +import { connections } from "../../utils/image.js"; class ImageStatsCommand extends Command { async run() { await this.acknowledge(); - const servers = await this.ipc.serviceCommand("image", { type: "stats" }, true); const embed = { embeds: [{ "author": { @@ -11,14 +11,17 @@ class ImageStatsCommand extends Command { "icon_url": this.client.user.avatarURL }, "color": 16711680, - "description": `The bot is currently connected to ${servers.length} image server(s).`, + "description": `The bot is currently connected to ${connections.size} image server(s).`, "fields": [] }] }; - for (let i = 0; i < servers.length; i++) { + let i = 0; + for (const connection of connections.values()) { + const count = await connection.getCount(); + if (!count) continue; embed.embeds[0].fields.push({ - name: `Server ${i + 1}`, - value: `Running Jobs: ${Math.min(servers[i].runningJobs, servers[i].max)}\nQueued: ${Math.max(0, servers[i].runningJobs - servers[i].max)}\nMax Jobs: ${servers[i].max}` + name: `Server ${i++}`, + value: `Running Jobs: ${count}` }); } return embed; diff --git a/commands/general/info.js b/commands/general/info.js index 51545f5..d16137e 100644 --- a/commands/general/info.js +++ b/commands/general/info.js @@ -5,13 +5,14 @@ const { version } = JSON.parse(readFileSync(new URL("../../package.json", import import Command from "../../classes/command.js"; import { exec as baseExec } from "child_process"; import { promisify } from "util"; +import { getServers } from "../../utils/misc.js"; const exec = promisify(baseExec); class InfoCommand extends Command { async run() { - let owner = await this.ipc.fetchUser(process.env.OWNER.split(",")[0]); - if (!owner) owner = await this.client.getRESTUser(process.env.OWNER.split(",")[0]); - const stats = await this.ipc.getStats(); + const owner = await this.client.getRESTUser(process.env.OWNER.split(",")[0]); + const servers = await getServers(); + await this.acknowledge(); return { embeds: [{ color: 16711680, @@ -30,7 +31,7 @@ class InfoCommand extends Command { }, { name: "💬 Total Servers:", - value: stats?.guilds ? stats.guilds : `${this.client.guilds.size} (for this cluster only)` + value: servers ? servers : `${this.client.guilds.size} (for this process only)` }, { name: "✅ Official Server:", diff --git a/commands/general/reload.js b/commands/general/reload.js index 4220984..c5d9818 100644 --- a/commands/general/reload.js +++ b/commands/general/reload.js @@ -1,27 +1,29 @@ import Command from "../../classes/command.js"; +import { load } from "../../utils/handler.js"; +import { checkStatus } from "../../utils/soundplayer.js"; +import { paths } from "../../utils/collections.js"; class ReloadCommand extends Command { - // quite possibly one of the hackiest commands in the bot - run() { - return new Promise((resolve) => { - const owners = process.env.OWNER.split(","); - if (!owners.includes(this.author.id)) return resolve("Only the bot owner can reload commands!"); - const commandName = this.options.cmd ?? this.args.join(" "); - if (!commandName || !commandName.trim()) return resolve("You need to provide a command to reload!"); - this.acknowledge().then(() => { - this.ipc.broadcast("reload", commandName); - this.ipc.register("reloadSuccess", () => { - this.ipc.unregister("reloadSuccess"); - this.ipc.unregister("reloadFail"); - resolve(`The command \`${commandName}\` has been reloaded.`); - }); - this.ipc.register("reloadFail", (message) => { - this.ipc.unregister("reloadSuccess"); - this.ipc.unregister("reloadFail"); - resolve(message.result); - }); + async run() { + const owners = process.env.OWNER.split(","); + if (!owners.includes(this.author.id)) return "Only the bot owner can reload commands!"; + const commandName = this.options.cmd ?? this.args.join(" "); + if (!commandName || !commandName.trim()) return "You need to provide a command to reload!"; + await this.acknowledge(); + const path = paths.get(commandName); + if (!path) return "I couldn't find that command!"; + const result = await load(this.client, path, await checkStatus(), true); + if (result !== commandName) return "I couldn't reload that command!"; + if (process.env.PM2_USAGE) { + process.send({ + type: "process:msg", + data: { + type: "reload", + message: commandName + } }); - }); + } + return `The command \`${commandName}\` has been reloaded.`; } static flags = [{ diff --git a/commands/general/restart.js b/commands/general/restart.js index 17cdda1..6714b3e 100644 --- a/commands/general/restart.js +++ b/commands/general/restart.js @@ -10,8 +10,7 @@ class RestartCommand extends Command { await this.client.createMessage(this.channel.id, Object.assign({ content: "esmBot is restarting." }, this.reference)); - this.ipc.restartAllClusters(true); - //this.ipc.broadcast("restart"); + process.exit(1); } static description = "Restarts me"; diff --git a/commands/general/soundreload.js b/commands/general/soundreload.js index 296c282..802de50 100644 --- a/commands/general/soundreload.js +++ b/commands/general/soundreload.js @@ -1,29 +1,29 @@ import Command from "../../classes/command.js"; +import { checkStatus, reload } from "../../utils/soundplayer.js"; class SoundReloadCommand extends Command { - // another very hacky command - run() { - return new Promise((resolve) => { - const owners = process.env.OWNER.split(","); - if (!owners.includes(this.author.id)) { - this.success = false; - return "Only the bot owner can reload Lavalink!"; + async run() { + const owners = process.env.OWNER.split(","); + if (!owners.includes(this.author.id)) { + this.success = false; + return "Only the bot owner can reload Lavalink!"; + } + await this.acknowledge(); + const soundStatus = await checkStatus(); + if (!soundStatus) { + const length = reload(); + if (process.env.PM2_USAGE) { + process.send({ + type: "process:msg", + data: { + type: "soundreload" + } + }); } - this.acknowledge().then(() => { - this.ipc.broadcast("soundreload"); - this.ipc.register("soundReloadSuccess", (msg) => { - this.ipc.unregister("soundReloadSuccess"); - this.ipc.unregister("soundReloadFail"); - resolve(`Successfully connected to ${msg.length} Lavalink node(s).`); - }); - this.ipc.register("soundReloadFail", () => { - this.ipc.unregister("soundReloadSuccess"); - this.ipc.unregister("soundReloadFail"); - resolve("I couldn't connect to any Lavalink nodes!"); - }); - }); - - }); + return `Successfully connected to ${length} Lavalink node(s).`; + } else { + return "I couldn't connect to any Lavalink nodes!"; + } } static description = "Attempts to reconnect to all available Lavalink nodes"; diff --git a/commands/general/stats.js b/commands/general/stats.js index f5d5f3f..4b4161c 100644 --- a/commands/general/stats.js +++ b/commands/general/stats.js @@ -7,6 +7,8 @@ import Command from "../../classes/command.js"; import { VERSION } from "eris"; import { exec as baseExec } from "child_process"; import { promisify } from "util"; +import pm2 from "pm2"; +import { getServers } from "../../utils/misc.js"; const exec = promisify(baseExec); class StatsCommand extends Command { @@ -14,7 +16,7 @@ class StatsCommand extends Command { const uptime = process.uptime() * 1000; const connUptime = this.client.uptime; const owner = await this.client.getRESTUser(process.env.OWNER.split(",")[0]); - const stats = await this.ipc.getStats(); + const servers = await getServers(); return { embeds: [{ "author": { @@ -28,13 +30,13 @@ class StatsCommand extends Command { "value": `v${version}${process.env.NODE_ENV === "development" ? `-dev (${(await exec("git rev-parse HEAD", { cwd: dirname(fileURLToPath(import.meta.url)) })).stdout.substring(0, 7)})` : ""}` }, { - "name": "Cluster Memory Usage", - "value": stats?.clusters[this.cluster] ? `${stats.clusters[this.cluster].ram.toFixed(2)} MB` : `${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)} MB`, + "name": "Process Memory Usage", + "value": `${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)} MB`, "inline": true }, { "name": "Total Memory Usage", - "value": stats?.totalRam ? `${stats.totalRam.toFixed(2)} MB` : "Unknown", + "value": process.env.PM2_USAGE ? `${((await this.list()).reduce((prev, cur) => prev + cur.monit.memory, 0) / 1024 / 1024).toFixed(2)} MB` : "Unknown", "inline": true }, { @@ -65,14 +67,9 @@ class StatsCommand extends Command { "value": this.channel.guild ? this.client.guildShardMap[this.channel.guild.id] : "N/A", "inline": true }, - { - "name": "Cluster", - "value": this.cluster, - "inline": true - }, { "name": "Servers", - "value": stats?.guilds ? stats.guilds : `${this.client.guilds.size} (for this cluster only)`, + "value": servers ? servers : `${this.client.guilds.size} (for this process only)`, "inline": true } ] @@ -80,6 +77,15 @@ class StatsCommand extends Command { }; } + list() { + return new Promise((resolve, reject) => { + pm2.list((err, list) => { + if (err) return reject(err); + resolve(list.filter((v) => v.name === "esmBot")); + }); + }); + } + static description = "Gets some statistics about me"; static aliases = ["status", "stat"]; } diff --git a/commands/general/userinfo.js b/commands/general/userinfo.js index 8e3a521..e8b1fe5 100644 --- a/commands/general/userinfo.js +++ b/commands/general/userinfo.js @@ -2,7 +2,7 @@ import Command from "../../classes/command.js"; class UserInfoCommand extends Command { async run() { - const getUser = this.message.mentions.length >= 1 ? this.message.mentions[0] : (this.args.length !== 0 ? await this.ipc.fetchUser(this.args[0]) : this.author); + const getUser = this.message.mentions.length >= 1 ? this.message.mentions[0] : (this.args.length !== 0 ? this.client.users.get(this.args[0]) : this.author); let user; if (getUser) { user = getUser; diff --git a/commands/music/host.js b/commands/music/host.js index b68948a..ccafba8 100644 --- a/commands/music/host.js +++ b/commands/music/host.js @@ -12,7 +12,7 @@ class HostCommand extends MusicCommand { if (input?.trim()) { let user; if (this.type === "classic") { - const getUser = this.message.mentions.length >= 1 ? this.message.mentions[0] : (await this.ipc.fetchUser(input)); + const getUser = this.message.mentions.length >= 1 ? this.message.mentions[0] : this.client.users.get(input); if (getUser) { user = getUser; } else if (input.match(/^?$/) && input >= 21154535154122752n) { diff --git a/commands/music/music.js b/commands/music/music.js index cdc3fcc..797784d 100644 --- a/commands/music/music.js +++ b/commands/music/music.js @@ -15,7 +15,7 @@ class MusicAIOCommand extends Command { if (aliases.has(cmd)) cmd = aliases.get(cmd); if (commands.has(cmd) && info.get(cmd).category === "music") { const command = commands.get(cmd); - const inst = new command(this.client, this.cluster, this.worker, this.ipc, this.origOptions); + const inst = new command(this.client, this.origOptions); const result = await inst.run(); this.success = inst.success; return result; diff --git a/commands/tags/tags.js b/commands/tags/tags.js index 18ea7fe..93defa0 100644 --- a/commands/tags/tags.js +++ b/commands/tags/tags.js @@ -44,7 +44,7 @@ class TagsCommand extends Command { if (!tagName || !tagName.trim()) return "You need to provide the name of the tag you want to check the owner of!"; const getResult = await database.getTag(this.channel.guild.id, tagName); if (!getResult) return "This tag doesn't exist!"; - const user = await this.ipc.fetchUser(getResult.author); + const user = this.client.users.get(getResult.author); this.success = true; if (!user) { try { diff --git a/docs/config.md b/docs/config.md index 14e4731..f2caa93 100644 --- a/docs/config.md +++ b/docs/config.md @@ -22,9 +22,7 @@ These variables that are not necessarily required for the bot to run, but can gr - `TMP_DOMAIN`: The root domain/directory that the images larger than 8MB are stored at. Example: `https://projectlounge.pw/tmp` - `THRESHOLD`: A filesize threshold that the bot will start deleting old files in `TEMPDIR` at. - `METRICS`: The HTTP port to serve [Prometheus](https://prometheus.io/)-compatible metrics on. -- `API_TYPE`: Set this to "none" if you want to process all images locally. Alternatively, set it to "ws" to use an image API server specified in the `image` block of `config/servers.json`, or "azure" to use the Azure Functions-based API. -- `AZURE_URL`: Your Azure webhook URL. Only applies if `API` is set to "azure". -- `AZURE_PASS`: An optional password used for Azure requests. Only applies if `API` is set to "azure". +- `API_TYPE`: Set this to "none" if you want to process all images locally. Alternatively, set it to "ws" to use an image API server specified in the `image` block of `config/servers.json`. - `ADMIN_SERVER`: A Discord server/guild ID to limit owner-only commands such as eval to. ## JSON diff --git a/docs/custom-commands.md b/docs/custom-commands.md index 605a2bf..324666e 100644 --- a/docs/custom-commands.md +++ b/docs/custom-commands.md @@ -45,9 +45,6 @@ The default command name is the same as the filename that you save it as, exclud The parameters available to your command consist of the following: - `this.client`: An instance of an Eris [`Client`](https://abal.moe/Eris/docs/Client), useful for getting info or performing lower-level communication with the Discord API. -- `this.cluster`: The ID of the eris-fleet cluster that the command is being run from. This should be a number greater than or equal to 0. -- `this.worker`: The ID of the current eris-fleet worker. This should be a number greater than or equal to 0. -- `this.ipc`: An eris-fleet [`IPC`](https://danclay.github.io/eris-fleet/classes/IPC.html) instance, useful for communication between worker processes. - `this.origOptions`: The raw options object provided to the command by the command handler. - `this.type`: The type of message that activated the command. Can be "classic" (a regular message) or "application" (slash/context menu commands). - `this.channel`: An Eris [`TextChannel`](https://abal.moe/Eris/docs/TextChannel) object of the channel that the command was run in, useful for getting info about a server and how to respond to a message. diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs new file mode 100644 index 0000000..6bf645b --- /dev/null +++ b/ecosystem.config.cjs @@ -0,0 +1,17 @@ +module.exports = { + apps: [{ + name: "esmBot-manager", + script: "ext.js", + autorestart: true, + exp_backoff_restart_delay: 1000, + watch: false, + exec_mode: "fork" + }, { + name: "esmBot", + script: "app.js", + autorestart: true, + exp_backoff_restart_delay: 1000, + watch: false, + exec_mode: "cluster" + }] +}; \ No newline at end of file diff --git a/events/debug.js b/events/debug.js index 33d5f55..aa77abb 100644 --- a/events/debug.js +++ b/events/debug.js @@ -1,5 +1,5 @@ import { debug } from "../utils/logger.js"; -export default async (client, cluster, worker, ipc, message) => { +export default async (client, message) => { debug(message); }; \ No newline at end of file diff --git a/events/error.js b/events/error.js new file mode 100644 index 0000000..71e1e84 --- /dev/null +++ b/events/error.js @@ -0,0 +1,5 @@ +import { error } from "../utils/logger.js"; + +export default async (client, message) => { + error(message); +}; \ No newline at end of file diff --git a/events/guildCreate.js b/events/guildCreate.js index ca7b411..b0f0393 100644 --- a/events/guildCreate.js +++ b/events/guildCreate.js @@ -2,7 +2,7 @@ import db from "../utils/database.js"; import { log } from "../utils/logger.js"; // run when the bot is added to a guild -export default async (client, cluster, worker, ipc, guild) => { +export default async (client, guild) => { log(`[GUILD JOIN] ${guild.name} (${guild.id}) added the bot.`); const guildDB = await db.getGuild(guild.id); if (!guildDB) await db.addGuild(guild); diff --git a/events/guildDelete.js b/events/guildDelete.js index 7d9d5b6..8bb2352 100644 --- a/events/guildDelete.js +++ b/events/guildDelete.js @@ -1,6 +1,6 @@ import { log } from "../utils/logger.js"; // run when the bot is removed from a guild -export default async (client, cluster, worker, ipc, guild) => { +export default async (client, guild) => { log(`[GUILD LEAVE] ${guild.name} (${guild.id}) removed the bot.`); }; diff --git a/events/interactionCreate.js b/events/interactionCreate.js index 7149055..d76098b 100644 --- a/events/interactionCreate.js +++ b/events/interactionCreate.js @@ -5,7 +5,7 @@ import { clean } from "../utils/misc.js"; import { upload } from "../utils/tempimages.js"; // run when a slash command is executed -export default async (client, cluster, worker, ipc, interaction) => { +export default async (client, interaction) => { if (interaction?.type !== 2) return; // check if command exists and if it's enabled @@ -23,7 +23,7 @@ export default async (client, cluster, worker, ipc, interaction) => { try { await database.addCount(command); // eslint-disable-next-line no-unused-vars - const commandClass = new cmd(client, cluster, worker, ipc, { type: "application", interaction }); + const commandClass = new cmd(client, { type: "application", interaction }); const result = await commandClass.run(); const replyMethod = interaction.acknowledged ? "editOriginalMessage" : "createMessage"; if (typeof result === "string") { @@ -39,7 +39,7 @@ export default async (client, cluster, worker, ipc, interaction) => { const fileSize = 8388119; if (result.file.length > fileSize) { if (process.env.TEMPDIR && process.env.TEMPDIR !== "") { - await upload(client, ipc, result, interaction, true); + await upload(client, result, interaction, true); } else { await interaction[replyMethod]({ content: "The resulting image was more than 8MB in size, so I can't upload it.", diff --git a/events/messageCreate.js b/events/messageCreate.js index 3d1347a..e556918 100644 --- a/events/messageCreate.js +++ b/events/messageCreate.js @@ -6,7 +6,7 @@ import { clean } from "../utils/misc.js"; import { upload } from "../utils/tempimages.js"; // run when someone sends a message -export default async (client, cluster, worker, ipc, message) => { +export default async (client, message) => { // ignore other bots if (message.author.bot) return; @@ -104,7 +104,7 @@ export default async (client, cluster, worker, ipc, message) => { await database.addCount(aliases.get(command) ?? command); const startTime = new Date(); // eslint-disable-next-line no-unused-vars - const commandClass = new cmd(client, cluster, worker, ipc, { type: "classic", message, args: parsed._, content: message.content.substring(prefix.length).trim().replace(command, "").trim(), specialArgs: (({ _, ...o }) => o)(parsed) }); // we also provide the message content as a parameter for cases where we need more accuracy + const commandClass = new cmd(client, { type: "classic", message, args: parsed._, content: message.content.substring(prefix.length).trim().replace(command, "").trim(), specialArgs: (({ _, ...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; @@ -129,7 +129,7 @@ export default async (client, cluster, worker, ipc, message) => { } if (result.file.length > fileSize) { if (process.env.TEMPDIR && process.env.TEMPDIR !== "") { - await upload(client, ipc, result, message); + await upload(client, result, message); } else { await client.createMessage(message.channel.id, "The resulting image was more than 8MB in size, so I can't upload it."); } diff --git a/events/voiceChannelLeave.js b/events/voiceChannelLeave.js index 8d03784..3b143b2 100644 --- a/events/voiceChannelLeave.js +++ b/events/voiceChannelLeave.js @@ -4,7 +4,7 @@ import { random } from "../utils/misc.js"; const isWaiting = new Map(); -export default async (client, cluster, worker, ipc, member, oldChannel) => { +export default async (client, member, oldChannel) => { if (!oldChannel) return; const connection = players.get(oldChannel.guild.id); if (oldChannel.id === connection?.voiceChannel.id) { diff --git a/events/voiceChannelSwitch.js b/events/voiceChannelSwitch.js index 3b5e673..3ab416e 100644 --- a/events/voiceChannelSwitch.js +++ b/events/voiceChannelSwitch.js @@ -1,5 +1,5 @@ import leaveHandler from "./voiceChannelLeave.js"; -export default async (client, cluster, worker, ipc, member, newChannel, oldChannel) => { - await leaveHandler(client, cluster, worker, ipc, member, oldChannel); +export default async (client, member, newChannel, oldChannel) => { + await leaveHandler(client, member, oldChannel); }; \ No newline at end of file diff --git a/events/warn.js b/events/warn.js new file mode 100644 index 0000000..ca1556e --- /dev/null +++ b/events/warn.js @@ -0,0 +1,5 @@ +import { warn } from "../utils/logger.js"; + +export default async (client, message) => { + warn(message); +}; \ No newline at end of file diff --git a/package.json b/package.json index 2fb2933..887e228 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "dotenv": "^16.0.2", "emoji-regex": "^10.1.0", "eris": "github:esmBot/eris#dev", - "eris-fleet": "github:esmBot/eris-fleet#a19920f", "file-type": "^17.1.6", "format-duration": "^2.0.0", "jsqr": "^1.4.0", @@ -57,6 +56,7 @@ "better-sqlite3": "^7.6.2", "bufferutil": "^4.0.6", "erlpack": "github:abalabahaha/erlpack", + "pm2": "^5.2.0", "postgres": "^3.2.4", "uuid": "^8.3.2", "ws": "^8.8.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 63a8da4..55bc05b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,7 +12,6 @@ specifiers: dotenv: ^16.0.2 emoji-regex: ^10.1.0 eris: github:esmBot/eris#dev - eris-fleet: github:esmBot/eris-fleet#a19920f erlpack: github:abalabahaha/erlpack eslint: ^8.23.1 eslint-plugin-unicorn: ^42.0.0 @@ -21,6 +20,7 @@ specifiers: jsqr: ^1.4.0 node-addon-api: ^5.0.0 node-emoji: ^1.11.0 + pm2: ^5.2.0 postgres: ^3.2.4 qrcode: ^1.5.1 sharp: ^0.30.7 @@ -38,7 +38,6 @@ dependencies: dotenv: 16.0.2 emoji-regex: 10.1.0 eris: github.com/esmBot/eris/fbc637e7f92963d7f9e57c86223e995269e70de0_bufferutil@4.0.6 - eris-fleet: github.com/esmBot/eris-fleet/a19920fa0b2723e6afb6a9d31375b419a8743e82_eris@0.17.2-dev file-type: 17.1.6 format-duration: 2.0.0 jsqr: 1.4.0 @@ -46,7 +45,7 @@ dependencies: node-emoji: 1.11.0 qrcode: 1.5.1 sharp: 0.30.7 - shoukaku: github.com/Deivu/shoukaku/b6c724bf6e72a5dcf14beb6f51d35bc4b6a17647_bufferutil@4.0.6 + shoukaku: github.com/Deivu/shoukaku/7822080092a13ea4cc71ab7d9f891f5cb933683b_bufferutil@4.0.6 undici: 5.10.0 winston: 3.8.2 winston-daily-rotate-file: 4.7.1_winston@3.8.2 @@ -55,6 +54,7 @@ optionalDependencies: better-sqlite3: 7.6.2 bufferutil: 4.0.6 erlpack: github.com/abalabahaha/erlpack/f7d730debe32c416d1b55b4217f8aef2ade05874 + pm2: 5.2.0_bufferutil@4.0.6 postgres: 3.2.4 uuid: 8.3.2 ws: 8.8.1_bufferutil@4.0.6 @@ -480,10 +480,116 @@ packages: fastq: 1.13.0 dev: true + /@opencensus/core/0.0.8: + resolution: {integrity: sha512-yUFT59SFhGMYQgX0PhoTR0LBff2BEhPrD9io1jWfF/VDbakRfs6Pq60rjv0Z7iaTav5gQlttJCX2+VPxFWCuoQ==} + engines: {node: '>=6.0'} + dependencies: + continuation-local-storage: 3.2.1 + log-driver: 1.2.7 + semver: 5.7.1 + shimmer: 1.2.1 + uuid: 3.4.0 + dev: false + optional: true + + /@opencensus/core/0.0.9: + resolution: {integrity: sha512-31Q4VWtbzXpVUd2m9JS6HEaPjlKvNMOiF7lWKNmXF84yUcgfAFL5re7/hjDmdyQbOp32oGc+RFV78jXIldVz6Q==} + engines: {node: '>=6.0'} + dependencies: + continuation-local-storage: 3.2.1 + log-driver: 1.2.7 + semver: 5.7.1 + shimmer: 1.2.1 + uuid: 3.4.0 + dev: false + optional: true + + /@opencensus/propagation-b3/0.0.8: + resolution: {integrity: sha512-PffXX2AL8Sh0VHQ52jJC4u3T0H6wDK6N/4bg7xh4ngMYOIi13aR1kzVvX1sVDBgfGwDOkMbl4c54Xm3tlPx/+A==} + engines: {node: '>=6.0'} + dependencies: + '@opencensus/core': 0.0.8 + uuid: 3.4.0 + dev: false + optional: true + + /@pm2/agent/2.0.1_bufferutil@4.0.6: + resolution: {integrity: sha512-QKHMm6yexcvdDfcNE7PL9D6uEjoQPGRi+8dh+rc4Hwtbpsbh5IAvZbz3BVGjcd4HaX6pt2xGpOohG7/Y2L4QLw==} + dependencies: + async: 3.2.4 + chalk: 3.0.0 + dayjs: 1.8.36 + debug: 4.3.4 + eventemitter2: 5.0.1 + fast-json-patch: 3.1.1 + fclone: 1.0.11 + nssocket: 0.6.0 + pm2-axon: 4.0.1 + pm2-axon-rpc: 0.7.1 + proxy-agent: 5.0.0 + semver: 7.2.3 + ws: 7.4.6_bufferutil@4.0.6 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + optional: true + + /@pm2/io/5.0.0: + resolution: {integrity: sha512-3rToDVJaRoob5Lq8+7Q2TZFruoEkdORxwzFpZaqF4bmH6Bkd7kAbdPrI/z8X6k1Meq5rTtScM7MmDgppH6aLlw==} + engines: {node: '>=6.0'} + dependencies: + '@opencensus/core': 0.0.9 + '@opencensus/propagation-b3': 0.0.8 + async: 2.6.4 + debug: 4.3.4 + eventemitter2: 6.4.9 + require-in-the-middle: 5.2.0 + semver: 6.3.0 + shimmer: 1.2.1 + signal-exit: 3.0.7 + tslib: 1.9.3 + transitivePeerDependencies: + - supports-color + dev: false + optional: true + + /@pm2/js-api/0.6.7_bufferutil@4.0.6: + resolution: {integrity: sha512-jiJUhbdsK+5C4zhPZNnyA3wRI01dEc6a2GhcQ9qI38DyIk+S+C8iC3fGjcjUbt/viLYKPjlAaE+hcT2/JMQPXw==} + engines: {node: '>=4.0'} + dependencies: + async: 2.6.4 + axios: 0.21.4_debug@4.3.4 + debug: 4.3.4 + eventemitter2: 6.4.9 + ws: 7.4.6_bufferutil@4.0.6 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + optional: true + + /@pm2/pm2-version-check/1.0.4: + resolution: {integrity: sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA==} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + optional: true + /@tokenizer/token/0.3.0: resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} dev: false + /@tootallnate/once/1.1.2: + resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} + engines: {node: '>= 6'} + dev: false + optional: true + /@top-gg/sdk/3.1.3: resolution: {integrity: sha512-jvj3jH8BY35AXKFXkap2cJRuFMOz1yBvBvCvNq90gfvplKWDYqGPQKt+nOhT/hdYc+XcKskY/c5OOWDX0xbg2Q==} dependencies: @@ -505,11 +611,26 @@ packages: acorn: 8.8.0 dev: true + /acorn-walk/8.2.0: + resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + engines: {node: '>=0.4.0'} + dev: false + optional: true + /acorn/8.8.0: resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==} engines: {node: '>=0.4.0'} hasBin: true - dev: true + + /agent-base/6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + optional: true /ajv/6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -520,6 +641,24 @@ packages: uri-js: 4.4.1 dev: true + /amp-message/0.1.2: + resolution: {integrity: sha512-JqutcFwoU1+jhv7ArgW38bqrE+LQdcRv4NxNw0mp0JHQyB6tXesWRjtYKlDgHRY2o3JE5UTaBGUK8kSWUdxWUg==} + dependencies: + amp: 0.3.1 + dev: false + optional: true + + /amp/0.3.1: + resolution: {integrity: sha512-OwIuC4yZaRogHKiuU5WlMR5Xk/jAcpPtawWL05Gj8Lvm2F6mwoJt4O/bHI+DHwG79vWd+8OFYM4/BzYqyRd3qw==} + dev: false + optional: true + + /ansi-colors/4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + dev: false + optional: true + /ansi-regex/2.1.1: resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==} engines: {node: '>=0.10.0'} @@ -546,6 +685,15 @@ packages: resolution: {integrity: sha512-iFY7JCgHbepc0b82yLaw4IMortylNb6wG4kL+4R0C3iv6i+RHGHux/yUX5BTiRvSX/shMnngjR1YyNMnXEFh5A==} dev: false + /anymatch/3.1.2: + resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: false + optional: true + /are-we-there-yet/1.0.6: resolution: {integrity: sha512-Zfw6bteqM9gQXZ1BIWOgM8xEwMrUGoyL8nW13+O+OOgNX3YhuDN1GDgg1NzdTlmm3j+9sHy7uBZ12r+z9lXnZQ==} dependencies: @@ -553,6 +701,13 @@ packages: readable-stream: 2.3.7 dev: false + /argparse/1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + dependencies: + sprintf-js: 1.0.3 + dev: false + optional: true + /argparse/2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true @@ -562,6 +717,30 @@ packages: engines: {node: '>=8'} dev: true + /ast-types/0.13.4: + resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} + engines: {node: '>=4'} + dependencies: + tslib: 2.4.0 + dev: false + optional: true + + /async-listener/0.6.10: + resolution: {integrity: sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==} + engines: {node: <=0.11.8 || >0.11.10} + dependencies: + semver: 5.7.1 + shimmer: 1.2.1 + dev: false + optional: true + + /async/2.6.4: + resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==} + dependencies: + lodash: 4.17.21 + dev: false + optional: true + /async/3.2.4: resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} dev: false @@ -595,6 +774,12 @@ packages: engines: {node: '>=0.6'} dev: false + /binary-extensions/2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: false + optional: true + /binary/0.3.0: resolution: {integrity: sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==} dependencies: @@ -617,6 +802,13 @@ packages: readable-stream: 3.6.0 dev: false + /blessed/0.1.81: + resolution: {integrity: sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ==} + engines: {node: '>= 0.8.0'} + hasBin: true + dev: false + optional: true + /bluebird/3.4.7: resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} dev: false @@ -625,6 +817,11 @@ packages: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} dev: false + /bodec/0.1.0: + resolution: {integrity: sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ==} + dev: false + optional: true + /brace-expansion/1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -636,7 +833,6 @@ packages: engines: {node: '>=8'} dependencies: fill-range: 7.0.1 - dev: true /browserslist/4.21.3: resolution: {integrity: sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==} @@ -649,6 +845,11 @@ packages: update-browserslist-db: 1.0.8_browserslist@4.21.3 dev: true + /buffer-from/1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: false + optional: true + /buffer-indexof-polyfill/1.0.2: resolution: {integrity: sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==} engines: {node: '>=0.10'} @@ -722,6 +923,15 @@ packages: supports-color: 5.5.0 dev: true + /chalk/3.0.0: + resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: false + optional: true + /chalk/4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -730,6 +940,27 @@ packages: supports-color: 7.2.0 dev: true + /charm/0.1.2: + resolution: {integrity: sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==} + dev: false + optional: true + + /chokidar/3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.2 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.2 + dev: false + optional: true + /chownr/1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} dev: false @@ -745,6 +976,14 @@ packages: escape-string-regexp: 1.0.5 dev: true + /cli-tableau/2.0.1: + resolution: {integrity: sha512-he+WTicka9cl0Fg/y+YyxcN6/bfQ/1O3QmgxRXDhABKqLzvoOSM4fMzp39uMyLBulAFuywD2N7UaoQE7WaADxQ==} + engines: {node: '>=8.10.0'} + dependencies: + chalk: 3.0.0 + dev: false + optional: true + /cliui/3.2.0: resolution: {integrity: sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==} dependencies: @@ -837,9 +1076,22 @@ packages: text-hex: 1.0.0 dev: false + /commander/2.15.1: + resolution: {integrity: sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==} + dev: false + optional: true + /concat-map/0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + /continuation-local-storage/3.2.1: + resolution: {integrity: sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==} + dependencies: + async-listener: 0.6.10 + emitter-listener: 1.1.2 + dev: false + optional: true + /convert-source-map/1.8.0: resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==} dependencies: @@ -850,6 +1102,11 @@ packages: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: false + /croner/4.1.97: + resolution: {integrity: sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==} + dev: false + optional: true + /cross-spawn/7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -859,6 +1116,34 @@ packages: which: 2.0.2 dev: true + /culvert/0.1.2: + resolution: {integrity: sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==} + dev: false + optional: true + + /data-uri-to-buffer/3.0.1: + resolution: {integrity: sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==} + engines: {node: '>= 6'} + dev: false + optional: true + + /dayjs/1.8.36: + resolution: {integrity: sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw==} + dev: false + optional: true + + /debug/3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + dev: false + optional: true + /debug/4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -889,7 +1174,17 @@ packages: /deep-is/0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - dev: true + + /degenerator/3.0.2: + resolution: {integrity: sha512-c0mef3SNQo56t6urUU6tdQAs+ThoD0o9B9MJ8HEt7NQcGEILCRFqQb7ZbP9JAv+QF1Ky5plydhMR/IrqWDm+TQ==} + engines: {node: '>= 6'} + dependencies: + ast-types: 0.13.4 + escodegen: 1.14.3 + esprima: 4.0.1 + vm2: 3.9.11 + dev: false + optional: true /delegates/1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} @@ -931,13 +1226,20 @@ packages: /duplexer2/0.1.4: resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} dependencies: - readable-stream: 2.1.5 + readable-stream: 2.3.7 dev: false /electron-to-chromium/1.4.247: resolution: {integrity: sha512-FLs6R4FQE+1JHM0hh3sfdxnYjKvJpHZyhQDjc2qFq/xFvmmRt/TATNToZhrcGUFzpF2XjeiuozrA8lI0PZmYYw==} dev: true + /emitter-listener/1.1.2: + resolution: {integrity: sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==} + dependencies: + shimmer: 1.2.1 + dev: false + optional: true + /emoji-regex/10.1.0: resolution: {integrity: sha512-xAEnNCT3w2Tg6MA7ly6QqYJvEoY1tm9iIjJ3yMKK9JPlWuRHAMoe5iETwQnx3M9TVbFMfsrBgWKR+IsmswwNjg==} dev: false @@ -960,6 +1262,14 @@ packages: once: 1.4.0 dev: false + /enquirer/2.3.6: + resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} + engines: {node: '>=8.6'} + dependencies: + ansi-colors: 4.1.3 + dev: false + optional: true + /error-ex/1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: @@ -979,7 +1289,20 @@ packages: /escape-string-regexp/4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - dev: true + + /escodegen/1.14.3: + resolution: {integrity: sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==} + engines: {node: '>=4.0'} + hasBin: true + dependencies: + esprima: 4.0.1 + estraverse: 4.3.0 + esutils: 2.0.3 + optionator: 0.8.3 + optionalDependencies: + source-map: 0.6.1 + dev: false + optional: true /eslint-plugin-unicorn/42.0.0_eslint@8.23.1: resolution: {integrity: sha512-ixBsbhgWuxVaNlPTT8AyfJMlhyC5flCJFjyK3oKE8TRrwBnaHvUbuIkCM1lqg8ryYrFStL/T557zfKzX4GKSlg==} @@ -1102,6 +1425,13 @@ packages: eslint-visitor-keys: 3.3.0 dev: true + /esprima/4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + dev: false + optional: true + /esquery/1.4.0: resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} engines: {node: '>=0.10'} @@ -1119,7 +1449,6 @@ packages: /estraverse/4.3.0: resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} engines: {node: '>=4.0'} - dev: true /estraverse/5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} @@ -1129,7 +1458,21 @@ packages: /esutils/2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - dev: true + + /eventemitter2/0.4.14: + resolution: {integrity: sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==} + dev: false + optional: true + + /eventemitter2/5.0.1: + resolution: {integrity: sha512-5EM1GHXycJBS6mauYAbVKT1cVs7POKWb2NXD4Vyt8dDqeZa7LaDK1/sjtL+Zb0lzTpSNil4596Dyu97hz37QLg==} + dev: false + optional: true + + /eventemitter2/6.4.9: + resolution: {integrity: sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==} + dev: false + optional: true /expand-template/2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} @@ -1151,13 +1494,17 @@ packages: micromatch: 4.0.5 dev: true + /fast-json-patch/3.1.1: + resolution: {integrity: sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==} + dev: false + optional: true + /fast-json-stable-stringify/2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: true /fast-levenshtein/2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - dev: true /fastq/1.13.0: resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} @@ -1165,6 +1512,11 @@ packages: reusify: 1.0.4 dev: true + /fclone/1.0.11: + resolution: {integrity: sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==} + dev: false + optional: true + /fecha/4.2.3: resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} dev: false @@ -1196,12 +1548,17 @@ packages: dev: false optional: true + /file-uri-to-path/2.0.0: + resolution: {integrity: sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg==} + engines: {node: '>= 6'} + dev: false + optional: true + /fill-range/7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 - dev: true /find-up/4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} @@ -1262,6 +1619,16 @@ packages: universalify: 0.1.2 dev: false + /fs-extra/8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + dependencies: + graceful-fs: 4.2.10 + jsonfile: 4.0.0 + universalify: 0.1.2 + dev: false + optional: true + /fs-minipass/1.2.7: resolution: {integrity: sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==} dependencies: @@ -1271,6 +1638,14 @@ packages: /fs.realpath/1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + /fsevents/2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: false + optional: true + /fstream/1.0.12: resolution: {integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==} engines: {node: '>=0.6'} @@ -1281,9 +1656,17 @@ packages: rimraf: 2.7.1 dev: false + /ftp/0.3.10: + resolution: {integrity: sha512-faFVML1aBx2UoDStmLwv2Wptt4vw5x03xxX172nhA5Y5HBshW5JweqQ2W4xL4dezQTG8inJsuYcpPHHU3X5OTQ==} + engines: {node: '>=0.8.0'} + dependencies: + readable-stream: 1.1.14 + xregexp: 2.0.0 + dev: false + optional: true + /function-bind/1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - dev: true /gauge/1.2.7: resolution: {integrity: sha512-fVbU2wRE91yDvKUnrIaQlHKAWKY5e08PmztCrwuH5YVQ+Z/p3d0ny2T48o6uvAAXHIUnfaQdHkmxYbQft1eHVA==} @@ -1305,6 +1688,38 @@ packages: engines: {node: 6.* || 8.* || >= 10.*} dev: false + /get-uri/3.0.2: + resolution: {integrity: sha512-+5s0SJbGoyiJTZZ2JTpFPLMPSch72KEqGOTvQsBqg0RBWvwhWUSYZFAtz3TPW0GXJuLBJPts1E241iHg+VRfhg==} + engines: {node: '>= 6'} + dependencies: + '@tootallnate/once': 1.1.2 + data-uri-to-buffer: 3.0.1 + debug: 4.3.4 + file-uri-to-path: 2.0.0 + fs-extra: 8.1.0 + ftp: 0.3.10 + transitivePeerDependencies: + - supports-color + dev: false + optional: true + + /git-node-fs/1.0.0_js-git@0.7.8: + resolution: {integrity: sha512-bLQypt14llVXBg0S0u8q8HmU7g9p3ysH+NvVlae5vILuUvs759665HvmR5+wb04KjHyjFcDRxdYb4kyNnluMUQ==} + peerDependencies: + js-git: ^0.7.8 + peerDependenciesMeta: + js-git: + optional: true + dependencies: + js-git: 0.7.8 + dev: false + optional: true + + /git-sha1/0.1.2: + resolution: {integrity: sha512-2e/nZezdVlyCopOCYHeW0onkbZg7xP1Ad6pndPy1rCygeRykefUS6r7oA5cJRGEFvseiaz5a/qUHFVX1dd6Isg==} + dev: false + optional: true + /github-from-package/0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} dev: false @@ -1314,7 +1729,6 @@ packages: engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 - dev: true /glob-parent/6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} @@ -1373,7 +1787,6 @@ packages: /has-flag/4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - dev: true /has-unicode/2.0.1: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} @@ -1384,7 +1797,6 @@ packages: engines: {node: '>= 0.4.0'} dependencies: function-bind: 1.1.1 - dev: true /hosted-git-info/2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} @@ -1401,6 +1813,29 @@ packages: toidentifier: 1.0.1 dev: false + /http-proxy-agent/4.0.1: + resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} + engines: {node: '>= 6'} + dependencies: + '@tootallnate/once': 1.1.2 + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + optional: true + + /https-proxy-agent/5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + optional: true + /iconv-lite/0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -1453,6 +1888,16 @@ packages: engines: {node: '>=0.10.0'} dev: false + /ip/1.1.8: + resolution: {integrity: sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==} + dev: false + optional: true + + /ip/2.0.0: + resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==} + dev: false + optional: true + /is-arrayish/0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} dev: true @@ -1461,6 +1906,14 @@ packages: resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} dev: false + /is-binary-path/2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: false + optional: true + /is-builtin-module/3.2.0: resolution: {integrity: sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw==} engines: {node: '>=6'} @@ -1472,12 +1925,10 @@ packages: resolution: {integrity: sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==} dependencies: has: 1.0.3 - dev: true /is-extglob/2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} - dev: true /is-fullwidth-code-point/1.0.0: resolution: {integrity: sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==} @@ -1496,7 +1947,6 @@ packages: engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 - dev: true /is-iojs/1.1.0: resolution: {integrity: sha512-tLn1j3wYSL6DkvEI+V/j0pKohpa5jk+ER74v6S4SgCXnjS0WA+DoZbwZBrrhgwksMvtuwndyGeG5F8YMsoBzSA==} @@ -1505,7 +1955,6 @@ packages: /is-number/7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - dev: true /is-stream/2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} @@ -1523,6 +1972,16 @@ packages: /isexe/2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + /js-git/0.7.8: + resolution: {integrity: sha512-+E5ZH/HeRnoc/LW0AmAyhU+mNcWBzAKE+30+IDMLSLbbK+Tdt02AdkOKq9u15rlJsDEGFqtgckc8ZM59LhhiUA==} + dependencies: + bodec: 0.1.0 + culvert: 0.1.2 + git-sha1: 0.1.2 + pako: 0.2.9 + dev: false + optional: true + /js-sdsl/4.1.4: resolution: {integrity: sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==} dev: true @@ -1556,6 +2015,11 @@ packages: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} dev: true + /json-stringify-safe/5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + dev: false + optional: true + /json5/2.2.1: resolution: {integrity: sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==} engines: {node: '>=6'} @@ -1576,6 +2040,12 @@ packages: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} dev: false + /lazy/1.0.11: + resolution: {integrity: sha512-Y+CjUfLmIpoUCCRl0ub4smrYtGGr5AOa2AKOaWelGHOGz33X/Y/KizefGqbkwfz44+cnq/+9habclf8vOmu2LA==} + engines: {node: '>=0.2.0'} + dev: false + optional: true + /lcid/1.0.0: resolution: {integrity: sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw==} engines: {node: '>=0.10.0'} @@ -1583,6 +2053,15 @@ packages: invert-kv: 1.0.0 dev: false + /levn/0.3.0: + resolution: {integrity: sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.1.2 + type-check: 0.3.2 + dev: false + optional: true + /levn/0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -1631,6 +2110,12 @@ packages: /lodash/4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + /log-driver/1.2.7: + resolution: {integrity: sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==} + engines: {node: '>=0.8.6'} + dev: false + optional: true + /logform/2.4.2: resolution: {integrity: sha512-W4c9himeAwXEdZ05dQNerhFz2XG80P9Oj0loPUMV23VC2it0orMHQhJm4hdnnor3rd1HsGf6a2lPwBM1zeXHGw==} dependencies: @@ -1641,6 +2126,13 @@ packages: triple-beam: 1.3.0 dev: false + /lru-cache/5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + dependencies: + yallist: 3.1.1 + dev: false + optional: true + /lru-cache/6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -1709,6 +2201,18 @@ packages: minimist: 1.2.6 dev: false + /mkdirp/1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + dev: false + optional: true + + /module-details-from-path/1.0.3: + resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} + dev: false + optional: true + /moment/2.29.4: resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} dev: false @@ -1720,6 +2224,11 @@ packages: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} dev: false + /mute-stream/0.0.8: + resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + dev: false + optional: true + /nan/2.16.0: resolution: {integrity: sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==} dev: false @@ -1733,6 +2242,25 @@ packages: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true + /needle/2.4.0: + resolution: {integrity: sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==} + engines: {node: '>= 4.4.x'} + hasBin: true + dependencies: + debug: 3.2.7 + iconv-lite: 0.4.24 + sax: 1.2.4 + transitivePeerDependencies: + - supports-color + dev: false + optional: true + + /netmask/2.0.2: + resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} + engines: {node: '>= 0.4.0'} + dev: false + optional: true + /node-abi/3.24.0: resolution: {integrity: sha512-YPG3Co0luSu6GwOBsmIdGW6Wx0NyNDLg/hriIyDllVsNwnI6UeqaWShxC3lbH4LtEQUgoLP3XR1ndXiDAWvmRw==} engines: {node: '>=10'} @@ -1780,6 +2308,12 @@ packages: validate-npm-package-license: 3.0.4 dev: true + /normalize-path/3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: false + optional: true + /npmlog/1.2.1: resolution: {integrity: sha512-1J5KqSRvESP6XbjPaXt2H6qDzgizLTM7x0y1cXIjP2PpvdCqyNC7TO3cPRKsuYlElbi/DwkzRRdG2zpmE0IktQ==} dependencies: @@ -1788,6 +2322,15 @@ packages: gauge: 1.2.7 dev: false + /nssocket/0.6.0: + resolution: {integrity: sha512-a9GSOIql5IqgWJR3F/JXG4KpJTA3Z53Cj0MeMvGpglytB1nxE4PdFNC0jINe27CS7cGivoynwc054EzCcT3M3w==} + engines: {node: '>= 0.10.x'} + dependencies: + eventemitter2: 0.4.14 + lazy: 1.0.11 + dev: false + optional: true + /number-is-nan/1.0.1: resolution: {integrity: sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==} engines: {node: '>=0.10.0'} @@ -1809,6 +2352,19 @@ packages: fn.name: 1.1.0 dev: false + /optionator/0.8.3: + resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==} + engines: {node: '>= 0.8.0'} + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.3.0 + prelude-ls: 1.1.2 + type-check: 0.3.2 + word-wrap: 1.2.3 + dev: false + optional: true + /optionator/0.9.1: resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} engines: {node: '>= 0.8.0'} @@ -1858,6 +2414,39 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + /pac-proxy-agent/5.0.0: + resolution: {integrity: sha512-CcFG3ZtnxO8McDigozwE3AqAw15zDvGH+OjXO4kzf7IkEKkQ4gxQ+3sdF50WmhQ4P/bVusXcqNE2S3XrNURwzQ==} + engines: {node: '>= 8'} + dependencies: + '@tootallnate/once': 1.1.2 + agent-base: 6.0.2 + debug: 4.3.4 + get-uri: 3.0.2 + http-proxy-agent: 4.0.1 + https-proxy-agent: 5.0.1 + pac-resolver: 5.0.1 + raw-body: 2.5.1 + socks-proxy-agent: 5.0.1 + transitivePeerDependencies: + - supports-color + dev: false + optional: true + + /pac-resolver/5.0.1: + resolution: {integrity: sha512-cy7u00ko2KVgBAjuhevqpPeHIkCIqPe1v24cydhWjmeuzaBfmUWFCZJ1iAh5TuVzVZoUzXIW7K8sMYOZ84uZ9Q==} + engines: {node: '>= 8'} + dependencies: + degenerator: 3.0.2 + ip: 1.1.8 + netmask: 2.0.2 + dev: false + optional: true + + /pako/0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + dev: false + optional: true + /parent-module/1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -1890,7 +2479,6 @@ packages: /path-parse/1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - dev: true /path-type/4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} @@ -1909,13 +2497,125 @@ packages: /picomatch/2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - dev: true + + /pidusage/2.0.21: + resolution: {integrity: sha512-cv3xAQos+pugVX+BfXpHsbyz/dLzX+lr44zNMsYiGxUw+kV5sgQCIcLd1z+0vq+KyC7dJ+/ts2PsfgWfSC3WXA==} + engines: {node: '>=8'} + dependencies: + safe-buffer: 5.2.1 + dev: false + optional: true + + /pidusage/3.0.0: + resolution: {integrity: sha512-8VJLToXhj+RYZGNVw8oxc7dS54iCQXUJ+MDFHezQ/fwF5B8W4OWodAMboc1wb08S/4LiHwAmkT4ohf/d3YPPsw==} + engines: {node: '>=10'} + dependencies: + safe-buffer: 5.2.1 + dev: false + optional: true /pluralize/8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} dev: true + /pm2-axon-rpc/0.7.1: + resolution: {integrity: sha512-FbLvW60w+vEyvMjP/xom2UPhUN/2bVpdtLfKJeYM3gwzYhoTEEChCOICfFzxkxuoEleOlnpjie+n1nue91bDQw==} + engines: {node: '>=5'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + optional: true + + /pm2-axon/4.0.1: + resolution: {integrity: sha512-kES/PeSLS8orT8dR5jMlNl+Yu4Ty3nbvZRmaAtROuVm9nYYGiaoXqqKQqQYzWQzMYWUKHMQTvBlirjE5GIIxqg==} + engines: {node: '>=5'} + dependencies: + amp: 0.3.1 + amp-message: 0.1.2 + debug: 4.3.4 + escape-string-regexp: 4.0.0 + transitivePeerDependencies: + - supports-color + dev: false + optional: true + + /pm2-deploy/1.0.2: + resolution: {integrity: sha512-YJx6RXKrVrWaphEYf++EdOOx9EH18vM8RSZN/P1Y+NokTKqYAca/ejXwVLyiEpNju4HPZEk3Y2uZouwMqUlcgg==} + engines: {node: '>=4.0.0'} + dependencies: + run-series: 1.1.9 + tv4: 1.3.0 + dev: false + optional: true + + /pm2-multimeter/0.1.2: + resolution: {integrity: sha512-S+wT6XfyKfd7SJIBqRgOctGxaBzUOmVQzTAS+cg04TsEUObJVreha7lvCfX8zzGVr871XwCSnHUU7DQQ5xEsfA==} + dependencies: + charm: 0.1.2 + dev: false + optional: true + + /pm2-sysmonit/1.2.8: + resolution: {integrity: sha512-ACOhlONEXdCTVwKieBIQLSi2tQZ8eKinhcr9JpZSUAL8Qy0ajIgRtsLxG/lwPOW3JEKqPyw/UaHmTWhUzpP4kA==} + requiresBuild: true + dependencies: + async: 3.2.4 + debug: 4.3.4 + pidusage: 2.0.21 + systeminformation: 5.12.6 + tx2: 1.0.5 + transitivePeerDependencies: + - supports-color + dev: false + optional: true + + /pm2/5.2.0_bufferutil@4.0.6: + resolution: {integrity: sha512-PO5hMVhQ85cTszFM++6v07Me9hPJMkFbHjkFigtMMk+La8ty2wCi2dlBTeZYJDhPUSjK8Ccltpq2buNRcyMOTw==} + engines: {node: '>=10.0.0'} + hasBin: true + requiresBuild: true + dependencies: + '@pm2/agent': 2.0.1_bufferutil@4.0.6 + '@pm2/io': 5.0.0 + '@pm2/js-api': 0.6.7_bufferutil@4.0.6 + '@pm2/pm2-version-check': 1.0.4 + async: 3.2.4 + blessed: 0.1.81 + chalk: 3.0.0 + chokidar: 3.5.3 + cli-tableau: 2.0.1 + commander: 2.15.1 + croner: 4.1.97 + dayjs: 1.8.36 + debug: 4.3.4 + enquirer: 2.3.6 + eventemitter2: 5.0.1 + fclone: 1.0.11 + mkdirp: 1.0.4 + needle: 2.4.0 + pidusage: 3.0.0 + pm2-axon: 4.0.1 + pm2-axon-rpc: 0.7.1 + pm2-deploy: 1.0.2 + pm2-multimeter: 0.1.2 + promptly: 2.2.0 + semver: 7.3.7 + source-map-support: 0.5.19 + sprintf-js: 1.1.2 + vizion: 2.2.1 + yamljs: 0.3.0 + optionalDependencies: + pm2-sysmonit: 1.2.8 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + optional: true + /pngjs/5.0.0: resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} engines: {node: '>=10.13.0'} @@ -1946,6 +2646,12 @@ packages: tunnel-agent: 0.6.0 dev: false + /prelude-ls/1.1.2: + resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} + engines: {node: '>= 0.8.0'} + dev: false + optional: true + /prelude-ls/1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -1959,6 +2665,35 @@ packages: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} dev: false + /promptly/2.2.0: + resolution: {integrity: sha512-aC9j+BZsRSSzEsXBNBwDnAxujdx19HycZoKgRgzWnS8eOHg1asuf9heuLprfbe739zY3IdUQx+Egv6Jn135WHA==} + dependencies: + read: 1.0.7 + dev: false + optional: true + + /proxy-agent/5.0.0: + resolution: {integrity: sha512-gkH7BkvLVkSfX9Dk27W6TyNOWWZWRilRfk1XxGNWOYJ2TuedAv1yFpCaU9QSBmBe716XOTNpYNOzhysyw8xn7g==} + engines: {node: '>= 8'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + http-proxy-agent: 4.0.1 + https-proxy-agent: 5.0.1 + lru-cache: 5.1.1 + pac-proxy-agent: 5.0.0 + proxy-from-env: 1.1.0 + socks-proxy-agent: 5.0.1 + transitivePeerDependencies: + - supports-color + dev: false + optional: true + + /proxy-from-env/1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false + optional: true + /pump/3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} dependencies: @@ -2025,6 +2760,14 @@ packages: type-fest: 0.6.0 dev: true + /read/1.0.7: + resolution: {integrity: sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==} + engines: {node: '>=0.8'} + dependencies: + mute-stream: 0.0.8 + dev: false + optional: true + /readable-stream/1.0.34: resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==} dependencies: @@ -2034,6 +2777,16 @@ packages: string_decoder: 0.10.31 dev: false + /readable-stream/1.1.14: + resolution: {integrity: sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==} + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 0.0.1 + string_decoder: 0.10.31 + dev: false + optional: true + /readable-stream/2.1.5: resolution: {integrity: sha512-NkXT2AER7VKXeXtJNSaWLpWIhmtSE3K2PguaLEeWr4JILghcIKqoLt1A3wHrnpDC5+ekf8gfk1GKWkFXe4odMw==} dependencies: @@ -2074,6 +2827,14 @@ packages: readable-stream: 3.6.0 dev: false + /readdirp/3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: false + optional: true + /regexp-tree/0.1.24: resolution: {integrity: sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw==} hasBin: true @@ -2089,6 +2850,18 @@ packages: engines: {node: '>=0.10.0'} dev: false + /require-in-the-middle/5.2.0: + resolution: {integrity: sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==} + engines: {node: '>=6'} + dependencies: + debug: 4.3.4 + module-details-from-path: 1.0.3 + resolve: 1.22.1 + transitivePeerDependencies: + - supports-color + dev: false + optional: true + /require-main-filename/2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} dev: false @@ -2105,7 +2878,6 @@ packages: is-core-module: 2.10.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - dev: true /reusify/1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} @@ -2132,6 +2904,11 @@ packages: queue-microtask: 1.2.3 dev: true + /run-series/1.1.9: + resolution: {integrity: sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==} + dev: false + optional: true + /safe-buffer/5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} @@ -2154,6 +2931,11 @@ packages: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: false + /sax/1.2.4: + resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} + dev: false + optional: true + /semver/5.7.1: resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} hasBin: true @@ -2161,7 +2943,13 @@ packages: /semver/6.3.0: resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} hasBin: true - dev: true + + /semver/7.2.3: + resolution: {integrity: sha512-utbW9Z7ZxVvwiIWkdOMLOR9G/NFXh2aRucghkVrEMJWuC++r3lCkBC3LwqBinyHzGMAJxY5tn6VakZGHObq5ig==} + engines: {node: '>=10'} + hasBin: true + dev: false + optional: true /semver/7.3.7: resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==} @@ -2209,6 +2997,16 @@ packages: engines: {node: '>=8'} dev: true + /shimmer/1.2.1: + resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} + dev: false + optional: true + + /signal-exit/3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + dev: false + optional: true + /simple-concat/1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} dev: false @@ -2232,6 +3030,47 @@ packages: engines: {node: '>=8'} dev: true + /smart-buffer/4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + dev: false + optional: true + + /socks-proxy-agent/5.0.1: + resolution: {integrity: sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ==} + engines: {node: '>= 6'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + socks: 2.7.0 + transitivePeerDependencies: + - supports-color + dev: false + optional: true + + /socks/2.7.0: + resolution: {integrity: sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA==} + engines: {node: '>= 10.13.0', npm: '>= 3.0.0'} + dependencies: + ip: 2.0.0 + smart-buffer: 4.2.0 + dev: false + optional: true + + /source-map-support/0.5.19: + resolution: {integrity: sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: false + optional: true + + /source-map/0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: false + optional: true + /spdx-correct/3.1.1: resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==} dependencies: @@ -2258,6 +3097,16 @@ packages: resolution: {integrity: sha512-UUFYD2oWbNwULH6WoVtLUOw8ch586B+HUqcsAjjjeoBQAM1bD4wZRXu01koaxyd8UeYpybWqW4h+lO1Okv40Tg==} dev: false + /sprintf-js/1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + dev: false + optional: true + + /sprintf-js/1.1.2: + resolution: {integrity: sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==} + dev: false + optional: true + /stack-trace/0.0.10: resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} dev: false @@ -2351,12 +3200,18 @@ packages: engines: {node: '>=8'} dependencies: has-flag: 4.0.0 - dev: true /supports-preserve-symlinks-flag/1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - dev: true + + /systeminformation/5.12.6: + resolution: {integrity: sha512-FkCvT5BOuH1OE3+8lFM25oXIYJ0CM8kq4Wgvz2jyBTrsOIgha/6gdJXgbF4rv+g0j/5wJqQLDKan7kc/p7uIvw==} + engines: {node: '>=8.0.0'} + os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] + hasBin: true + dev: false + optional: true /tar-fs/2.1.1: resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} @@ -2409,7 +3264,6 @@ packages: engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 - dev: true /toidentifier/1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} @@ -2436,12 +3290,43 @@ packages: resolution: {integrity: sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==} dev: false + /tslib/1.9.3: + resolution: {integrity: sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==} + dev: false + optional: true + + /tslib/2.4.0: + resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} + dev: false + optional: true + /tunnel-agent/0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} dependencies: safe-buffer: 5.2.1 dev: false + /tv4/1.3.0: + resolution: {integrity: sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==} + engines: {node: '>= 0.8.0'} + dev: false + optional: true + + /tx2/1.0.5: + resolution: {integrity: sha512-sJ24w0y03Md/bxzK4FU8J8JveYYUbSs2FViLJ2D/8bytSiyPRbuE3DyL/9UKYXTZlV3yXq0L8GLlhobTnekCVg==} + dependencies: + json-stringify-safe: 5.0.1 + dev: false + optional: true + + /type-check/0.3.2: + resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.1.2 + dev: false + optional: true + /type-check/0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -2518,6 +3403,13 @@ packages: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: false + /uuid/3.4.0: + resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} + deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. + hasBin: true + dev: false + optional: true + /uuid/8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true @@ -2532,6 +3424,27 @@ packages: spdx-expression-parse: 3.0.1 dev: true + /vizion/2.2.1: + resolution: {integrity: sha512-sfAcO2yeSU0CSPFI/DmZp3FsFE9T+8913nv1xWBOyzODv13fwkn6Vl7HqxGpkr9F608M+8SuFId3s+BlZqfXww==} + engines: {node: '>=4.0'} + dependencies: + async: 2.6.4 + git-node-fs: 1.0.0_js-git@0.7.8 + ini: 1.3.8 + js-git: 0.7.8 + dev: false + optional: true + + /vm2/3.9.11: + resolution: {integrity: sha512-PFG8iJRSjvvBdisowQ7iVF580DXb1uCIiGaXgm7tynMR1uTBlv7UJlB1zdv5KJ+Tmq1f0Upnj3fayoEOPpCBKg==} + engines: {node: '>=6.0'} + hasBin: true + dependencies: + acorn: 8.8.0 + acorn-walk: 8.2.0 + dev: false + optional: true + /webidl-conversions/3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: false @@ -2610,7 +3523,6 @@ packages: /word-wrap/1.2.3: resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} engines: {node: '>=0.10.0'} - dev: true /wrap-ansi/2.1.0: resolution: {integrity: sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==} @@ -2632,6 +3544,22 @@ packages: /wrappy/1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + /ws/7.4.6_bufferutil@4.0.6: + resolution: {integrity: sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dependencies: + bufferutil: 4.0.6 + dev: false + optional: true + /ws/8.8.1_bufferutil@4.0.6: resolution: {integrity: sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==} engines: {node: '>=10.0.0'} @@ -2647,6 +3575,11 @@ packages: bufferutil: 4.0.6 dev: false + /xregexp/2.0.0: + resolution: {integrity: sha512-xl/50/Cf32VsGq/1R8jJE5ajH1yMCQkpmoS10QbFZWl2Oor4H0Me64Pu2yxvsRWK3m6soJbmGfzSR7BYmDcWAA==} + dev: false + optional: true + /y18n/3.2.2: resolution: {integrity: sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==} dev: false @@ -2662,6 +3595,15 @@ packages: /yallist/4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + /yamljs/0.3.0: + resolution: {integrity: sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==} + hasBin: true + dependencies: + argparse: 1.0.10 + glob: 7.2.3 + dev: false + optional: true + /yargs-parser/18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} @@ -2712,11 +3654,11 @@ packages: dev: false optional: true - github.com/Deivu/shoukaku/b6c724bf6e72a5dcf14beb6f51d35bc4b6a17647_bufferutil@4.0.6: - resolution: {tarball: https://codeload.github.com/Deivu/shoukaku/tar.gz/b6c724bf6e72a5dcf14beb6f51d35bc4b6a17647} - id: github.com/Deivu/shoukaku/b6c724bf6e72a5dcf14beb6f51d35bc4b6a17647 + github.com/Deivu/shoukaku/7822080092a13ea4cc71ab7d9f891f5cb933683b_bufferutil@4.0.6: + resolution: {tarball: https://codeload.github.com/Deivu/shoukaku/tar.gz/7822080092a13ea4cc71ab7d9f891f5cb933683b} + id: github.com/Deivu/shoukaku/7822080092a13ea4cc71ab7d9f891f5cb933683b name: shoukaku - version: 3.1.3 + version: 3.2.0 engines: {node: '>=16.0.0', npm: '>=7.0.0'} prepare: true requiresBuild: true @@ -2739,17 +3681,6 @@ packages: dev: false optional: true - github.com/esmBot/eris-fleet/a19920fa0b2723e6afb6a9d31375b419a8743e82_eris@0.17.2-dev: - resolution: {tarball: https://codeload.github.com/esmBot/eris-fleet/tar.gz/a19920fa0b2723e6afb6a9d31375b419a8743e82} - id: github.com/esmBot/eris-fleet/a19920fa0b2723e6afb6a9d31375b419a8743e82 - name: eris-fleet - version: 1.0.2 - peerDependencies: - eris: ~0.16.0 - dependencies: - eris: github.com/esmBot/eris/fbc637e7f92963d7f9e57c86223e995269e70de0_bufferutil@4.0.6 - dev: false - github.com/esmBot/eris/fbc637e7f92963d7f9e57c86223e995269e70de0_bufferutil@4.0.6: resolution: {tarball: https://codeload.github.com/esmBot/eris/tar.gz/fbc637e7f92963d7f9e57c86223e995269e70de0} id: github.com/esmBot/eris/fbc637e7f92963d7f9e57c86223e995269e70de0 diff --git a/shard.js b/shard.js deleted file mode 100644 index 2a262f6..0000000 --- a/shard.js +++ /dev/null @@ -1,178 +0,0 @@ -// shard base -import { BaseClusterWorker } from "eris-fleet"; -// path stuff -import { readdir } from "fs/promises"; -import { readFileSync } from "fs"; -import { resolve, dirname } from "path"; -import { fileURLToPath } from "url"; -// fancy loggings -import { log, error } from "./utils/logger.js"; -// initialize command loader -import { load, send } from "./utils/handler.js"; -// lavalink stuff -import { checkStatus, connect, reload, status, connected } from "./utils/soundplayer.js"; -// database stuff -import database from "./utils/database.js"; -// command collections -import { paths } from "./utils/collections.js"; -// playing messages -const { messages } = JSON.parse(readFileSync(new URL("./config/messages.json", import.meta.url))); -// command config -const { types } = JSON.parse(readFileSync(new URL("./config/commands.json", import.meta.url))); -// other stuff -import { random } from "./utils/misc.js"; -// generate help page -import { generateList, createPage } from "./utils/help.js"; -// whether a broadcast is currently in effect -let broadcast = false; - -class Shard extends BaseClusterWorker { - constructor(bot) { - super(bot); - - console.info = (str) => this.ipc.sendToAdmiral("info", str); - this.playingSuffix = types.classic ? ` | @${this.bot.user.username} help` : ""; - this.init(); - } - - async init() { - if (!types.classic && !types.application) { - error("Both classic and application commands are disabled! Please enable at least one command type in config/commands.json."); - this.ipc.totalShutdown(true); - return; - } - // register commands and their info - const soundStatus = await checkStatus(); - log("info", "Attempting to load commands..."); - for await (const commandFile of this.getFiles(resolve(dirname(fileURLToPath(import.meta.url)), "./commands/"))) { - log("log", `Loading command from ${commandFile}...`); - try { - await load(this.bot, commandFile, soundStatus); - } catch (e) { - error(`Failed to register command from ${commandFile}: ${e}`); - } - } - if (types.application) { - try { - await send(this.bot); - } catch (e) { - log("error", e); - log("error", "Failed to send command data to Discord, slash/message commands may be unavailable."); - } - } - log("info", "Finished loading commands."); - - await database.setup(this.ipc); - - // register events - log("info", "Attempting to load events..."); - for await (const file of this.getFiles(resolve(dirname(fileURLToPath(import.meta.url)), "./events/"))) { - log("log", `Loading event from ${file}...`); - const eventArray = file.split("/"); - const eventName = eventArray[eventArray.length - 1].split(".")[0]; - if (eventName === "interactionCreate" && !types.application) { - log("warn", `Skipped loading event from ${file} because application commands are disabled`); - continue; - } - const { default: event } = await import(file); - this.bot.on(eventName, event.bind(null, this.bot, this.clusterID, this.workerID, this.ipc)); - } - log("info", "Finished loading events."); - - // generate docs - if (process.env.OUTPUT && process.env.OUTPUT !== "") { - generateList(); - if (this.clusterID === 0) { - await createPage(process.env.OUTPUT); - log("info", "The help docs have been generated."); - } - } - - this.ipc.register("reload", async (message) => { - const path = paths.get(message); - if (!path) return this.ipc.broadcast("reloadFail", { result: "I couldn't find that command!" }); - try { - const result = await load(this.bot, path, await checkStatus(), true); - if (result !== message) return this.ipc.broadcast("reloadFail", { result }); - return this.ipc.broadcast("reloadSuccess"); - } catch (result) { - return this.ipc.broadcast("reloadFail", { result }); - } - }); - - this.ipc.register("soundreload", async () => { - const soundStatus = await checkStatus(); - if (!soundStatus) { - const length = reload(); - return this.ipc.broadcast("soundReloadSuccess", { length }); - } else { - return this.ipc.broadcast("soundReloadFail"); - } - }); - - this.ipc.register("playbroadcast", (message) => { - this.bot.editStatus("dnd", { - name: message + this.playingSuffix, - }); - broadcast = true; - return this.ipc.broadcast("broadcastSuccess"); - }); - - this.ipc.register("broadcastend", () => { - this.bot.editStatus("dnd", { - name: random(messages) + this.playingSuffix, - }); - broadcast = false; - return this.ipc.broadcast("broadcastEnd"); - }); - - // connect to lavalink - if (!status && !connected) connect(this.bot); - - const broadcastMessage = await this.ipc.centralStore.get("broadcast"); - if (broadcastMessage) { - broadcast = true; - this.bot.editStatus("dnd", { - name: broadcastMessage + this.playingSuffix, - }); - } - - this.activityChanger(); - - log("info", `Started worker ${this.workerID}.`); - } - - // set activity (a.k.a. the gamer code) - activityChanger() { - if (!broadcast) { - this.bot.editStatus("dnd", { - name: random(messages) + this.playingSuffix, - }); - } - setTimeout(this.activityChanger.bind(this), 900000); - } - - async* getFiles(dir) { - const dirents = await readdir(dir, { withFileTypes: true }); - for (const dirent of dirents) { - const name = dir + (dir.charAt(dir.length - 1) !== "/" ? "/" : "") + dirent.name; - if (dirent.isDirectory()) { - yield* this.getFiles(name); - } else if (dirent.name.endsWith(".js")) { - yield name; - } - } - } - - shutdown(done) { - log("warn", "Shutting down..."); - this.bot.editStatus("dnd", { - name: "Restarting/shutting down..." - }); - database.stop(); - done(); - } - -} - -export default Shard; diff --git a/utils/help.js b/utils/help.js index 887ad62..7c5729d 100644 --- a/utils/help.js +++ b/utils/help.js @@ -37,11 +37,7 @@ This page was last generated on \`${new Date().toString()}\`. \`[]\` means an argument is required, \`{}\` means an argument is optional. -Default prefix is \`&\`. - **Want to help support esmBot's development? Consider donating on Patreon!** https://patreon.com/TheEssem - -> Tip: You can get much more info about a command by using \`help [command]\` in the bot itself. `; template += "\n## Table of Contents\n"; diff --git a/utils/image.js b/utils/image.js index d2465a6..bf0f0b3 100644 --- a/utils/image.js +++ b/utils/image.js @@ -1,14 +1,22 @@ import { request } from "undici"; import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import { Worker } from "worker_threads"; +import { createRequire } from "module"; import { fileTypeFromBuffer, fileTypeFromFile } from "file-type"; +import * as logger from "./logger.js"; +import ImageConnection from "./imageConnection.js"; + +// only requiring this to work around an issue regarding worker threads +const nodeRequire = createRequire(import.meta.url); +if (!process.env.API_TYPE || process.env.API_TYPE === "none") { + nodeRequire(`../build/${process.env.DEBUG && process.env.DEBUG === "true" ? "Debug" : "Release"}/image.node`); +} const formats = ["image/jpeg", "image/png", "image/webp", "image/gif", "video/mp4", "video/webm", "video/quicktime"]; - -export const jobs = {}; - export const connections = new Map(); - -export const servers = JSON.parse(fs.readFileSync(new URL("../config/servers.json", import.meta.url), { encoding: "utf8" })).image; +export let servers = process.env.API_TYPE === "ws" ? JSON.parse(fs.readFileSync(new URL("../config/servers.json", import.meta.url), { encoding: "utf8" })).image : null; export async function getType(image, extraReturnTypes) { if (!image.startsWith("http")) { @@ -65,3 +73,98 @@ export async function getType(image, extraReturnTypes) { } return type; } + +function connect(server, auth) { + const connection = new ImageConnection(server, auth); + connections.set(server, connection); +} + +function disconnect() { + for (const connection of connections.values()) { + connection.close(); + } + connections.clear(); +} + +async function repopulate() { + const data = await fs.promises.readFile(new URL("../config/servers.json", import.meta.url), { encoding: "utf8" }); + servers = JSON.parse(data).image; +} + +export async function reloadImageConnections() { + disconnect(); + await repopulate(); + let amount = 0; + for (const server of servers) { + try { + connect(server.server, server.auth); + amount += 1; + } catch (e) { + logger.error(e); + } + } + return amount; +} + +function chooseServer(ideal) { + if (ideal.length === 0) throw "No available servers"; + const sorted = ideal.sort((a, b) => { + return a.load - b.load; + }); + return sorted[0]; +} + +async function getIdeal(object) { + const idealServers = []; + for (const [address, connection] of connections) { + if (connection.conn.readyState !== 0 && connection.conn.readyState !== 1) { + continue; + } + if (object.params.type && !connection.formats[object.cmd]?.includes(object.params.type)) continue; + idealServers.push({ + addr: address, + load: await connection.getCount() + }); + } + const server = chooseServer(idealServers); + return connections.get(server.addr); +} + +function waitForWorker(worker) { + return new Promise((resolve, reject) => { + worker.once("message", (data) => { + resolve({ + buffer: Buffer.from([...data.buffer]), + type: data.fileExtension + }); + }); + worker.once("error", reject); + }); +} + +export async function runImageJob(params) { + if (process.env.API_TYPE === "ws") { + for (let i = 0; i < 3; i++) { + const currentServer = await getIdeal(params); + try { + await currentServer.queue(BigInt(params.id), params); + await currentServer.wait(BigInt(params.id)); + const output = await currentServer.getOutput(params.id); + return output; + } catch (e) { + if (i < 2 && e === "Request ended prematurely due to a closed connection") { + continue; + } else { + if (e === "No available servers" && i >= 2) throw "Request ended prematurely due to a closed connection"; + throw e; + } + } + } + } else { + // Called from command (not using image API) + const worker = new Worker(path.join(path.dirname(fileURLToPath(import.meta.url)), "./image-runner.js"), { + workerData: params + }); + return await waitForWorker(worker); + } +} \ No newline at end of file diff --git a/utils/imageConnection.js b/utils/imageConnection.js index 239c1c6..49afc73 100644 --- a/utils/imageConnection.js +++ b/utils/imageConnection.js @@ -22,8 +22,6 @@ class ImageConnection { this.auth = auth; this.tag = 0; this.disconnected = false; - this.njobs = 0; - this.max = 0; this.formats = {}; this.wsproto = null; if (tls) { @@ -43,17 +41,15 @@ class ImageConnection { } else { httpproto = "http"; } - this.httpurl = `${httpproto}://${host}/image`; + this.httpurl = `${httpproto}://${host}`; this.conn.on("message", (msg) => this.onMessage(msg)); this.conn.once("error", (err) => this.onError(err)); this.conn.once("close", () => this.onClose()); } - onMessage(msg) { + async onMessage(msg) { const op = msg.readUint8(0); if (op === Rinit) { - this.max = msg.readUint16LE(3); - this.njobs = msg.readUint16LE(5); this.formats = JSON.parse(msg.toString("utf8", 7)); return; } @@ -64,10 +60,7 @@ class ImageConnection { return; } this.requests.delete(tag); - if (op === Rqueue) this.njobs++; - if (op === Rcancel || op === Rwait) this.njobs--; if (op === Rerror) { - this.njobs--; promise.reject(new Error(msg.slice(3, msg.length).toString())); return; } @@ -82,9 +75,7 @@ class ImageConnection { for (const [tag, obj] of this.requests.entries()) { obj.reject("Request ended prematurely due to a closed connection"); this.requests.delete(tag); - if (obj.op === Twait || obj.op === Tcancel) this.njobs--; } - //this.requests.clear(); if (!this.disconnected) { logger.warn(`Lost connection to ${this.host}, attempting to reconnect in 5 seconds...`); await setTimeout(5000); @@ -107,25 +98,25 @@ class ImageConnection { queue(jobid, jobobj) { const str = JSON.stringify(jobobj); - const buf = Buffer.alloc(4); - buf.writeUint32LE(jobid); + const buf = Buffer.alloc(8); + buf.writeBigUint64LE(jobid); return this.do(Tqueue, jobid, Buffer.concat([buf, Buffer.from(str)])); } wait(jobid) { - const buf = Buffer.alloc(4); - buf.writeUint32LE(jobid); + const buf = Buffer.alloc(8); + buf.writeBigUint64LE(jobid); return this.do(Twait, jobid, buf); } cancel(jobid) { - const buf = Buffer.alloc(4); - buf.writeUint32LE(jobid); + const buf = Buffer.alloc(8); + buf.writeBigUint64LE(jobid); return this.do(Tcancel, jobid, buf); } async getOutput(jobid) { - const req = await request(`${this.httpurl}?id=${jobid}`, { + const req = await request(`${this.httpurl}/image?id=${jobid}`, { headers: { authentication: this.auth || undefined } @@ -149,7 +140,18 @@ class ImageConnection { type = contentType; break; } - return { buffer: Buffer.from(await req.body.arrayBuffer()), type }; + return { arrayBuffer: await req.body.arrayBuffer(), type }; + } + + async getCount() { + const req = await request(`${this.httpurl}/count`, { + headers: { + authentication: this.auth || undefined + } + }); + if (req.statusCode !== 200) return; + const res = parseInt(await req.body.text()); + return res; } async do(op, id, data) { diff --git a/utils/logger.js b/utils/logger.js index 3dbd278..95dfed7 100644 --- a/utils/logger.js +++ b/utils/logger.js @@ -1,4 +1,41 @@ -export function log(type, content) { return content ? console[type](content) : console.info(type); } +import winston from "winston"; +import "winston-daily-rotate-file"; + +export 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 }), stderrLevels: ["error", "warn"] }), + new winston.transports.DailyRotateFile({ filename: "logs/error-%DATE%.log", level: "error", zippedArchive: true, maxSize: 4194304, maxFiles: 8 }), + new winston.transports.DailyRotateFile({ filename: "logs/main-%DATE%.log", zippedArchive: true, maxSize: 4194304, maxFiles: 8 }) + ], + level: process.env.DEBUG_LOG ? "debug" : "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: "magenta", + warn: "yellow", + error: "red" +}); + +export function log(type, content) { return content ? logger.log(type === "log" ? "main" : type, content) : logger.info(type); } export function error(...args) { return log("error", ...args); } diff --git a/utils/misc.js b/utils/misc.js index b5fe9ff..cc831a3 100644 --- a/utils/misc.js +++ b/utils/misc.js @@ -1,7 +1,14 @@ import util from "util"; import fs from "fs"; +import pm2 from "pm2"; import { config } from "dotenv"; +// playing messages +const { messages } = JSON.parse(fs.readFileSync(new URL("../config/messages.json", import.meta.url))); +const { types } = JSON.parse(fs.readFileSync(new URL("../config/commands.json", import.meta.url))); + +let broadcast = false; + // random(array) to select a random entry in array export function random(array) { if (!array || array.length < 1) return null; @@ -41,4 +48,62 @@ export function clean(text) { // textEncode(string) to encode characters for image processing export function textEncode(string) { return string.replaceAll("&", "&").replaceAll(">", ">").replaceAll("<", "<").replaceAll("\"", """).replaceAll("'", "'").replaceAll("\\n", "\n").replaceAll("\\:", ":"); +} + +// set activity (a.k.a. the gamer code) +export function activityChanger(bot) { + if (!broadcast) { + bot.editStatus("dnd", { + name: random(messages) + (types.classic ? ` | @${bot.user.username} help` : ""), + }); + } + setTimeout(() => activityChanger(bot), 900000); +} + +export function checkBroadcast(bot) { + /*if () { + startBroadcast(bot, message); + }*/ +} + +export function startBroadcast(bot, message) { + bot.editStatus("dnd", { + name: message + (types.classic ? ` | @${bot.user.username} help` : ""), + }); + broadcast = true; +} + +export function endBroadcast(bot) { + bot.editStatus("dnd", { + name: random(messages) + (types.classic ? ` | @${bot.user.username} help` : ""), + }); + broadcast = false; +} + +export function getServers() { + return new Promise((resolve, reject) => { + if (process.env.PM2_USAGE) { + pm2.launchBus((err, pm2Bus) => { + const listener = (packet) => { + if (packet.data?.type === "countResponse") { + resolve(packet.data.serverCount); + pm2Bus.off("process:msg"); + } + }; + pm2Bus.on("process:msg", listener); + }); + pm2.sendDataToProcessId(0, { + id: 0, + type: "process:msg", + data: { + type: "getCount" + }, + topic: true + }, (err) => { + if (err) reject(err); + }); + } else { + resolve(0); + } + }); } \ No newline at end of file diff --git a/utils/pm2/ext.js b/utils/pm2/ext.js new file mode 100644 index 0000000..4b35939 --- /dev/null +++ b/utils/pm2/ext.js @@ -0,0 +1,125 @@ +import pm2 from "pm2"; +import { Api } from "@top-gg/sdk"; +import winston from "winston"; + +// load config from .env file +import { resolve, dirname } from "path"; +import { fileURLToPath } from "url"; +import { config } from "dotenv"; +config({ path: resolve(dirname(fileURLToPath(import.meta.url)), "../../.env") }); + +const dbl = process.env.NODE_ENV === "production" && process.env.DBL ? new Api(process.env.DBL) : null; + +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 }), stderrLevels: ["error", "warn"] }) + ], + level: process.env.DEBUG_LOG ? "debug" : "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: "magenta", + warn: "yellow", + error: "red" +}); + +let serverCount = 0; +let shardCount = 0; +let clusterCount = 0; +let responseCount = 0; + +let timeout; + +process.on("message", (packet) => { + if (packet.data?.type === "getCount") { + process.send({ + type: "process:msg", + data: { + type: "countResponse", + serverCount + } + }); + } +}); + +function updateStats() { + return new Promise((resolve, reject) => { + pm2.list((err, list) => { + if (err) reject(err); + const clusters = list.filter((v) => v.name === "esmBot"); + clusterCount = clusters.length; + const listener = (packet) => { + if (packet.data?.type === "serverCounts") { + clearTimeout(timeout); + serverCount += packet.data.guilds; + shardCount += packet.data.shards; + responseCount += 1; + if (responseCount >= clusterCount) { + resolve(); + process.removeListener("message", listener); + } else { + timeout = setTimeout(() => { + reject(); + process.removeListener("message", listener); + }, 5000); + } + } + }; + timeout = setTimeout(() => { + reject(); + process.removeListener("message", listener); + }, 5000); + process.on("message", listener); + process.send({ + type: "process:msg", + data: { + type: "serverCounts" + } + }); + }); + }); +} + +async function dblPost() { + logger.main("Posting stats to Top.gg..."); + serverCount = 0; + shardCount = 0; + clusterCount = 0; + responseCount = 0; + try { + //await updateStats(); + await dbl.postStats({ + serverCount, + shardCount + }); + logger.main("Stats posted."); + } catch (e) { + logger.error(e); + } +} + +setInterval(updateStats, 300000); +if (dbl) setInterval(dblPost, 1800000); + +setTimeout(updateStats, 10000); + +logger.info("Started esmBot management process."); \ No newline at end of file diff --git a/utils/services/image.js b/utils/services/image.js deleted file mode 100644 index 41ad952..0000000 --- a/utils/services/image.js +++ /dev/null @@ -1,268 +0,0 @@ -import { BaseServiceWorker } from "eris-fleet"; -import * as logger from "../logger.js"; -import fs from "fs"; -import path from "path"; -import { fileURLToPath } from "url"; -import { Worker } from "worker_threads"; -import { createRequire } from "module"; -import { createServer } from "http"; -import { request } from "undici"; -import EventEmitter from "events"; - -// only requiring this to work around an issue regarding worker threads -const nodeRequire = createRequire(import.meta.url); -if (!process.env.API_TYPE || process.env.API_TYPE === "none") { - nodeRequire(`../../build/${process.env.DEBUG && process.env.DEBUG === "true" ? "Debug" : "Release"}/image.node`); -} - -import ImageConnection from "../imageConnection.js"; - -class ImageWorker extends BaseServiceWorker { - constructor(setup) { - super(setup); - - console.info = (str) => this.ipc.sendToAdmiral("info", str); - - if (process.env.API_TYPE === "ws") { - this.connections = new Map(); - this.servers = JSON.parse(fs.readFileSync(new URL("../../config/servers.json", import.meta.url), { encoding: "utf8" })).image; - this.nextID = 0; - } else if (process.env.API_TYPE === "azure") { - this.jobs = new Map(); - this.webhook = createServer(); - this.port = parseInt(process.env.WEBHOOK_PORT) || 3763; - } - - this.begin().then(() => this.serviceReady()); - } - - async begin() { - // connect to image api if enabled - if (process.env.API_TYPE === "ws") { - for (const server of this.servers) { - try { - await this.connect(server.server, server.auth); - } catch (e) { - logger.error(e); - } - } - } else if (process.env.API_TYPE === "azure") { - this.webhook.on("request", async (req, res) => { - if (req.method !== "POST") { - res.statusCode = 405; - return res.end("405 Method Not Allowed"); - } - if (process.env.AZURE_PASS && req.headers.authorization !== process.env.AZURE_PASS) { - res.statusCode = 401; - return res.end("401 Unauthorized"); - } - const reqUrl = new URL(req.url, `http://${req.headers.host}`); - if (reqUrl.pathname === "/callback") { - try { - const chunks = []; - req.on("data", (data) => { - chunks.push(data); - }); - req.once("end", () => { - if (this.jobs.has(req.headers["x-azure-id"])) { - try { - const error = JSON.parse(Buffer.concat(chunks).toString()); - if (error.error) this.jobs.get(req.headers["x-azure-id"]).emit("error", new Error(error.message)); - } catch { - // no-op - } - const contentType = req.headers["content-type"]; - let type; - switch (contentType) { - case "image/gif": - type = "gif"; - break; - case "image/png": - type = "png"; - break; - case "image/jpeg": - type = "jpg"; - break; - case "image/webp": - type = "webp"; - break; - default: - type = contentType; - break; - } - this.jobs.get(req.headers["x-azure-id"]).emit("image", { buffer: Buffer.concat(chunks), type }); - return res.end("OK"); - } else { - res.statusCode = 409; - return res.end("409 Conflict"); - } - }); - } catch (e) { - logger.error("An error occurred while processing a webhook request: ", e); - res.statusCode = 500; - return res.end("500 Internal Server Error"); - } - } else { - res.statusCode = 404; - return res.end("404 Not Found"); - } - }); - this.webhook.on("error", (e) => { - logger.error("An error occurred on the Azure webhook: ", e); - }); - this.webhook.listen(this.port, () => { - logger.log(`Azure HTTP webhook listening on port ${this.port}`); - }); - } - } - - async repopulate() { - const data = await fs.promises.readFile(new URL("../../config/servers.json", import.meta.url), { encoding: "utf8" }); - this.servers = JSON.parse(data).image; - return; - } - - async getRunning() { - const statuses = []; - if (process.env.API_TYPE === "ws") { - for (const [address, connection] of this.connections) { - if (connection.conn.readyState !== 0 && connection.conn.readyState !== 1) { - continue; - } - statuses.push({ - address, - runningJobs: connection.njobs, - max: connection.max - }); - } - } - return statuses; - } - - async chooseServer(ideal) { - if (ideal.length === 0) throw "No available servers"; - const sorted = ideal.sort((a, b) => { - return a.load - b.load; - }).filter((e, i, array) => { - return !(e.load < array[0].load); - }); - return sorted[0]; - } - - async getIdeal(object) { - const idealServers = []; - for (const [address, connection] of this.connections) { - if (connection.conn.readyState !== 0 && connection.conn.readyState !== 1) { - continue; - } - if (object.params.type && !connection.formats[object.cmd]?.includes(object.params.type)) continue; - idealServers.push({ - addr: address, - load: connection.njobs / connection.max - }); - } - const server = await this.chooseServer(idealServers); - return this.connections.get(server.addr); - } - - async connect(server, auth) { - const connection = new ImageConnection(server, auth); - this.connections.set(server, connection); - } - - async disconnect() { - for (const connection of this.connections.values()) { - connection.close(); - } - this.connections.clear(); - return; - } - - waitForWorker(worker) { - return new Promise((resolve, reject) => { - worker.once("message", (data) => { - resolve({ - buffer: Buffer.from([...data.buffer]), - type: data.fileExtension - }); - }); - worker.once("error", reject); - }); - } - - waitForAzure(event) { - return new Promise((resolve, reject) => { - event.once("image", (data) => { - resolve(data); - }); - event.once("error", reject); - }); - } - - async run(object) { - if (process.env.API_TYPE === "ws") { - let num = this.nextID++; - if (num > 4294967295) num = this.nextID = 0; - for (let i = 0; i < 3; i++) { - const currentServer = await this.getIdeal(object); - try { - await currentServer.queue(num, object); - await currentServer.wait(num); - const output = await currentServer.getOutput(num); - return output; - } catch (e) { - if (i < 2 && e === "Request ended prematurely due to a closed connection") { - continue; - } else { - if (e === "No available servers" && i >= 2) throw "Request ended prematurely due to a closed connection"; - throw e; - } - } - } - } else if (process.env.API_TYPE === "azure") { - object.callback = `${process.env.AZURE_CALLBACK_URL}:${this.port}/callback`; - const response = await request(`${process.env.AZURE_URL}/api/orchestrators/ImageOrchestrator`, { method: "POST", body: JSON.stringify(object) }).then(r => r.body.json()); - const event = new EventEmitter(); - this.jobs.set(response.id, event); - return await this.waitForAzure(event); - } else { - // Called from command (not using image API) - const worker = new Worker(path.join(path.dirname(fileURLToPath(import.meta.url)), "../image-runner.js"), { - workerData: object - }); - return await this.waitForWorker(worker); - } - } - - async handleCommand(data) { - try { - if (data.type === "run") { - const result = await this.run(data.obj); - return result; - } 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.server, server.auth); - amount += 1; - } catch (e) { - logger.error(e); - } - } - return amount; - } else if (data.type === "stats") { - return await this.getRunning(); - } - } catch (err) { - return { err: typeof err === "string" ? err : err.message }; - } - } - - shutdown(done) { - done(); - } -} - -export default ImageWorker; diff --git a/utils/tempimages.js b/utils/tempimages.js index df84934..9d744ab 100644 --- a/utils/tempimages.js +++ b/utils/tempimages.js @@ -1,7 +1,9 @@ import * as logger from "../utils/logger.js"; -import { readdir, lstat, rm, writeFile } from "fs/promises"; +import { readdir, lstat, rm, writeFile, stat } from "fs/promises"; -export async function upload(client, ipc, result, context, interaction = false) { +let dirSizeCache; + +export async function upload(client, result, context, interaction = false) { const filename = `${Math.random().toString(36).substring(2, 15)}.${result.name.split(".")[1]}`; await writeFile(`${process.env.TEMPDIR}/${filename}`, result.file); const imageURL = `${process.env.TMP_DOMAIN || "https://tmp.projectlounge.pw"}/${filename}`; @@ -34,13 +36,13 @@ export async function upload(client, ipc, result, context, interaction = false) })); } if (process.env.THRESHOLD) { - const size = await ipc.centralStore.get("dirSizeCache") + result.file.length; - await ipc.centralStore.set("dirSizeCache", size); - await removeOldImages(ipc, size); + const size = dirSizeCache + result.file.length; + dirSizeCache = size; + await removeOldImages(size); } } -export async function removeOldImages(ipc, size) { +async function removeOldImages(size) { if (size > process.env.THRESHOLD) { const files = (await readdir(process.env.TEMPDIR)).map((file) => { return lstat(`${process.env.TEMPDIR}/${file}`).then((stats) => { @@ -67,6 +69,30 @@ export async function removeOldImages(ipc, size) { const newSize = oldestFiles.reduce((a, b) => { return a + b.size; }, 0); - await ipc.centralStore.set("dirSizeCache", newSize); + dirSizeCache = newSize; } } + +export async function parseThreshold() { + const matched = process.env.THRESHOLD.match(/(\d+)([KMGT])/); + const sizes = { + K: 1024, + M: 1048576, + G: 1073741824, + T: 1099511627776 + }; + if (matched && matched[1] && matched[2]) { + process.env.THRESHOLD = matched[1] * sizes[matched[2]]; + } else { + logger.error("Invalid THRESHOLD config."); + process.env.THRESHOLD = undefined; + } + const dirstat = (await readdir(process.env.TEMPDIR)).map((file) => { + return stat(`${process.env.TEMPDIR}/${file}`).then((stats) => stats.size); + }); + const size = await Promise.all(dirstat); + const reduced = size.reduce((a, b) => { + return a + b; + }, 0); + dirSizeCache = reduced; +} \ No newline at end of file