"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var monitor_exports = {}; __export(monitor_exports, { Monitor: () => Monitor, TimedCounter: () => TimedCounter }); module.exports = __toCommonJS(monitor_exports); var import_child_process = require("child_process"); var import_lib = require("../lib"); var pathModule = __toESM(require("path")); /** * Monitor * Pokemon Showdown - http://pokemonshowdown.com/ * * Various utility functions to make sure PS is running healthily. * * @license MIT */ const MONITOR_CLEAN_TIMEOUT = 2 * 60 * 60 * 1e3; class TimedCounter extends Map { /** * Increments the number of times an action has been committed by one, and * updates the delta of time since it was last committed. * * @returns [action count, time delta] */ increment(key, timeLimit) { const val = this.get(key); const now = Date.now(); if (!val || now > val[1] + timeLimit) { this.set(key, [1, Date.now()]); return [1, 0]; } else { val[0]++; return [val[0], now - val[1]]; } } } if ("Config" in global && (typeof Config.loglevel !== "number" || Config.loglevel < 0 || Config.loglevel > 5)) { Config.loglevel = 2; } const Monitor = new class { constructor() { this.connections = new TimedCounter(); this.netRequests = new TimedCounter(); this.battles = new TimedCounter(); this.battlePreps = new TimedCounter(); this.groupChats = new TimedCounter(); this.tickets = new TimedCounter(); this.activeIp = null; this.networkUse = {}; this.networkCount = {}; this.hotpatchLock = {}; this.TimedCounter = TimedCounter; this.updateServerLock = false; this.cleanInterval = null; /** * Inappropriate userid : has the user logged in since the FR */ this.forceRenames = /* @__PURE__ */ new Map(); } /********************************************************* * Logging *********************************************************/ crashlog(err, source = "The main process", details = null) { const error = err || {}; if ((error.stack || "").startsWith("@!!@")) { try { const stack = error.stack || ""; const nlIndex = stack.indexOf("\n"); [error.name, error.message, source, details] = JSON.parse(stack.slice(4, nlIndex)); error.stack = stack.slice(nlIndex + 1); } catch { } } const crashType = (0, import_lib.crashlogger)(error, source, details); Rooms.global.reportCrash(error, source); if (crashType === "lockdown") { Config.autolockdown = false; Rooms.global.startLockdown(error); } } logPath(path) { if (Config.logsdir) { return (0, import_lib.FS)(pathModule.join(Config.logsdir, path)); } return (0, import_lib.FS)(pathModule.join("logs", path)); } log(text) { this.notice(text); const staffRoom = Rooms.get("staff"); if (staffRoom) { staffRoom.add(`|c|~|${text}`).update(); } } adminlog(text) { this.notice(text); const upperstaffRoom = Rooms.get("upperstaff"); if (upperstaffRoom) { upperstaffRoom.add(`|c|~|${text}`).update(); } } logHTML(text) { this.notice(text); const staffRoom = Rooms.get("staff"); if (staffRoom) { staffRoom.add(`|html|${text}`).update(); } } error(text) { (Rooms.get("development") || Rooms.get("staff") || Rooms.get("lobby"))?.add(`|error|${text}`).update(); if (Config.loglevel <= 3) console.error(text); } debug(text) { if (Config.loglevel <= 1) console.log(text); } warn(text) { if (Config.loglevel <= 3) console.log(text); } notice(text) { if (Config.loglevel <= 2) console.log(text); } slow(text) { const logRoom = Rooms.get("slowlog"); if (logRoom) { logRoom.add(`|c|~|/log ${text}`).update(); } else { this.warn(text); } } /********************************************************* * Resource Monitor *********************************************************/ clean() { this.clearNetworkUse(); this.battlePreps.clear(); this.battles.clear(); this.connections.clear(); IPTools.dnsblCache.clear(); } /** * Counts a connection. Returns true if the connection should be terminated for abuse. */ countConnection(ip, name = "") { if (Config.noipchecks || Config.nothrottle) return false; const [count, duration] = this.connections.increment(ip, 30 * 60 * 1e3); if (count === 500) { this.adminlog(`[ResourceMonitor] IP ${ip} banned for cflooding (${count} times in ${Chat.toDurationString(duration)}${name ? ": " + name : ""})`); return true; } if (count > 500) { if (count % 500 === 0) { const c = count / 500; if (c === 2 || c === 4 || c === 10 || c === 20 || c % 40 === 0) { this.adminlog(`[ResourceMonitor] IP ${ip} still cflooding (${count} times in ${Chat.toDurationString(duration)}${name ? ": " + name : ""})`); } } return true; } return false; } /** * Counts battles created. Returns true if the connection should be * terminated for abuse. */ countBattle(ip, name = "") { if (Config.noipchecks || Config.nothrottle) return false; const [count, duration] = this.battles.increment(ip, 30 * 60 * 1e3); if (duration < 5 * 60 * 1e3 && count % 30 === 0) { this.adminlog(`[ResourceMonitor] IP ${ip} has battled ${count} times in the last ${Chat.toDurationString(duration)}${name ? ": " + name : ""})`); return true; } if (count % 150 === 0) { this.adminlog(`[ResourceMonitor] IP ${ip} has battled ${count} times in the last ${Chat.toDurationString(duration)}${name ? ": " + name : ""}`); return true; } return false; } /** * Counts team validations. Returns true if too many. */ countPrepBattle(ip, connection) { if (Config.noipchecks || Config.nothrottle) return false; const count = this.battlePreps.increment(ip, 3 * 60 * 1e3)[0]; if (count <= 12) return false; if (count < 120 && Punishments.isSharedIp(ip)) return false; connection.popup("Due to high load, you are limited to 12 battles and team validations every 3 minutes."); return true; } /** * Counts concurrent battles. Returns true if too many. */ countConcurrentBattle(count, connection) { if (Config.noipchecks || Config.nothrottle) return false; if (count <= 5) return false; connection.popup(`Due to high load, you are limited to 5 games at the same time.`); return true; } /** * Counts group chat creation. Returns true if too much. */ countGroupChat(ip) { if (Config.noipchecks) return false; const count = this.groupChats.increment(ip, 60 * 60 * 1e3)[0]; return count > 4; } /** * Counts commands that use HTTPs requests. Returns true if too many. */ countNetRequests(ip) { if (Config.noipchecks || Config.nothrottle) return false; const [count] = this.netRequests.increment(ip, 1 * 60 * 1e3); if (count <= 10) return false; if (count < 120 && Punishments.isSharedIp(ip)) return false; return true; } /** * Counts ticket creation. Returns true if too much. */ countTickets(ip) { if (Config.noipchecks || Config.nothrottle) return false; const count = this.tickets.increment(ip, 60 * 60 * 1e3)[0]; if (Punishments.isSharedIp(ip)) { return count >= 20; } else { return count >= 5; } } /** * Counts the data length received by the last connection to send a * message, as well as the data length in the server's response. */ countNetworkUse(size) { if (!Config.emergency || typeof this.activeIp !== "string" || Config.noipchecks || Config.nothrottle) { return; } if (this.activeIp in this.networkUse) { this.networkUse[this.activeIp] += size; this.networkCount[this.activeIp]++; } else { this.networkUse[this.activeIp] = size; this.networkCount[this.activeIp] = 1; } } writeNetworkUse() { let buf = ""; for (const i in this.networkUse) { buf += `${this.networkUse[i]} ${this.networkCount[i]} ${i} `; } void Monitor.logPath("networkuse.tsv").write(buf); } clearNetworkUse() { if (Config.emergency) { this.networkUse = {}; this.networkCount = {}; } } /** * Counts roughly the size of an object to have an idea of the server load. */ sizeOfObject(object) { const objectCache = /* @__PURE__ */ new Set(); const stack = [object]; let bytes = 0; while (stack.length) { const value = stack.pop(); switch (typeof value) { case "boolean": bytes += 4; break; case "string": bytes += value.length * 2; break; case "number": bytes += 8; break; case "object": if (!objectCache.has(value)) objectCache.add(value); if (Array.isArray(value)) { for (const el of value) stack.push(el); } else { for (const i in value) stack.push(value[i]); } break; } } return bytes; } sh(command, options = {}) { return new Promise((resolve, reject) => { (0, import_child_process.exec)(command, options, (error, stdout, stderr) => { resolve([error?.code || 0, `${stdout}`, `${stderr}`]); }); }); } async version() { let hash; try { await (0, import_lib.FS)(".git/index").copyFile(Monitor.logPath(".gitindex").path); const index = Monitor.logPath(".gitindex"); const options = { cwd: __dirname, env: { GIT_INDEX_FILE: index.path } }; let [code, stdout, stderr] = await this.sh(`git add -A`, options); if (code || stderr) return; [code, stdout, stderr] = await this.sh(`git write-tree`, options); if (code || stderr) return; hash = stdout.trim(); await this.sh(`git reset`, options); await index.unlinkIfExists(); } catch { } return hash; } }(); Monitor.cleanInterval = setInterval(() => Monitor.clean(), MONITOR_CLEAN_TIMEOUT); //# sourceMappingURL=monitor.js.map