/**
* Modlog
* Pokemon Showdown - http://pokemonshowdown.com/
*
* Interface for viewing and searching modlog. These run in a
* subprocess.
*
* Also handles searching battle logs.
*
* Actually writing to modlog is handled in chat.js, rooms.js, and
* roomlogs.js
*
* @license MIT
*/
'use strict';
const FS = require('../../.lib-dist/fs').FS;
const path = require('path');
const Dashycode = require('../../.lib-dist/dashycode');
const util = require('util');
const execFile = util.promisify(require('child_process').execFile);
const MAX_PROCESSES = 1;
const MAX_QUERY_LENGTH = 2500;
const DEFAULT_RESULTS_LENGTH = 100;
const MORE_BUTTON_INCREMENTS = [200, 400, 800, 1600, 3200];
// If a modlog query takes longer than this, it will be logged.
const LONG_QUERY_DURATION = 2000;
const LINES_SEPARATOR = 'lines=';
const MAX_RESULTS_LENGTH = MORE_BUTTON_INCREMENTS[MORE_BUTTON_INCREMENTS.length - 1];
const LOG_PATH = 'logs/modlog/';
class SortedLimitedLengthList {
constructor(maxSize) {
this.maxSize = maxSize;
this.list = [];
}
getListClone() {
return this.list.slice();
}
insert(element) {
let insertedAt = -1;
for (let i = this.list.length - 1; i >= 0; i--) {
if (element.localeCompare(this.list[i]) < 0) {
insertedAt = i + 1;
if (i === this.list.length - 1) {
this.list.push(element);
break;
}
this.list.splice(i + 1, 0, element);
break;
}
}
if (insertedAt < 0) this.list.splice(0, 0, element);
if (this.list.length > this.maxSize) {
this.list.pop();
}
}
}
/*********************************************************
* Modlog Functions
*********************************************************/
function checkRipgrepAvailability() {
if (Config.ripgrepmodlog === undefined) {
Config.ripgrepmodlog = (async () => {
try {
await execFile('rg', ['--version'], {cwd: path.normalize(`${__dirname}/../`)});
await execFile('tac', ['--version'], {cwd: path.normalize(`${__dirname}/../`)});
return true;
} catch (error) {
return false;
}
})();
}
return Config.ripgrepmodlog;
}
function getMoreButton(roomid, search, useExactSearch, lines, maxLines) {
let newLines = 0;
for (let increase of MORE_BUTTON_INCREMENTS) {
if (increase > lines) {
newLines = increase;
break;
}
}
if (!newLines || lines < maxLines) {
return ''; // don't show a button if no more pre-set increments are valid or if the amount of results is already below the max
} else {
if (useExactSearch) search = Chat.escapeHTML(`"${search}"`);
return `
[${date}]
`;
} else {
date = ``;
}
if (!line) {
return `${date}[${timestamp}] \u2190 current server time`;
}
let parenIndex = line.indexOf(')');
let thisRoomID = line.slice(bracketIndex + 3, parenIndex);
if (addModlogLinks) {
let url = Config.modloglink(time, thisRoomID);
if (url) timestamp = `${timestamp}`;
}
return `${date}[${timestamp}] (${thisRoomID})${Chat.escapeHTML(line.slice(parenIndex + 1))}`;
}).join(`
`);
let preamble;
const modlogid = roomid + (searchString ? '-' + Dashycode.encode(searchString) : '');
if (searchString) {
const searchStringDescription = (exactSearch ? `containing the string "${searchString}"` : `matching the username "${searchString}"`);
preamble = `>view-modlog-${modlogid}\n|init|html\n|title|[Modlog]${title}\n` +
`|pagehtml|
The last ${Chat.count(lines, "logged actions")} ${searchStringDescription} on ${roomName}.` + (exactSearch ? "" : " Add quotes to the search parameter to search for a phrase, rather than a user."); } else { preamble = `>view-modlog-${modlogid}\n|init|html\n|title|[Modlog]${title}\n` + `|pagehtml|
The last ${Chat.count(lines, "lines")} of the Moderator Log of ${roomName}.`; } let moreButton = getMoreButton(roomid, searchString, exactSearch, lines, maxLines); return `${preamble}${resultString}${moreButton}
${tierid} battles on ${date} where ${userid} was a player and the battle lasted less than ${turnLimit} turn${Chat.plural(turnLimit)}:
`; buf += `${userid}'s ${tierid} Battles | |
|---|---|
| Category | Number |
| Total Battles | ${data.totalBattles} |
| Total Wins | ${data.totalWins} |
| Total Losses | ${data.totalLosses} |
| Total Ties | ${data.totalTies} |
| Opponent | Times Battled |
| ${foe} | ${data[foe]} |
Userid: ${userid}
Maximum Turns: ${turnLimit}
`; const months = (await FS('logs/').readdir()).filter(f => f.length === 7 && f.includes('-')).sort((aKey, bKey) => { const a = aKey.split('-').map(n => parseInt(n)); const b = bKey.split('-').map(n => parseInt(n)); if (a[0] !== b[0]) return b[0] - a[0]; return b[1] - a[1]; }); let month = args.shift(); if (!month) { buf += `Please select a month:
Please select the tier to search:
`; } else { let tierids = tiers.map(toId); if (!tierids.includes(tierid)) return buf + `Invalid tier selected. `; this.title += `[${tierid}]`; buf += ``; } let date = args.shift(); const days = (await FS(`logs/${month}/${tierid}/`).readdir()).sort((a, b) => { a = a.split('-').map(n => parseInt(n)); b = b.split('-').map(n => parseInt(n)); if (a[0] !== b[0]) return b[0] - a[0]; if (a[1] !== b[1]) return b[1] - a[1]; return b[2] - a[2]; }); if (!date) { buf += `Please select the date to search:
`; } else { date = date += `-${args.shift()}-${args.shift()}`; if (!days.includes(date)) return buf + `Invalid date selected. `; this.title += `[${date}]`; buf += ``; } if (args[0] !== 'confirm') { buf += `Are you sure you want to run a battle search for for ${tierid} battles on ${date} where ${userid} was a player and the battle lasted less than ${turnLimit} turn${Chat.plural(turnLimit)}?
`; buf += ``; return buf + ``; } // Run search getBattleSearch(connection, userid, turnLimit, month, tierid, date); return `Searching for ${tierid} battles on ${date} where ${userid} was a player and the battle lasted less than ${turnLimit} turn${Chat.plural(turnLimit)}.
Loading... (this will take a while)