Jofthomas's picture
Jofthomas HF staff
Upload 4781 files
5c2ed06 verified
{
"version": 3,
"sources": ["../../../server/modlog/index.ts"],
"sourcesContent": ["/**\n * Modlog\n * Pokemon Showdown - http://pokemonshowdown.com/\n *\n * Moderator actions are logged into a set of files known as the moderation log, or \"modlog.\"\n * This file handles reading, writing, and querying the modlog.\n *\n * @license MIT\n */\n\nimport { SQL, Utils, FS } from '../../lib';\nimport { Config } from '../config-loader';\n\n// If a modlog query takes longer than this, it will be logged.\nconst LONG_QUERY_DURATION = 2000;\n\nconst MODLOG_SCHEMA_PATH = 'databases/schemas/modlog.sql';\nconst MODLOG_V2_MIGRATION_PATH = 'databases/migrations/modlog/v2.sql';\n\nexport const MODLOG_DB_PATH = Config.nofswriting ? ':memory:' : FS(`databases/modlog.db`).path;\n\nconst GLOBAL_PUNISHMENTS = [\n\t'WEEKLOCK', 'LOCK', 'BAN', 'RANGEBAN', 'RANGELOCK', 'FORCERENAME',\n\t'TICKETBAN', 'AUTOLOCK', 'AUTONAMELOCK', 'NAMELOCK', 'AUTOBAN', 'MONTHLOCK',\n\t'AUTOWEEKLOCK', 'WEEKNAMELOCK', 'FORCEWEEKLOCK', 'FORCELOCK', 'FORCEMONTHLOCK',\n\t'FORCERENAME OFFLINE',\n];\n\nconst PUNISHMENTS = [\n\t...GLOBAL_PUNISHMENTS, 'ROOMBAN', 'WEEKROOMBAN', 'UNROOMBAN', 'WARN', 'MUTE', 'HOURMUTE', 'UNMUTE',\n\t'CRISISDEMOTE', 'UNLOCK', 'UNLOCKNAME', 'UNLOCKRANGE', 'UNLOCKIP', 'UNBAN',\n\t'UNRANGEBAN', 'TRUSTUSER', 'UNTRUSTUSER', 'BLACKLIST', 'BATTLEBAN', 'UNBATTLEBAN',\n\t'NAMEBLACKLIST', 'KICKBATTLE', 'UNTICKETBAN', 'HIDETEXT', 'HIDEALTSTEXT', 'REDIRECT',\n\t'NOTE', 'MAFIAHOSTBAN', 'MAFIAUNHOSTBAN', 'MAFIAGAMEBAN', 'MAFIAUNGAMEBAN', 'GIVEAWAYBAN', 'GIVEAWAYUNBAN',\n\t'TOUR BAN', 'TOUR UNBAN', 'UNNAMELOCK', 'PERMABLACKLIST',\n];\n\nexport type ModlogID = RoomID | 'global' | 'all';\ninterface SQLQuery {\n\tquery: string;\n\targs: (string | number)[];\n}\ninterface ModlogResults {\n\tresults: (ModlogEntry & { entryID: number })[];\n\tduration: number;\n}\n\ninterface ModlogSQLQuery<T> {\n\tqueryText: string;\n\targs: T[];\n\treturnsResults?: boolean;\n}\n\nexport interface ModlogSearch {\n\tnote: { search: string, isExact?: boolean, isExclusion?: boolean }[];\n\tuser: { search: string, isExact?: boolean, isExclusion?: boolean }[];\n\tip: { search: string, isExclusion?: boolean }[];\n\taction: { search: string, isExclusion?: boolean }[];\n\tactionTaker: { search: string, isExclusion?: boolean }[];\n}\n\nexport interface ModlogEntry {\n\taction: string;\n\troomID: string;\n\tvisualRoomID: string;\n\tuserid: ID | null;\n\tautoconfirmedID: ID | null;\n\talts: ID[];\n\tip: string | null;\n\tisGlobal: boolean;\n\tloggedBy: ID | null;\n\tnote: string;\n\t/** Milliseconds since the epoch */\n\ttime: number;\n}\n\nexport interface TransactionArguments extends Record<string, unknown> {\n\tentries: Iterable<ModlogEntry>;\n\tmodlogInsertionStatement: string;\n\taltsInsertionStatement: string;\n}\n\nexport type PartialModlogEntry = Partial<ModlogEntry> & { action: string };\n\nexport class Modlog {\n\treadonly database: SQL.DatabaseManager;\n\treadyPromise: Promise<void> | null;\n\tprivate databaseReady: boolean;\n\t/** entries to be written once the DB is ready */\n\tqueuedEntries: ModlogEntry[];\n\n\tmodlogInsertionQuery: SQL.Statement | null = null;\n\taltsInsertionQuery: SQL.Statement | null = null;\n\trenameQuery: SQL.Statement | null = null;\n\tglobalPunishmentsSearchQuery: SQL.Statement | null = null;\n\n\tconstructor(databasePath: string, options: Partial<SQL.Options>) {\n\t\tthis.queuedEntries = [];\n\t\tthis.databaseReady = false;\n\t\tif (!options.onError) {\n\t\t\toptions.onError = (error, data, isParent) => {\n\t\t\t\tif (!isParent) return;\n\t\t\t\tMonitor.crashlog(error, 'A modlog SQLite query', {\n\t\t\t\t\tquery: JSON.stringify(data),\n\t\t\t\t});\n\t\t\t};\n\t\t}\n\t\tthis.database = SQL(module, {\n\t\t\tfile: databasePath,\n\t\t\textension: 'server/modlog/transactions.js',\n\t\t\t...options,\n\t\t});\n\n\t\tif (Config.usesqlite) {\n\t\t\tif (this.database.isParentProcess) {\n\t\t\t\tthis.database.spawn(Config.modlogprocesses || 1);\n\t\t\t} else {\n\t\t\t\tglobal.Monitor = {\n\t\t\t\t\tcrashlog(error: Error, source = 'A modlog child process', details: AnyObject | null = null) {\n\t\t\t\t\t\tconst repr = JSON.stringify([error.name, error.message, source, details]);\n\t\t\t\t\t\tprocess.send!(`THROW\\n@!!@${repr}\\n${error.stack}`);\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t\tprocess.on('uncaughtException', err => {\n\t\t\t\t\tMonitor.crashlog(err, 'A modlog database process');\n\t\t\t\t});\n\t\t\t\tprocess.on('unhandledRejection', err => {\n\t\t\t\t\tMonitor.crashlog(err as Error, 'A modlog database process');\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tthis.readyPromise = this.setupDatabase().then(result => {\n\t\t\tthis.databaseReady = result;\n\t\t\tthis.readyPromise = null;\n\t\t});\n\t}\n\n\tasync setupDatabase() {\n\t\tif (!Config.usesqlite) return false;\n\t\tawait this.database.exec(\"PRAGMA foreign_keys = ON;\");\n\t\tawait this.database.exec(`PRAGMA case_sensitive_like = true;`);\n\n\t\t// Set up tables, etc\n\t\tconst dbExists = await this.database.get(`SELECT * FROM sqlite_master WHERE name = 'modlog'`);\n\t\tif (!dbExists) {\n\t\t\tawait this.database.runFile(MODLOG_SCHEMA_PATH);\n\t\t}\n\n\t\tconst { hasDBInfo } = await this.database.get(\n\t\t\t`SELECT count(*) AS hasDBInfo FROM sqlite_master WHERE type = 'table' AND name = 'db_info'`\n\t\t);\n\n\t\tif (hasDBInfo === 0) {\n\t\t\t// needs v2 migration\n\t\t\tconst warnFunction = ('Monitor' in global && Monitor.warn) ? Monitor.warn : console.log;\n\t\t\twarnFunction(`The modlog database is being migrated to version 2; this may take a while.`);\n\t\t\tawait this.database.runFile(MODLOG_V2_MIGRATION_PATH);\n\t\t\twarnFunction(`Modlog database migration complete.`);\n\t\t}\n\n\t\tthis.modlogInsertionQuery = await this.database.prepare(\n\t\t\t`INSERT INTO modlog (timestamp, roomid, visual_roomid, action, userid, autoconfirmed_userid, ip, action_taker_userid, is_global, note)` +\n\t\t\t` VALUES ($time, $roomID, $visualRoomID, $action, $userid, $autoconfirmedID, $ip, $loggedBy, $isGlobal, $note)`\n\t\t);\n\t\tthis.altsInsertionQuery = await this.database.prepare(`INSERT INTO alts (modlog_id, userid) VALUES (?, ?)`);\n\t\tthis.renameQuery = await this.database.prepare(`UPDATE modlog SET roomid = ? WHERE roomid = ?`);\n\t\tthis.globalPunishmentsSearchQuery = await this.database.prepare(\n\t\t\t`SELECT * FROM modlog WHERE is_global = 1 ` +\n\t\t\t`AND (userid = ? OR autoconfirmed_userid = ? OR EXISTS(SELECT * FROM alts WHERE alts.modlog_id = modlog.modlog_id AND userid = ?)) ` +\n\t\t\t`AND timestamp > ? ` +\n\t\t\t`AND action IN (${Utils.formatSQLArray(GLOBAL_PUNISHMENTS, [])})`\n\t\t);\n\t\tawait this.writeSQL(this.queuedEntries);\n\t\treturn true;\n\t}\n\n\t/******************\n\t * Helper methods *\n\t ******************/\n\tgetSharedID(roomid: ModlogID): ID | false {\n\t\treturn roomid.includes('-') ? `${toID(roomid.split('-')[0])}-rooms` as ID : false;\n\t}\n\n\t/**************************************\n\t * Methods for writing to the modlog. *\n\t **************************************/\n\n\t/** @deprecated Modlogs use SQLite and no longer need initialization. */\n\tinitialize(roomid: ModlogID) {}\n\n\t/**\n\t * Writes to the modlog\n\t */\n\tasync write(roomid: string, entry: PartialModlogEntry, overrideID?: string) {\n\t\tif (!Config.usesqlite || !Config.usesqlitemodlog) return;\n\t\tconst roomID = entry.roomID || roomid;\n\t\tconst insertableEntry: ModlogEntry = {\n\t\t\taction: entry.action,\n\t\t\troomID,\n\t\t\tvisualRoomID: overrideID || entry.visualRoomID || '',\n\t\t\tuserid: entry.userid || null,\n\t\t\tautoconfirmedID: entry.autoconfirmedID || null,\n\t\t\talts: entry.alts ? [...new Set(entry.alts)] : [],\n\t\t\tip: entry.ip || null,\n\t\t\tisGlobal: entry.isGlobal || roomID === 'global' || false,\n\t\t\tloggedBy: entry.loggedBy || null,\n\t\t\tnote: entry.note || '',\n\t\t\ttime: entry.time || Date.now(),\n\t\t};\n\n\t\tawait this.writeSQL([insertableEntry]);\n\t}\n\n\tasync writeSQL(entries: Iterable<ModlogEntry>) {\n\t\tif (!Config.usesqlite) return;\n\t\tif (!this.databaseReady) {\n\t\t\tthis.queuedEntries.push(...entries);\n\t\t\treturn;\n\t\t}\n\t\tconst toInsert: TransactionArguments = {\n\t\t\tentries,\n\t\t\tmodlogInsertionStatement: this.modlogInsertionQuery!.toString(),\n\t\t\taltsInsertionStatement: this.altsInsertionQuery!.toString(),\n\t\t};\n\t\tawait this.database.transaction('insertion', toInsert);\n\t}\n\n\t/**\n\t * @deprecated Modlogs use SQLite and no longer need to be destroyed\n\t */\n\tasync destroy(roomid: ModlogID) {\n\t\treturn Promise.resolve(undefined);\n\t}\n\n\tdestroyAllSQLite() {\n\t\tif (!this.database) return;\n\t\tvoid this.database.destroy();\n\t\tthis.databaseReady = false;\n\t}\n\n\tdestroyAll() {\n\t\tthis.destroyAllSQLite();\n\t}\n\n\tasync rename(oldID: ModlogID, newID: ModlogID) {\n\t\tif (!Config.usesqlite) return;\n\t\tif (oldID === newID) return;\n\n\t\t// rename SQL modlogs\n\t\tif (this.readyPromise) await this.readyPromise;\n\t\tif (this.databaseReady) {\n\t\t\tawait this.database.run(this.renameQuery!, [newID, oldID]);\n\t\t} else {\n\t\t\t// shouldn't happen since we await the ready promise and check that useslite is on\n\t\t\t// but will still happen if usesqlite is enabled without a subsequent hotpatch\n\t\t\tthrow new Error(`Attempted to rename a room's modlog before the SQL database was ready.`);\n\t\t}\n\t}\n\n\t/******************************************\n\t * Methods for reading (searching) modlog *\n\t ******************************************/\n\tasync getGlobalPunishments(user: User | string, days = 30) {\n\t\tif (!Config.usesqlite || !Config.usesqlitemodlog) return null;\n\t\treturn this.getGlobalPunishmentsSQL(toID(user), days);\n\t}\n\n\tasync getGlobalPunishmentsSQL(userid: ID, days: number) {\n\t\tif (this.readyPromise) await this.readyPromise;\n\n\t\tif (!this.globalPunishmentsSearchQuery) {\n\t\t\tthrow new Error(`Modlog#globalPunishmentsSearchQuery is falsy but an SQL search function was called.`);\n\t\t}\n\t\tconst args: (string | number)[] = [\n\t\t\tuserid, userid, userid, Date.now() - (days * 24 * 60 * 60 * 1000), ...GLOBAL_PUNISHMENTS,\n\t\t];\n\t\tconst results = await this.database.all(this.globalPunishmentsSearchQuery, args);\n\t\treturn results.length;\n\t}\n\n\t/**\n\t * Searches the modlog.\n\t *\n\t * @returns Either a promise for ModlogResults or `null` if modlog is disabled.\n\t */\n\tasync search(\n\t\troomid: ModlogID = 'global',\n\t\tsearch: ModlogSearch = { note: [], user: [], ip: [], action: [], actionTaker: [] },\n\t\tmaxLines = 20,\n\t\tonlyPunishments = false,\n\t): Promise<ModlogResults | null> {\n\t\tif (!Config.usesqlite || !Config.usesqlitemodlog) return null;\n\t\tconst startTime = Date.now();\n\n\t\tlet rooms: ModlogID[] | 'all';\n\t\tif (roomid === 'public') {\n\t\t\trooms = [...Rooms.rooms.values()]\n\t\t\t\t.filter(room => !room.settings.isPrivate && !room.settings.isPersonal)\n\t\t\t\t.map(room => room.roomid);\n\t\t} else if (roomid === 'all') {\n\t\t\trooms = 'all';\n\t\t} else {\n\t\t\trooms = [roomid];\n\t\t}\n\n\t\tif (this.readyPromise) await this.readyPromise;\n\t\tif (!this.databaseReady) return null;\n\t\tconst query = this.prepareSQLSearch(rooms, maxLines, onlyPunishments, search);\n\t\tconst results = (await this.database.all(query.queryText, query.args))\n\t\t\t.map((row: any) => this.dbRowToModlogEntry(row));\n\n\t\tconst duration = Date.now() - startTime;\n\t\tif (duration > LONG_QUERY_DURATION) {\n\t\t\tMonitor.slow(`[slow SQL modlog search] ${duration}ms - ${JSON.stringify(query)}`);\n\t\t}\n\t\treturn { results, duration };\n\t}\n\n\tdbRowToModlogEntry(row: any): ModlogEntry & { entryID: number } {\n\t\treturn {\n\t\t\tentryID: row.modlog_id,\n\t\t\taction: row.action,\n\t\t\troomID: row.roomid,\n\t\t\tvisualRoomID: row.visual_roomid,\n\t\t\tuserid: row.userid,\n\t\t\tautoconfirmedID: row.autoconfirmed_userid,\n\t\t\talts: row.alts?.split(',') || [],\n\t\t\tip: row.ip || null,\n\t\t\tisGlobal: Boolean(row.is_global),\n\t\t\tloggedBy: row.action_taker_userid,\n\t\t\tnote: row.note,\n\t\t\ttime: row.timestamp,\n\t\t};\n\t}\n\n\t/**\n\t * This is a helper method to build SQL queries optimized to better utilize indices.\n\t * This was discussed in https://psim.us/devdiscord (although the syntax is slightly different in practice):\n\t * https://discord.com/channels/630837856075513856/630845310033330206/766736895132303371\n\t *\n\t * @param select A query fragment of the form `SELECT ... FROM ...`\n\t * @param ors Each OR condition fragment (e.g. `userid = ?`)\n\t * @param ands Each AND conditions to be appended to every OR condition (e.g. `roomid = ?`)\n\t * @param sortAndLimit A fragment of the form `ORDER BY ... LIMIT ...`\n\t */\n\tbuildParallelIndexScanQuery(\n\t\tselect: string,\n\t\tors: SQLQuery[],\n\t\tands: SQLQuery[],\n\t\tsortAndLimit: SQLQuery\n\t): ModlogSQLQuery<string | number> {\n\t\tif (!this.database) throw new Error(`Parallel index scan queries cannot be built when SQLite is not enabled.`);\n\t\t// assemble AND fragment\n\t\tlet andQuery = ``;\n\t\tconst andArgs = [];\n\t\tfor (const and of ands) {\n\t\t\tif (andQuery.length) andQuery += ` AND `;\n\t\t\tandQuery += and.query;\n\t\t\tandArgs.push(...and.args);\n\t\t}\n\n\t\t// assemble query\n\t\tlet query = ``;\n\t\tconst args = [];\n\t\tif (!ors.length) {\n\t\t\tquery = `${select} ${andQuery ? ` WHERE ${andQuery}` : ``}`;\n\t\t\targs.push(...andArgs);\n\t\t} else {\n\t\t\tfor (const or of ors) {\n\t\t\t\tif (query.length) query += ` UNION `;\n\t\t\t\tquery += `SELECT * FROM (${select} WHERE ${or.query} ${andQuery ? ` AND ${andQuery}` : ``} ${sortAndLimit.query})`;\n\t\t\t\targs.push(...or.args, ...andArgs, ...sortAndLimit.args);\n\t\t\t}\n\t\t}\n\t\tquery += ` ${sortAndLimit.query}`;\n\t\targs.push(...sortAndLimit.args);\n\n\t\treturn {\n\t\t\tqueryText: query,\n\t\t\targs,\n\t\t};\n\t}\n\n\tprepareSQLSearch(\n\t\trooms: ModlogID[] | 'all',\n\t\tmaxLines: number,\n\t\tonlyPunishments: boolean,\n\t\tsearch: ModlogSearch\n\t): ModlogSQLQuery<string | number> {\n\t\tconst select = `SELECT *, (SELECT group_concat(userid, ',') FROM alts WHERE alts.modlog_id = modlog.modlog_id) as alts FROM modlog`;\n\t\tconst ors = [];\n\t\tconst ands = [];\n\t\tconst sortAndLimit = { query: `ORDER BY timestamp DESC`, args: [] } as SQLQuery;\n\t\tif (maxLines) {\n\t\t\tsortAndLimit.query += ` LIMIT ?`;\n\t\t\tsortAndLimit.args.push(maxLines);\n\t\t}\n\n\t\t// Limit the query to only the specified rooms, treating \"global\" as a pseudo-room that checks is_global\n\t\t// (This is because the text modlog system gave global modlog entries their own file, as a room would have.)\n\t\tif (rooms !== 'all') {\n\t\t\tconst args: (string | number)[] = [];\n\t\t\tlet roomChecker = `roomid IN (${Utils.formatSQLArray(rooms, args)})`;\n\t\t\tif (rooms.includes('global')) {\n\t\t\t\tif (rooms.length > 1) {\n\t\t\t\t\troomChecker = `(is_global = 1 OR ${roomChecker})`;\n\t\t\t\t} else {\n\t\t\t\t\troomChecker = `is_global = 1`;\n\t\t\t\t\t// remove the room argument added by the initial roomChecker assignment\n\t\t\t\t\targs.pop();\n\t\t\t\t}\n\t\t\t}\n\t\t\tands.push({ query: roomChecker, args });\n\t\t}\n\n\t\tfor (const action of search.action) {\n\t\t\tconst args = [action.search + '%'];\n\t\t\tif (action.isExclusion) {\n\t\t\t\tands.push({ query: `action NOT LIKE ?`, args });\n\t\t\t} else {\n\t\t\t\tands.push({ query: `action LIKE ?`, args });\n\t\t\t}\n\t\t}\n\t\tif (onlyPunishments) {\n\t\t\tconst args: (string | number)[] = [];\n\t\t\tands.push({ query: `action IN (${Utils.formatSQLArray(PUNISHMENTS, args)})`, args });\n\t\t}\n\n\t\tfor (const ip of search.ip) {\n\t\t\tconst args = [ip.search + '%'];\n\t\t\tif (ip.isExclusion) {\n\t\t\t\tands.push({ query: `ip NOT LIKE ?`, args });\n\t\t\t} else {\n\t\t\t\tands.push({ query: `ip LIKE ?`, args });\n\t\t\t}\n\t\t}\n\t\tfor (const actionTaker of search.actionTaker) {\n\t\t\tconst args = [actionTaker.search + '%'];\n\t\t\tif (actionTaker.isExclusion) {\n\t\t\t\tands.push({ query: `action_taker_userid NOT LIKE ?`, args });\n\t\t\t} else {\n\t\t\t\tands.push({ query: `action_taker_userid LIKE ?`, args });\n\t\t\t}\n\t\t}\n\n\t\tfor (const noteSearch of search.note) {\n\t\t\tconst tester = noteSearch.isExact ? `= ?` : `LIKE ?`;\n\t\t\tconst args = [noteSearch.isExact ? noteSearch.search : `%${noteSearch.search}%`];\n\t\t\tif (noteSearch.isExclusion) {\n\t\t\t\tands.push({ query: `note ${noteSearch.isExact ? '!' : 'NOT '}${tester}`, args });\n\t\t\t} else {\n\t\t\t\tands.push({ query: `note ${tester}`, args });\n\t\t\t}\n\t\t}\n\n\t\tfor (const user of search.user) {\n\t\t\tlet tester;\n\t\t\tlet param;\n\t\t\tif (user.isExact) {\n\t\t\t\ttester = user.isExclusion ? `!= ?` : `= ?`;\n\t\t\t\tparam = user.search.toLowerCase();\n\t\t\t} else {\n\t\t\t\ttester = user.isExclusion ? `NOT LIKE ?` : `LIKE ?`;\n\t\t\t\tparam = user.search.toLowerCase() + '%';\n\t\t\t}\n\n\t\t\tors.push({ query: `(userid ${tester} OR autoconfirmed_userid ${tester})`, args: [param, param] });\n\t\t\tors.push({\n\t\t\t\tquery: `EXISTS(SELECT * FROM alts WHERE alts.modlog_id = modlog.modlog_id AND alts.userid ${tester})`,\n\t\t\t\targs: [param],\n\t\t\t});\n\t\t}\n\t\treturn this.buildParallelIndexScanQuery(select, ors, ands, sortAndLimit);\n\t}\n}\n\nexport const mainModlog = new Modlog(MODLOG_DB_PATH, { sqliteOptions: Config.modlogsqliteoptions });\n"],
"mappings": "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",
"names": []
}