760 lines
30 KiB
JavaScript
760 lines
30 KiB
JavaScript
"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 converter_exports = {};
|
|
__export(converter_exports, {
|
|
ModlogConverter: () => ModlogConverter,
|
|
ModlogConverterSQLite: () => ModlogConverterSQLite,
|
|
ModlogConverterTest: () => ModlogConverterTest,
|
|
ModlogConverterTxt: () => ModlogConverterTxt,
|
|
modernizeLog: () => modernizeLog,
|
|
parseBrackets: () => parseBrackets,
|
|
parseModlog: () => parseModlog,
|
|
rawifyLog: () => rawifyLog
|
|
});
|
|
module.exports = __toCommonJS(converter_exports);
|
|
var import_lib = require("../../lib");
|
|
var import_ip_tools = require("../../server/ip-tools");
|
|
if (!global.Config) {
|
|
let hasSQLite = true;
|
|
try {
|
|
require.resolve("better-sqlite3");
|
|
} catch {
|
|
console.warn(`Warning: the modlog conversion script is running without a SQLite library.`);
|
|
hasSQLite = false;
|
|
}
|
|
global.Config = {
|
|
nofswriting: false,
|
|
usesqlitemodlog: hasSQLite,
|
|
usesqlite: hasSQLite
|
|
};
|
|
}
|
|
const Database = Config.usesqlite ? require("better-sqlite3") : null;
|
|
const { Modlog } = require("../../server/modlog");
|
|
const ENTRIES_TO_BUFFER = 7500;
|
|
const ALTS_REGEX = /\(.*?'s (lock|mut|bann|blacklist)ed alts: (.*)\)/;
|
|
const AUTOCONFIRMED_REGEX = /\(.*?'s ac account: (.*)\)/;
|
|
const IP_ONLY_ACTIONS = /* @__PURE__ */ new Set([
|
|
"SHAREDIP",
|
|
"UNSHAREDIP",
|
|
"UNLOCKIP",
|
|
"UNLOCKRANGE",
|
|
"RANGEBAN",
|
|
"RANGELOCK"
|
|
]);
|
|
function parseBrackets(line, openingBracket, greedy) {
|
|
const brackets = {
|
|
"(": ")",
|
|
"[": "]"
|
|
};
|
|
const bracketOpenIndex = line.indexOf(openingBracket);
|
|
const bracketCloseIndex = greedy ? line.lastIndexOf(brackets[openingBracket]) : line.indexOf(brackets[openingBracket]);
|
|
if (bracketCloseIndex < 0 || bracketOpenIndex < 0)
|
|
return "";
|
|
return line.slice(bracketOpenIndex + 1, bracketCloseIndex);
|
|
}
|
|
function toID(text) {
|
|
return (text && typeof text === "string" ? text : "").toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
}
|
|
function modernizeLog(line, nextLine) {
|
|
const prefix = line.match(/\[.+?\] \(.+?\) /i)?.[0];
|
|
if (!prefix)
|
|
return;
|
|
if (ALTS_REGEX.test(line) || AUTOCONFIRMED_REGEX.test(line))
|
|
return;
|
|
line = line.replace(prefix, "");
|
|
if (line.startsWith("("))
|
|
line = line.replace(/\([a-z0-9-]*\) /, "");
|
|
if (line.startsWith("(") && line.endsWith(")")) {
|
|
line = line.slice(1, -1);
|
|
}
|
|
const getAlts = () => {
|
|
let alts = "";
|
|
nextLine?.replace(ALTS_REGEX, (_a, _b, rawAlts) => {
|
|
if (rawAlts)
|
|
alts = `alts: [${rawAlts.split(",").map(toID).join("], [")}] `;
|
|
return "";
|
|
});
|
|
return alts;
|
|
};
|
|
const getAutoconfirmed = () => {
|
|
let autoconfirmed = "";
|
|
nextLine?.replace(AUTOCONFIRMED_REGEX, (_a, rawAutoconfirmed) => {
|
|
if (rawAutoconfirmed)
|
|
autoconfirmed = `ac: [${toID(rawAutoconfirmed)}] `;
|
|
return "";
|
|
});
|
|
return autoconfirmed;
|
|
};
|
|
if (line.startsWith("SCAV ")) {
|
|
line = line.replace(/: (\[room: .*?\]) by (.*)/, (match, roominfo, rest) => `: by ${rest} ${roominfo}`);
|
|
}
|
|
line = line.replace(
|
|
/(GIVEAWAY WIN|GTS FINISHED): ([A-Za-z0-9].*?)(won|has finished)/,
|
|
(match, action, user) => `${action}: [${toID(user)}]:`
|
|
);
|
|
if (line.includes(":")) {
|
|
const possibleModernAction = line.slice(0, line.indexOf(":")).trim();
|
|
if (possibleModernAction === possibleModernAction.toUpperCase()) {
|
|
if (possibleModernAction.includes("[")) {
|
|
const [drop, ...keep] = line.split("[");
|
|
process.stderr.write(`Ignoring malformed line: ${drop}
|
|
`);
|
|
return modernizeLog(keep.join(""));
|
|
}
|
|
if (/\(.+\) by [a-z0-9]{1,19}$/.test(line) && !["OLD MODLOG", "NOTE"].includes(possibleModernAction)) {
|
|
const reason = parseBrackets(line, "(", true);
|
|
return `${prefix}${line.replace(` (${reason})`, "")}: ${reason}`;
|
|
}
|
|
return `${prefix}${line}`;
|
|
}
|
|
}
|
|
if (/\[(the|a)poll\] was (started|ended) by/.test(line)) {
|
|
const actionTaker = toID(line.slice(line.indexOf(" by ") + " by ".length));
|
|
const isEnding = line.includes("was ended by");
|
|
return `${prefix}POLL${isEnding ? " END" : ""}: by ${actionTaker}`;
|
|
}
|
|
if (/User (.*?) won the game of (.*?) mode trivia/.test(line)) {
|
|
return `${prefix}TRIVIAGAME: by unknown: ${line}`;
|
|
}
|
|
const modernizerTransformations = {
|
|
"notes: ": (log) => {
|
|
const [actionTaker, ...rest] = line.split(" notes: ");
|
|
return `NOTE: by ${toID(actionTaker)}: ${rest.join("")}`;
|
|
},
|
|
" declared": (log) => {
|
|
let newAction = "DECLARE";
|
|
let oldAction = " declared";
|
|
if (log.includes(" globally declared")) {
|
|
oldAction = " globally declared";
|
|
newAction = "GLOBALDECLARE";
|
|
}
|
|
if (log.includes("(chat level)")) {
|
|
oldAction += " (chat level)";
|
|
newAction = `CHATDECLARE`;
|
|
}
|
|
const actionTakerName = toID(log.slice(0, log.lastIndexOf(oldAction)));
|
|
log = log.slice(actionTakerName.length);
|
|
log = log.slice(oldAction.length);
|
|
log = log.replace(/^\s?:/, "").trim();
|
|
return `${newAction}: by ${actionTakerName}: ${log}`;
|
|
},
|
|
"changed the roomdesc to: ": (log) => {
|
|
const actionTaker = parseBrackets(log, "[");
|
|
log = log.slice(actionTaker.length + 3);
|
|
log = log.slice("changed the roomdesc to: ".length + 1, -2);
|
|
return `ROOMDESC: by ${actionTaker}: to "${log}"`;
|
|
},
|
|
'roomevent titled "': (log) => {
|
|
let action;
|
|
if (log.includes(' added a roomevent titled "')) {
|
|
action = "added a";
|
|
} else if (log.includes(' removed a roomevent titled "')) {
|
|
action = "removed a";
|
|
} else {
|
|
action = "edited the";
|
|
}
|
|
const actionTakerName = log.slice(0, log.lastIndexOf(` ${action} roomevent titled "`));
|
|
log = log.slice(actionTakerName.length + 1);
|
|
const eventName = log.slice(` ${action} roomevent titled `.length, -2);
|
|
return `ROOMEVENT: by ${toID(actionTakerName)}: ${action.split(" ")[0]} "${eventName}"`;
|
|
},
|
|
"set modchat to ": (log) => {
|
|
const actionTaker = parseBrackets(log, "[");
|
|
log = log.slice(actionTaker.length + 3);
|
|
log = log.slice("set modchat to ".length);
|
|
return `MODCHAT: by ${actionTaker}: to ${log}`;
|
|
},
|
|
"set modjoin to ": (log) => {
|
|
const actionTakerName = log.slice(0, log.lastIndexOf(" set"));
|
|
log = log.slice(actionTakerName.length + 1);
|
|
log = log.slice("set modjoin to ".length);
|
|
const rank = log.startsWith("sync") ? "sync" : log.replace(".", "");
|
|
return `MODJOIN${rank === "sync" ? " SYNC" : ""}: by ${toID(actionTakerName)}${rank !== "sync" ? `: ${rank}` : ``}`;
|
|
},
|
|
"turned off modjoin": (log) => {
|
|
const actionTakerName = log.slice(0, log.lastIndexOf(" turned off modjoin"));
|
|
return `MODJOIN: by ${toID(actionTakerName)}: OFF`;
|
|
},
|
|
"changed the roomintro": (log) => {
|
|
const isDeletion = /deleted the (staff|room)intro/.test(log);
|
|
const isRoomintro = log.includes("roomintro");
|
|
const actionTaker = toID(log.slice(0, log.indexOf(isDeletion ? "deleted" : "changed")));
|
|
return `${isDeletion ? "DELETE" : ""}${isRoomintro ? "ROOM" : "STAFF"}INTRO: by ${actionTaker}`;
|
|
},
|
|
"deleted the roomintro": (log) => modernizerTransformations["changed the roomintro"](log),
|
|
"changed the staffintro": (log) => modernizerTransformations["changed the roomintro"](log),
|
|
"deleted the staffintro": (log) => modernizerTransformations["changed the roomintro"](log),
|
|
"created a tournament in": (log) => {
|
|
const actionTaker = parseBrackets(log, "[");
|
|
log = log.slice(actionTaker.length + 3);
|
|
log = log.slice(24, -8);
|
|
return `TOUR CREATE: by ${actionTaker}: ${log}`;
|
|
},
|
|
"was disqualified from the tournament by": (log) => {
|
|
const disqualified = parseBrackets(log, "[");
|
|
log = log.slice(disqualified.length + 3);
|
|
log = log.slice("was disqualified from the tournament by".length);
|
|
return `TOUR DQ: [${toID(disqualified)}] by ${toID(log)}`;
|
|
},
|
|
"The tournament auto disqualify timeout was set to": (log) => {
|
|
const byIndex = log.indexOf(" by ");
|
|
const actionTaker = log.slice(byIndex + " by ".length);
|
|
const length = log.slice("The tournament auto disqualify timeout was set to".length, byIndex);
|
|
return `TOUR AUTODQ: by ${toID(actionTaker)}: ${length.trim()}`;
|
|
},
|
|
" was blacklisted from ": (log) => {
|
|
const isName = log.includes(" was nameblacklisted from ");
|
|
const banned = toID(log.slice(0, log.indexOf(` was ${isName ? "name" : ""}blacklisted from `)));
|
|
log = log.slice(log.indexOf(" by ") + " by ".length);
|
|
let reason, ip;
|
|
if (/\(.*\)/.test(log)) {
|
|
reason = parseBrackets(log, "(");
|
|
if (/\[.*\]/.test(log))
|
|
ip = parseBrackets(log, "[");
|
|
log = log.slice(0, log.indexOf("("));
|
|
}
|
|
const actionTaker = toID(log);
|
|
return `${isName ? "NAME" : ""}BLACKLIST: [${banned}] ${getAutoconfirmed()}${getAlts()}${ip ? `[${ip}] ` : ``}by ${actionTaker}${reason ? `: ${reason}` : ``}`;
|
|
},
|
|
" was nameblacklisted from ": (log) => modernizerTransformations[" was blacklisted from "](log),
|
|
" was banned from room ": (log) => {
|
|
const banned = toID(log.slice(0, log.indexOf(" was banned from room ")));
|
|
log = log.slice(log.indexOf(" by ") + " by ".length);
|
|
let reason, ip;
|
|
if (/\(.*\)/.test(log)) {
|
|
reason = parseBrackets(log, "(");
|
|
if (/\[.*\]/.test(log))
|
|
ip = parseBrackets(log, "[");
|
|
log = log.slice(0, log.indexOf("("));
|
|
}
|
|
const actionTaker = toID(log);
|
|
return `ROOMBAN: [${banned}] ${getAutoconfirmed()}${getAlts()}${ip ? `[${ip}] ` : ``}by ${actionTaker}${reason ? `: ${reason}` : ``}`;
|
|
},
|
|
" was muted by ": (log) => {
|
|
let muted = "";
|
|
let isHour = false;
|
|
[muted, log] = log.split(" was muted by ");
|
|
muted = toID(muted);
|
|
let reason, ip;
|
|
if (/\(.*\)/.test(log)) {
|
|
reason = parseBrackets(log, "(");
|
|
if (/\[.*\]/.test(log))
|
|
ip = parseBrackets(log, "[");
|
|
log = log.slice(0, log.indexOf("("));
|
|
}
|
|
let actionTaker = toID(log);
|
|
if (actionTaker.endsWith("for1hour")) {
|
|
isHour = true;
|
|
actionTaker = actionTaker.replace(/^(.*)(for1hour)$/, (match, staff) => staff);
|
|
}
|
|
return `${isHour ? "HOUR" : ""}MUTE: [${muted}] ${getAutoconfirmed()}${getAlts()}${ip ? `[${ip}] ` : ``}by ${actionTaker}${reason ? `: ${reason}` : ``}`;
|
|
},
|
|
" was locked from talking ": (log) => {
|
|
const isWeek = log.includes(" was locked from talking for a week ");
|
|
const locked = toID(log.slice(0, log.indexOf(" was locked from talking ")));
|
|
log = log.slice(log.indexOf(" by ") + " by ".length);
|
|
let reason, ip;
|
|
if (/\(.*\)/.test(log)) {
|
|
reason = parseBrackets(log, "(");
|
|
if (/\[.*\]/.test(log))
|
|
ip = parseBrackets(log, "[");
|
|
log = log.slice(0, log.indexOf("("));
|
|
}
|
|
const actionTaker = toID(log);
|
|
return `${isWeek ? "WEEK" : ""}LOCK: [${locked}] ${getAutoconfirmed()}${getAlts()}${ip ? `[${ip}] ` : ``}by ${actionTaker}${reason ? `: ${reason}` : ``}`;
|
|
},
|
|
" was banned ": (log) => {
|
|
if (log.includes(" was banned from room "))
|
|
return modernizerTransformations[" was banned from room "](log);
|
|
const banned = toID(log.slice(0, log.indexOf(" was banned ")));
|
|
log = log.slice(log.indexOf(" by ") + " by ".length);
|
|
let reason, ip;
|
|
if (/\(.*\)/.test(log)) {
|
|
reason = parseBrackets(log, "(");
|
|
if (/\[.*\]/.test(log))
|
|
ip = parseBrackets(log, "[");
|
|
log = log.slice(0, log.indexOf("("));
|
|
}
|
|
const actionTaker = toID(log);
|
|
return `BAN: [${banned}] ${getAutoconfirmed()}${getAlts()}${ip ? `[${ip}] ` : ``}by ${actionTaker}${reason ? `: ${reason}` : ``}`;
|
|
},
|
|
"was promoted to ": (log) => {
|
|
const isDemotion = log.includes("was demoted to ");
|
|
const userid = toID(log.split(" was ")[0]);
|
|
if (!userid) {
|
|
throw new Error(`Ignoring malformed line: ${prefix}${log}`);
|
|
}
|
|
log = log.slice(userid.length + 3);
|
|
log = log.slice(`was ${isDemotion ? "demoted" : "promoted"} to `.length);
|
|
let rank = log.slice(0, log.indexOf(" by")).replace(/ /, "").toUpperCase();
|
|
log = log.slice(`${rank} by `.length);
|
|
if (!rank.startsWith("ROOM"))
|
|
rank = `GLOBAL ${rank}`;
|
|
const actionTaker = parseBrackets(log, "[");
|
|
return `${rank}: [${userid}] by ${actionTaker}${isDemotion ? ": (demote)" : ""}`;
|
|
},
|
|
"was demoted to ": (log) => modernizerTransformations["was promoted to "](log),
|
|
"was appointed Room Owner by ": (log) => {
|
|
const userid = parseBrackets(log, "[");
|
|
log = log.slice(userid.length + 3);
|
|
log = log.slice("was appointed Room Owner by ".length);
|
|
const actionTaker = parseBrackets(log, "[");
|
|
return `ROOMOWNER: [${userid}] by ${actionTaker}`;
|
|
},
|
|
" claimed this ticket": (log) => {
|
|
const actions = {
|
|
" claimed this ticket": "TICKETCLAIM",
|
|
" closed this ticket": "TICKETCLOSE",
|
|
" deleted this ticket": "TICKETDELETE"
|
|
};
|
|
for (const oldAction in actions) {
|
|
if (log.includes(oldAction)) {
|
|
const actionTaker = toID(log.slice(0, log.indexOf(oldAction)));
|
|
return `${actions[oldAction]}: by ${actionTaker}`;
|
|
}
|
|
}
|
|
return log;
|
|
},
|
|
"This ticket is now claimed by ": (log) => {
|
|
const claimer = toID(log.slice(log.indexOf(" by ") + " by ".length));
|
|
return `TICKETCLAIM: by ${claimer}`;
|
|
},
|
|
" is no longer interested in this ticket": (log) => {
|
|
const abandoner = toID(log.slice(0, log.indexOf(" is no longer interested in this ticket")));
|
|
return `TICKETABANDON: by ${abandoner}`;
|
|
},
|
|
" opened a new ticket": (log) => {
|
|
const opener = toID(log.slice(0, log.indexOf(" opened a new ticket")));
|
|
const problem = log.slice(log.indexOf(" Issue: ") + " Issue: ".length).trim();
|
|
return `TICKETOPEN: by ${opener}: ${problem}`;
|
|
},
|
|
" closed this ticket": (log) => modernizerTransformations[" claimed this ticket"](log),
|
|
" deleted this ticket": (log) => modernizerTransformations[" claimed this ticket"](log),
|
|
"This ticket is no longer claimed": () => "TICKETUNCLAIM",
|
|
" has been caught attempting a hunt with ": (log) => {
|
|
const index = log.indexOf(" has been caught attempting a hunt with ");
|
|
const user = toID(log.slice(0, index));
|
|
log = log.slice(index + " has been caught attempting a hunt with ".length);
|
|
log = log.replace(". The user has also", "; has also").replace(".", "");
|
|
return `SCAV CHEATER: [${user}]: caught attempting a hunt with ${log}`;
|
|
},
|
|
"made this room hidden": (log) => {
|
|
const user = toID(log.slice(0, log.indexOf(" made this room hidden")));
|
|
return `HIDDENROOM: by ${user}`;
|
|
},
|
|
"The tournament auto start timer was set to ": (log) => {
|
|
log = log.slice("The tournament auto start timer was set to".length);
|
|
const [length, setter] = log.split(" by ").map(toID);
|
|
return `TOUR AUTOSTART: by ${setter}: ${length}`;
|
|
},
|
|
"The tournament auto disqualify timer was set to ": (log) => {
|
|
log = log.slice("The tournament auto disqualify timer was set to".length);
|
|
const [length, setter] = log.split(" by ").map(toID);
|
|
return `TOUR AUTODQ: by ${setter}: ${length}`;
|
|
},
|
|
" set the tournament's banlist to ": (log) => {
|
|
const [setter, banlist] = log.split(` set the tournament's banlist to `);
|
|
return `TOUR BANLIST: by ${toID(setter)}: ${banlist.slice(0, -1)}`;
|
|
},
|
|
" set the tournament's custom rules to": (log) => {
|
|
const [setter, rules] = log.split(` set the tournament's custom rules to `);
|
|
return `TOUR RULES: by ${toID(setter)}: ${rules.slice(0, -1)}`;
|
|
},
|
|
"[agameofhangman] was started by ": (log) => `HANGMAN: by ${toID(log.slice("[agameofhangman] was started by ".length))}`,
|
|
"[agameofunowas] created by ": (log) => `UNO CREATE: by ${toID(log.slice("[agameofunowas] created by ".length))}`,
|
|
"[thetournament] was set to autostart": (log) => {
|
|
const [, user] = log.split(" by ");
|
|
return `TOUR AUTOSTART: by ${toID(user)}: when playercap is reached`;
|
|
},
|
|
"[thetournament] was set to allow scouting": (log) => {
|
|
const [, user] = log.split(" by ");
|
|
return `TOUR SCOUT: by ${toID(user)}: allow`;
|
|
},
|
|
"[thetournament] was set to disallow scouting": (log) => {
|
|
const [, user] = log.split(" by ");
|
|
return `TOUR SCOUT: by ${toID(user)}: disallow`;
|
|
}
|
|
};
|
|
for (const oldAction in modernizerTransformations) {
|
|
if (line.includes(oldAction)) {
|
|
try {
|
|
return prefix + modernizerTransformations[oldAction](line);
|
|
} catch (err) {
|
|
if (Config.nofswriting)
|
|
throw err;
|
|
process.stderr.write(`${err.message}
|
|
`);
|
|
}
|
|
}
|
|
}
|
|
return `${prefix}${line}`;
|
|
}
|
|
function parseModlog(raw, nextLine, isGlobal = false) {
|
|
let line = modernizeLog(raw);
|
|
if (!line)
|
|
return;
|
|
const timestamp = parseBrackets(line, "[");
|
|
line = line.slice(timestamp.length + 3);
|
|
const [roomID, ...bonus] = parseBrackets(line, "(").split(" ");
|
|
const log = {
|
|
action: "NULL",
|
|
roomID,
|
|
visualRoomID: "",
|
|
userid: null,
|
|
autoconfirmedID: null,
|
|
alts: [],
|
|
ip: null,
|
|
isGlobal,
|
|
loggedBy: null,
|
|
note: "",
|
|
time: Math.floor(new Date(timestamp).getTime()) || 0
|
|
};
|
|
if (bonus.length)
|
|
log.visualRoomID = `${log.roomID} ${bonus.join(" ")}`;
|
|
line = line.slice((log.visualRoomID || log.roomID).length + 3);
|
|
const actionColonIndex = line.indexOf(":");
|
|
const action = line.slice(0, actionColonIndex);
|
|
if (action !== action.toUpperCase()) {
|
|
log.action = "OLD MODLOG";
|
|
log.loggedBy = "unknown";
|
|
log.note = line.trim();
|
|
return log;
|
|
} else {
|
|
log.action = action;
|
|
if (log.action === "OLD MODLOG") {
|
|
log.loggedBy = "unknown";
|
|
log.note = line.slice(line.indexOf("by unknown: ") + "by unknown :".length).trim();
|
|
return log;
|
|
}
|
|
line = line.slice(actionColonIndex + 2);
|
|
}
|
|
if (line[0] === "[") {
|
|
if (!IP_ONLY_ACTIONS.has(log.action)) {
|
|
const userid = toID(parseBrackets(line, "["));
|
|
log.userid = userid;
|
|
line = line.slice(userid.length + 3).trim();
|
|
if (line.startsWith("ac:")) {
|
|
line = line.slice(3).trim();
|
|
const ac = parseBrackets(line, "[");
|
|
log.autoconfirmedID = toID(ac);
|
|
line = line.slice(ac.length + 3).trim();
|
|
}
|
|
if (line.startsWith("alts:")) {
|
|
line = line.slice(5).trim();
|
|
const alts = /* @__PURE__ */ new Set();
|
|
let alt = parseBrackets(line, "[");
|
|
do {
|
|
if (alt.includes(", ")) {
|
|
for (const trueAlt of alt.split(", ")) {
|
|
alts.add(toID(trueAlt));
|
|
}
|
|
line = line.slice(line.indexOf(`[${alt}],`) + `[${alt}],`.length).trim();
|
|
if (!line.startsWith("["))
|
|
line = `[${line}`;
|
|
} else {
|
|
if (import_ip_tools.IPTools.ipRegex.test(alt))
|
|
break;
|
|
alts.add(toID(alt));
|
|
line = line.slice(line.indexOf(`[${alt}],`) + `[${alt}],`.length).trim();
|
|
if (alt.includes("[") && !line.startsWith("["))
|
|
line = `[${line}`;
|
|
}
|
|
alt = parseBrackets(line, "[");
|
|
} while (alt);
|
|
log.alts = [...alts];
|
|
}
|
|
}
|
|
if (line[0] === "[") {
|
|
log.ip = parseBrackets(line, "[");
|
|
line = line.slice(log.ip.length + 3).trim();
|
|
}
|
|
}
|
|
let regex = /\bby .*:/;
|
|
let actionTakerIndex = regex.exec(line)?.index;
|
|
if (actionTakerIndex === void 0) {
|
|
actionTakerIndex = line.indexOf("by ");
|
|
regex = /\bby .*/;
|
|
}
|
|
if (actionTakerIndex !== -1) {
|
|
const colonIndex = line.indexOf(": ");
|
|
const actionTaker = line.slice(actionTakerIndex + 3, colonIndex > actionTakerIndex ? colonIndex : void 0);
|
|
if (toID(actionTaker).length < 19) {
|
|
log.loggedBy = toID(actionTaker) || null;
|
|
if (colonIndex > actionTakerIndex)
|
|
line = line.slice(colonIndex);
|
|
line = line.replace(regex, " ");
|
|
}
|
|
}
|
|
if (line)
|
|
log.note = line.replace(/^\s?:\s?/, "").trim();
|
|
return log;
|
|
}
|
|
function rawifyLog(log) {
|
|
let result = `[${new Date(log.time || Date.now()).toJSON()}] (${(log.visualRoomID || log.roomID || "global").replace(/^global-/, "")}) ${log.action}`;
|
|
if (log.userid)
|
|
result += `: [${log.userid}]`;
|
|
if (log.autoconfirmedID)
|
|
result += ` ac: [${log.autoconfirmedID}]`;
|
|
if (log.alts.length)
|
|
result += ` alts: [${log.alts.join("], [")}]`;
|
|
if (log.ip) {
|
|
if (!log.userid)
|
|
result += `:`;
|
|
result += ` [${log.ip}]`;
|
|
}
|
|
if (log.loggedBy)
|
|
result += `${result.endsWith("]") ? "" : ":"} by ${log.loggedBy}`;
|
|
if (log.note)
|
|
result += `: ${log.note}`;
|
|
return result + `
|
|
`;
|
|
}
|
|
class ModlogConverterSQLite {
|
|
constructor(databaseFile, textLogDir, isTesting, newestAllowedTimestamp) {
|
|
this.isTesting = null;
|
|
this.databaseFile = databaseFile;
|
|
this.textLogDir = textLogDir;
|
|
if (isTesting || Config.nofswriting) {
|
|
this.isTesting = { files: /* @__PURE__ */ new Map(), db: isTesting || new Database(":memory:") };
|
|
}
|
|
this.newestAllowedTimestamp = newestAllowedTimestamp;
|
|
}
|
|
async toTxt() {
|
|
const database = this.isTesting?.db || new Database(this.databaseFile, { fileMustExist: true });
|
|
const roomids = database.prepare("SELECT DISTINCT roomid FROM modlog").all();
|
|
const globalEntries = [];
|
|
for (const { roomid } of roomids) {
|
|
if (!Config.nofswriting)
|
|
console.log(`Reading ${roomid}...`);
|
|
const results = database.prepare(
|
|
`SELECT *, (SELECT group_concat(userid, ',') FROM alts WHERE alts.modlog_id = modlog.modlog_id) as alts FROM modlog WHERE roomid = ? ORDER BY timestamp ASC`
|
|
).all(roomid);
|
|
const trueRoomID = roomid.replace(/^global-/, "");
|
|
let entriesLogged = 0;
|
|
let entries = [];
|
|
const insertEntries = async () => {
|
|
if (roomid === "global")
|
|
return;
|
|
entriesLogged += entries.length;
|
|
if (!Config.nofswriting && (entriesLogged % ENTRIES_TO_BUFFER === 0 || entriesLogged < ENTRIES_TO_BUFFER)) {
|
|
process.stdout.clearLine(0);
|
|
process.stdout.cursorTo(0);
|
|
process.stdout.write(`Wrote ${entriesLogged} entries from '${trueRoomID}'`);
|
|
}
|
|
await this.writeFile(`${this.textLogDir}/modlog_${trueRoomID}.txt`, entries.join(""));
|
|
entries = [];
|
|
};
|
|
for (const result of results) {
|
|
if (this.newestAllowedTimestamp && result.timestamp > this.newestAllowedTimestamp)
|
|
break;
|
|
const entry = {
|
|
action: result.action,
|
|
roomID: result.roomid?.replace(/^global-/, ""),
|
|
visualRoomID: result.visual_roomid,
|
|
userid: result.userid,
|
|
autoconfirmedID: result.autoconfirmed_userid,
|
|
alts: result.alts?.split(","),
|
|
ip: result.ip,
|
|
isGlobal: result.roomid?.startsWith("global-") || result.roomid === "global" || result.is_global,
|
|
loggedBy: result.action_taker_userid,
|
|
note: result.note,
|
|
time: result.timestamp
|
|
};
|
|
const rawLog = rawifyLog(entry);
|
|
entries.push(rawLog);
|
|
if (entry.isGlobal) {
|
|
globalEntries.push(rawLog);
|
|
}
|
|
if (entries.length === ENTRIES_TO_BUFFER)
|
|
await insertEntries();
|
|
}
|
|
await insertEntries();
|
|
if (entriesLogged)
|
|
process.stdout.write("\n");
|
|
}
|
|
if (!Config.nofswriting)
|
|
console.log(`Writing the global modlog...`);
|
|
await this.writeFile(`${this.textLogDir}/modlog_global.txt`, globalEntries.join(""));
|
|
}
|
|
async writeFile(path, text) {
|
|
if (this.isTesting) {
|
|
const old = this.isTesting.files.get(path);
|
|
return this.isTesting.files.set(path, `${old || ""}${text}`);
|
|
}
|
|
return (0, import_lib.FS)(path).append(text);
|
|
}
|
|
}
|
|
class ModlogConverterTxt {
|
|
constructor(databaseFile, textLogDir, isTesting, newestAllowedTimestamp) {
|
|
this.isTesting = null;
|
|
this.databaseFile = databaseFile;
|
|
this.textLogDir = textLogDir;
|
|
if (isTesting || Config.nofswriting) {
|
|
this.isTesting = {
|
|
files: isTesting || /* @__PURE__ */ new Map()
|
|
};
|
|
}
|
|
this.modlog = new Modlog(
|
|
this.isTesting ? ":memory:" : this.databaseFile,
|
|
// wait 15 seconds for DB to no longer be busy - this is important since I'm trying to do
|
|
// a no-downtime transfer of text -> SQLite
|
|
{ sqliteOptions: { timeout: 15e3 } }
|
|
);
|
|
this.newestAllowedTimestamp = newestAllowedTimestamp;
|
|
}
|
|
async toSQLite() {
|
|
await this.modlog.readyPromise;
|
|
const files = this.isTesting ? [...this.isTesting.files.keys()] : await (0, import_lib.FS)(this.textLogDir).readdir();
|
|
if (files.includes("modlog_global.txt")) {
|
|
files.splice(files.indexOf("modlog_global.txt"), 1);
|
|
files.unshift("modlog_global.txt");
|
|
}
|
|
const globalEntries = {};
|
|
for (const file of files) {
|
|
if (file === "README.md")
|
|
continue;
|
|
const roomid = file.slice(7, -4);
|
|
const lines = this.isTesting ? this.isTesting.files.get(file)?.split("\n") || [] : (0, import_lib.FS)(`${this.textLogDir}/${file}`).createReadStream().byLine();
|
|
let entriesLogged = 0;
|
|
let lastLine = void 0;
|
|
let entries = [];
|
|
const insertEntries = async () => {
|
|
await this.modlog.writeSQL(entries);
|
|
entriesLogged += entries.length;
|
|
if (!Config.nofswriting) {
|
|
process.stdout.clearLine(0);
|
|
process.stdout.cursorTo(0);
|
|
process.stdout.write(`Inserted ${entriesLogged} entries from '${roomid}'`);
|
|
}
|
|
entries = [];
|
|
};
|
|
for await (const line of lines) {
|
|
const entry = parseModlog(line, lastLine, roomid === "global");
|
|
lastLine = line;
|
|
if (!entry)
|
|
continue;
|
|
if (this.newestAllowedTimestamp && entry.time > this.newestAllowedTimestamp)
|
|
break;
|
|
if (roomid !== "global" && globalEntries[entry.roomID]?.includes(line)) {
|
|
continue;
|
|
}
|
|
if (entry.isGlobal) {
|
|
if (!globalEntries[entry.roomID])
|
|
globalEntries[entry.roomID] = [];
|
|
globalEntries[entry.roomID].push(line);
|
|
}
|
|
entries.push(entry);
|
|
if (entries.length === ENTRIES_TO_BUFFER)
|
|
await insertEntries();
|
|
}
|
|
delete globalEntries[roomid];
|
|
await insertEntries();
|
|
if (entriesLogged)
|
|
process.stdout.write("\n");
|
|
}
|
|
return this.modlog.database;
|
|
}
|
|
}
|
|
class ModlogConverterTest {
|
|
constructor(inputDir, outputDir) {
|
|
this.inputDir = inputDir;
|
|
this.outputDir = outputDir;
|
|
}
|
|
async toTxt() {
|
|
const files = await (0, import_lib.FS)(this.inputDir).readdir();
|
|
if (files.includes("modlog_global.txt")) {
|
|
files.splice(files.indexOf("modlog_global.txt"), 1);
|
|
files.push("modlog_global.txt");
|
|
}
|
|
const globalEntries = [];
|
|
for (const file of files) {
|
|
if (file === "README.md")
|
|
continue;
|
|
const roomid = file.slice(7, -4);
|
|
let entriesLogged = 0;
|
|
let lastLine = void 0;
|
|
let entries = [];
|
|
const insertEntries = async () => {
|
|
if (roomid === "global")
|
|
return;
|
|
entriesLogged += entries.length;
|
|
if (!Config.nofswriting && (entriesLogged % ENTRIES_TO_BUFFER === 0 || entriesLogged < ENTRIES_TO_BUFFER)) {
|
|
process.stdout.clearLine(0);
|
|
process.stdout.cursorTo(0);
|
|
process.stdout.write(`Wrote ${entriesLogged} entries from '${roomid}'`);
|
|
}
|
|
await (0, import_lib.FS)(`${this.outputDir}/modlog_${roomid}.txt`).append(entries.join(""));
|
|
entries = [];
|
|
};
|
|
const readStream = (0, import_lib.FS)(`${this.inputDir}/${file}`).createReadStream();
|
|
for await (const line of readStream.byLine()) {
|
|
const entry = parseModlog(line, lastLine, roomid === "global");
|
|
lastLine = line;
|
|
if (!entry)
|
|
continue;
|
|
const rawLog = rawifyLog(entry);
|
|
if (roomid !== "global")
|
|
entries.push(rawLog);
|
|
if (entry.isGlobal) {
|
|
globalEntries.push(rawLog);
|
|
}
|
|
if (entries.length === ENTRIES_TO_BUFFER)
|
|
await insertEntries();
|
|
}
|
|
await insertEntries();
|
|
if (entriesLogged)
|
|
process.stdout.write("\n");
|
|
}
|
|
if (!Config.nofswriting)
|
|
console.log(`Writing the global modlog...`);
|
|
await (0, import_lib.FS)(`${this.outputDir}/modlog_global.txt`).append(globalEntries.join(""));
|
|
}
|
|
}
|
|
const ModlogConverter = {
|
|
async convert(from, to, databasePath, textLogDirectoryPath, outputLogPath, newestAllowedTimestamp) {
|
|
if (from === "txt" && to === "txt" && outputLogPath) {
|
|
const converter = new ModlogConverterTest(textLogDirectoryPath, outputLogPath);
|
|
await converter.toTxt();
|
|
console.log("\nDone!");
|
|
process.exit();
|
|
} else if (from === "sqlite" && to === "txt") {
|
|
const converter = new ModlogConverterSQLite(databasePath, textLogDirectoryPath, void 0, newestAllowedTimestamp);
|
|
await converter.toTxt();
|
|
console.log("\nDone!");
|
|
process.exit();
|
|
} else if (from === "txt" && to === "sqlite") {
|
|
const converter = new ModlogConverterTxt(databasePath, textLogDirectoryPath, void 0, newestAllowedTimestamp);
|
|
await converter.toSQLite();
|
|
console.log("\nDone!");
|
|
process.exit();
|
|
}
|
|
}
|
|
};
|
|
//# sourceMappingURL=converter.js.map
|