{ "version": 3, "sources": ["../../../../server/modlog/index.ts"], "sourcesContent": ["/**\r\n * Modlog\r\n * Pokemon Showdown - http://pokemonshowdown.com/\r\n *\r\n * Moderator actions are logged into a set of files known as the moderation log, or \"modlog.\"\r\n * This file handles reading, writing, and querying the modlog.\r\n *\r\n * @license MIT\r\n */\r\n\r\nimport {SQL, Utils, FS} from '../../lib';\r\nimport {Config} from '../config-loader';\r\n\r\n// If a modlog query takes longer than this, it will be logged.\r\nconst LONG_QUERY_DURATION = 2000;\r\n\r\nconst MODLOG_SCHEMA_PATH = 'databases/schemas/modlog.sql';\r\nconst MODLOG_V2_MIGRATION_PATH = 'databases/migrations/modlog/v2.sql';\r\n\r\nexport const MODLOG_DB_PATH = Config.nofswriting ? ':memory:' : FS(`databases/modlog.db`).path;\r\n\r\nconst GLOBAL_PUNISHMENTS = [\r\n\t'WEEKLOCK', 'LOCK', 'BAN', 'RANGEBAN', 'RANGELOCK', 'FORCERENAME',\r\n\t'TICKETBAN', 'AUTOLOCK', 'AUTONAMELOCK', 'NAMELOCK', 'AUTOBAN', 'MONTHLOCK',\r\n\t'AUTOWEEKLOCK', 'WEEKNAMELOCK', 'FORCEWEEKLOCK', 'FORCELOCK', 'FORCEMONTHLOCK',\r\n\t'FORCERENAME OFFLINE',\r\n];\r\n\r\nconst PUNISHMENTS = [\r\n\t...GLOBAL_PUNISHMENTS, 'ROOMBAN', 'WEEKROOMBAN', 'UNROOMBAN', 'WARN', 'MUTE', 'HOURMUTE', 'UNMUTE',\r\n\t'CRISISDEMOTE', 'UNLOCK', 'UNLOCKNAME', 'UNLOCKRANGE', 'UNLOCKIP', 'UNBAN',\r\n\t'UNRANGEBAN', 'TRUSTUSER', 'UNTRUSTUSER', 'BLACKLIST', 'BATTLEBAN', 'UNBATTLEBAN',\r\n\t'NAMEBLACKLIST', 'KICKBATTLE', 'UNTICKETBAN', 'HIDETEXT', 'HIDEALTSTEXT', 'REDIRECT',\r\n\t'NOTE', 'MAFIAHOSTBAN', 'MAFIAUNHOSTBAN', 'MAFIAGAMEBAN', 'MAFIAUNGAMEBAN', 'GIVEAWAYBAN', 'GIVEAWAYUNBAN',\r\n\t'TOUR BAN', 'TOUR UNBAN', 'UNNAMELOCK',\r\n];\r\n\r\nexport type ModlogID = RoomID | 'global' | 'all';\r\ninterface SQLQuery {\r\n\tquery: string;\r\n\targs: (string | number)[];\r\n}\r\ninterface ModlogResults {\r\n\tresults: (ModlogEntry & {entryID: number})[];\r\n\tduration: number;\r\n}\r\n\r\ninterface ModlogSQLQuery {\r\n\tqueryText: string;\r\n\targs: T[];\r\n\treturnsResults?: boolean;\r\n}\r\n\r\nexport interface ModlogSearch {\r\n\tnote: {search: string, isExact?: boolean, isExclusion?: boolean}[];\r\n\tuser: {search: string, isExact?: boolean, isExclusion?: boolean}[];\r\n\tip: {search: string, isExclusion?: boolean}[];\r\n\taction: {search: string, isExclusion?: boolean}[];\r\n\tactionTaker: {search: string, isExclusion?: boolean}[];\r\n}\r\n\r\nexport interface ModlogEntry {\r\n\taction: string;\r\n\troomID: string;\r\n\tvisualRoomID: string;\r\n\tuserid: ID | null;\r\n\tautoconfirmedID: ID | null;\r\n\talts: ID[];\r\n\tip: string | null;\r\n\tisGlobal: boolean;\r\n\tloggedBy: ID | null;\r\n\tnote: string;\r\n\t/** Milliseconds since the epoch */\r\n\ttime: number;\r\n}\r\n\r\nexport interface TransactionArguments extends Record {\r\n\tentries: Iterable;\r\n\tmodlogInsertionStatement: string;\r\n\taltsInsertionStatement: string;\r\n}\r\n\r\nexport type PartialModlogEntry = Partial & {action: string};\r\n\r\nexport class Modlog {\r\n\treadonly database: SQL.DatabaseManager;\r\n\treadyPromise: Promise | null;\r\n\tprivate databaseReady: boolean;\r\n\t/** entries to be written once the DB is ready */\r\n\tqueuedEntries: ModlogEntry[];\r\n\r\n\tmodlogInsertionQuery: SQL.Statement | null = null;\r\n\taltsInsertionQuery: SQL.Statement | null = null;\r\n\trenameQuery: SQL.Statement | null = null;\r\n\tglobalPunishmentsSearchQuery: SQL.Statement | null = null;\r\n\r\n\tconstructor(databasePath: string, options: Partial) {\r\n\t\tthis.queuedEntries = [];\r\n\t\tthis.databaseReady = false;\r\n\t\tif (!options.onError) {\r\n\t\t\toptions.onError = (error, data, isParent) => {\r\n\t\t\t\tif (!isParent) return;\r\n\t\t\t\tMonitor.crashlog(error, 'A modlog SQLite query', {\r\n\t\t\t\t\tquery: JSON.stringify(data),\r\n\t\t\t\t});\r\n\t\t\t};\r\n\t\t}\r\n\t\tthis.database = SQL(module, {\r\n\t\t\tfile: databasePath,\r\n\t\t\textension: 'server/modlog/transactions.js',\r\n\t\t\t...options,\r\n\t\t});\r\n\r\n\t\tif (Config.usesqlite) {\r\n\t\t\tif (this.database.isParentProcess) {\r\n\t\t\t\tthis.database.spawn(Config.modlogprocesses || 1);\r\n\t\t\t} else {\r\n\t\t\t\tglobal.Monitor = {\r\n\t\t\t\t\tcrashlog(error: Error, source = 'A modlog child process', details: AnyObject | null = null) {\r\n\t\t\t\t\t\tconst repr = JSON.stringify([error.name, error.message, source, details]);\r\n\t\t\t\t\t\tprocess.send!(`THROW\\n@!!@${repr}\\n${error.stack}`);\r\n\t\t\t\t\t},\r\n\t\t\t\t};\r\n\t\t\t\tprocess.on('uncaughtException', err => {\r\n\t\t\t\t\tMonitor.crashlog(err, 'A modlog database process');\r\n\t\t\t\t});\r\n\t\t\t\tprocess.on('unhandledRejection', err => {\r\n\t\t\t\t\tMonitor.crashlog(err as Error, 'A modlog database process');\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.readyPromise = this.setupDatabase().then(result => {\r\n\t\t\tthis.databaseReady = result;\r\n\t\t\tthis.readyPromise = null;\r\n\t\t});\r\n\t}\r\n\r\n\tasync setupDatabase() {\r\n\t\tif (!Config.usesqlite) return false;\r\n\t\tawait this.database.exec(\"PRAGMA foreign_keys = ON;\");\r\n\t\tawait this.database.exec(`PRAGMA case_sensitive_like = true;`);\r\n\r\n\t\t// Set up tables, etc\r\n\t\tconst dbExists = await this.database.get(`SELECT * FROM sqlite_master WHERE name = 'modlog'`);\r\n\t\tif (!dbExists) {\r\n\t\t\tawait this.database.runFile(MODLOG_SCHEMA_PATH);\r\n\t\t}\r\n\r\n\t\tconst {hasDBInfo} = await this.database.get(\r\n\t\t\t`SELECT count(*) AS hasDBInfo FROM sqlite_master WHERE type = 'table' AND name = 'db_info'`\r\n\t\t);\r\n\r\n\t\tif (hasDBInfo === 0) {\r\n\t\t\t// needs v2 migration\r\n\t\t\tconst warnFunction = ('Monitor' in global && Monitor.warn) ? Monitor.warn : console.log;\r\n\t\t\twarnFunction(`The modlog database is being migrated to version 2; this may take a while.`);\r\n\t\t\tawait this.database.runFile(MODLOG_V2_MIGRATION_PATH);\r\n\t\t\twarnFunction(`Modlog database migration complete.`);\r\n\t\t}\r\n\r\n\t\tthis.modlogInsertionQuery = await this.database.prepare(\r\n\t\t\t`INSERT INTO modlog (timestamp, roomid, visual_roomid, action, userid, autoconfirmed_userid, ip, action_taker_userid, is_global, note)` +\r\n\t\t\t` VALUES ($time, $roomID, $visualRoomID, $action, $userid, $autoconfirmedID, $ip, $loggedBy, $isGlobal, $note)`\r\n\t\t);\r\n\t\tthis.altsInsertionQuery = await this.database.prepare(`INSERT INTO alts (modlog_id, userid) VALUES (?, ?)`);\r\n\t\tthis.renameQuery = await this.database.prepare(`UPDATE modlog SET roomid = ? WHERE roomid = ?`);\r\n\t\tthis.globalPunishmentsSearchQuery = await this.database.prepare(\r\n\t\t\t`SELECT * FROM modlog WHERE is_global = 1 ` +\r\n\t\t\t`AND (userid = ? OR autoconfirmed_userid = ? OR EXISTS(SELECT * FROM alts WHERE alts.modlog_id = modlog.modlog_id AND userid = ?)) ` +\r\n\t\t\t`AND timestamp > ? ` +\r\n\t\t\t`AND action IN (${Utils.formatSQLArray(GLOBAL_PUNISHMENTS, [])})`\r\n\t\t);\r\n\t\tawait this.writeSQL(this.queuedEntries);\r\n\t\treturn true;\r\n\t}\r\n\r\n\t/******************\r\n\t * Helper methods *\r\n\t ******************/\r\n\tgetSharedID(roomid: ModlogID): ID | false {\r\n\t\treturn roomid.includes('-') ? `${toID(roomid.split('-')[0])}-rooms` as ID : false;\r\n\t}\r\n\r\n\t/**************************************\r\n\t * Methods for writing to the modlog. *\r\n\t **************************************/\r\n\r\n\t/**\r\n\t * @deprecated Modlogs use SQLite and no longer need initialization.\r\n\t */\r\n\tinitialize(roomid: ModlogID) {\r\n\t\treturn;\r\n\t}\r\n\r\n\r\n\t/**\r\n\t * Writes to the modlog\r\n\t */\r\n\tasync write(roomid: string, entry: PartialModlogEntry, overrideID?: string) {\r\n\t\tif (!Config.usesqlite || !Config.usesqlitemodlog) return;\r\n\t\tconst roomID = entry.roomID || roomid;\r\n\t\tconst insertableEntry: ModlogEntry = {\r\n\t\t\taction: entry.action,\r\n\t\t\troomID,\r\n\t\t\tvisualRoomID: overrideID || entry.visualRoomID || '',\r\n\t\t\tuserid: entry.userid || null,\r\n\t\t\tautoconfirmedID: entry.autoconfirmedID || null,\r\n\t\t\talts: entry.alts ? [...new Set(entry.alts)] : [],\r\n\t\t\tip: entry.ip || null,\r\n\t\t\tisGlobal: entry.isGlobal || roomID === 'global' || false,\r\n\t\t\tloggedBy: entry.loggedBy || null,\r\n\t\t\tnote: entry.note || '',\r\n\t\t\ttime: entry.time || Date.now(),\r\n\t\t};\r\n\r\n\t\tawait this.writeSQL([insertableEntry]);\r\n\t}\r\n\r\n\tasync writeSQL(entries: Iterable) {\r\n\t\tif (!Config.usesqlite) return;\r\n\t\tif (!this.databaseReady) {\r\n\t\t\tthis.queuedEntries.push(...entries);\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tconst toInsert: TransactionArguments = {\r\n\t\t\tentries,\r\n\t\t\tmodlogInsertionStatement: this.modlogInsertionQuery!.toString(),\r\n\t\t\taltsInsertionStatement: this.altsInsertionQuery!.toString(),\r\n\t\t};\r\n\t\tawait this.database.transaction('insertion', toInsert);\r\n\t}\r\n\r\n\t/**\r\n\t * @deprecated Modlogs use SQLite and no longer need to be destroyed\r\n\t */\r\n\tasync destroy(roomid: ModlogID) {\r\n\t\treturn Promise.resolve(undefined);\r\n\t}\r\n\r\n\tdestroyAllSQLite() {\r\n\t\tif (!this.database) return;\r\n\t\tvoid this.database.destroy();\r\n\t\tthis.databaseReady = false;\r\n\t}\r\n\r\n\tdestroyAll() {\r\n\t\tthis.destroyAllSQLite();\r\n\t}\r\n\r\n\tasync rename(oldID: ModlogID, newID: ModlogID) {\r\n\t\tif (!Config.usesqlite) return;\r\n\t\tif (oldID === newID) return;\r\n\r\n\t\t// rename SQL modlogs\r\n\t\tif (this.readyPromise) await this.readyPromise;\r\n\t\tif (this.databaseReady) {\r\n\t\t\tawait this.database.run(this.renameQuery!, [newID, oldID]);\r\n\t\t} else {\r\n\t\t\t// shouldn't happen since we await the ready promise and check that useslite is on\r\n\t\t\t// but will still happen if usesqlite is enabled without a subsequent hotpatch\r\n\t\t\tthrow new Error(`Attempted to rename a room's modlog before the SQL database was ready.`);\r\n\t\t}\r\n\t}\r\n\r\n\t/******************************************\r\n\t * Methods for reading (searching) modlog *\r\n\t ******************************************/\r\n\tasync getGlobalPunishments(user: User | string, days = 30) {\r\n\t\tif (!Config.usesqlite || !Config.usesqlitemodlog) return null;\r\n\t\treturn this.getGlobalPunishmentsSQL(toID(user), days);\r\n\t}\r\n\r\n\tasync getGlobalPunishmentsSQL(userid: ID, days: number) {\r\n\t\tif (this.readyPromise) await this.readyPromise;\r\n\r\n\t\tif (!this.globalPunishmentsSearchQuery) {\r\n\t\t\tthrow new Error(`Modlog#globalPunishmentsSearchQuery is falsy but an SQL search function was called.`);\r\n\t\t}\r\n\t\tconst args: (string | number)[] = [\r\n\t\t\tuserid, userid, userid, Date.now() - (days * 24 * 60 * 60 * 1000), ...GLOBAL_PUNISHMENTS,\r\n\t\t];\r\n\t\tconst results = await this.database.all(this.globalPunishmentsSearchQuery, args);\r\n\t\treturn results.length;\r\n\t}\r\n\r\n\t/**\r\n\t * Searches the modlog.\r\n\t *\r\n\t * @returns Either a promise for ModlogResults or `null` if modlog is disabled.\r\n\t */\r\n\tasync search(\r\n\t\troomid: ModlogID = 'global',\r\n\t\tsearch: ModlogSearch = {note: [], user: [], ip: [], action: [], actionTaker: []},\r\n\t\tmaxLines = 20,\r\n\t\tonlyPunishments = false,\r\n\t): Promise {\r\n\t\tif (!Config.usesqlite || !Config.usesqlitemodlog) return null;\r\n\t\tconst startTime = Date.now();\r\n\r\n\t\tlet rooms: ModlogID[] | 'all';\r\n\t\tif (roomid === 'public') {\r\n\t\t\trooms = [...Rooms.rooms.values()]\r\n\t\t\t\t.filter(room => !room.settings.isPrivate && !room.settings.isPersonal)\r\n\t\t\t\t.map(room => room.roomid);\r\n\t\t} else if (roomid === 'all') {\r\n\t\t\trooms = 'all';\r\n\t\t} else {\r\n\t\t\trooms = [roomid];\r\n\t\t}\r\n\r\n\t\tif (this.readyPromise) await this.readyPromise;\r\n\t\tif (!this.databaseReady) return null;\r\n\t\tconst query = this.prepareSQLSearch(rooms, maxLines, onlyPunishments, search);\r\n\t\tconst results = (await this.database.all(query.queryText, query.args))\r\n\t\t\t.map((row: any) => this.dbRowToModlogEntry(row));\r\n\r\n\t\tconst duration = Date.now() - startTime;\r\n\t\tif (duration > LONG_QUERY_DURATION) {\r\n\t\t\tMonitor.slow(`[slow SQL modlog search] ${duration}ms - ${JSON.stringify(query)}`);\r\n\t\t}\r\n\t\treturn {results, duration};\r\n\t}\r\n\r\n\tdbRowToModlogEntry(row: any): ModlogEntry & {entryID: number} {\r\n\t\treturn {\r\n\t\t\tentryID: row.modlog_id,\r\n\t\t\taction: row.action,\r\n\t\t\troomID: row.roomid,\r\n\t\t\tvisualRoomID: row.visual_roomid,\r\n\t\t\tuserid: row.userid,\r\n\t\t\tautoconfirmedID: row.autoconfirmed_userid,\r\n\t\t\talts: row.alts?.split(',') || [],\r\n\t\t\tip: row.ip || null,\r\n\t\t\tisGlobal: Boolean(row.is_global),\r\n\t\t\tloggedBy: row.action_taker_userid,\r\n\t\t\tnote: row.note,\r\n\t\t\ttime: row.timestamp,\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * This is a helper method to build SQL queries optimized to better utilize indices.\r\n\t * This was discussed in https://psim.us/devdiscord (although the syntax is slightly different in practice):\r\n\t * https://discord.com/channels/630837856075513856/630845310033330206/766736895132303371\r\n\t *\r\n\t * @param select A query fragment of the form `SELECT ... FROM ...`\r\n\t * @param ors Each OR condition fragment (e.g. `userid = ?`)\r\n\t * @param ands Each AND conditions to be appended to every OR condition (e.g. `roomid = ?`)\r\n\t * @param sortAndLimit A fragment of the form `ORDER BY ... LIMIT ...`\r\n\t */\r\n\tbuildParallelIndexScanQuery(\r\n\t\tselect: string,\r\n\t\tors: SQLQuery[],\r\n\t\tands: SQLQuery[],\r\n\t\tsortAndLimit: SQLQuery\r\n\t): ModlogSQLQuery {\r\n\t\tif (!this.database) throw new Error(`Parallel index scan queries cannot be built when SQLite is not enabled.`);\r\n\t\t// assemble AND fragment\r\n\t\tlet andQuery = ``;\r\n\t\tconst andArgs = [];\r\n\t\tfor (const and of ands) {\r\n\t\t\tif (andQuery.length) andQuery += ` AND `;\r\n\t\t\tandQuery += and.query;\r\n\t\t\tandArgs.push(...and.args);\r\n\t\t}\r\n\r\n\t\t// assemble query\r\n\t\tlet query = ``;\r\n\t\tconst args = [];\r\n\t\tif (!ors.length) {\r\n\t\t\tquery = `${select} ${andQuery ? ` WHERE ${andQuery}` : ``}`;\r\n\t\t\targs.push(...andArgs);\r\n\t\t} else {\r\n\t\t\tfor (const or of ors) {\r\n\t\t\t\tif (query.length) query += ` UNION `;\r\n\t\t\t\tquery += `SELECT * FROM (${select} WHERE ${or.query} ${andQuery ? ` AND ${andQuery}` : ``} ${sortAndLimit.query})`;\r\n\t\t\t\targs.push(...or.args, ...andArgs, ...sortAndLimit.args);\r\n\t\t\t}\r\n\t\t}\r\n\t\tquery += ` ${sortAndLimit.query}`;\r\n\t\targs.push(...sortAndLimit.args);\r\n\r\n\t\treturn {\r\n\t\t\tqueryText: query,\r\n\t\t\targs,\r\n\t\t};\r\n\t}\r\n\r\n\tprepareSQLSearch(\r\n\t\trooms: ModlogID[] | 'all',\r\n\t\tmaxLines: number,\r\n\t\tonlyPunishments: boolean,\r\n\t\tsearch: ModlogSearch\r\n\t): ModlogSQLQuery {\r\n\t\tconst select = `SELECT *, (SELECT group_concat(userid, ',') FROM alts WHERE alts.modlog_id = modlog.modlog_id) as alts FROM modlog`;\r\n\t\tconst ors = [];\r\n\t\tconst ands = [];\r\n\t\tconst sortAndLimit = {query: `ORDER BY timestamp DESC`, args: []} as SQLQuery;\r\n\t\tif (maxLines) {\r\n\t\t\tsortAndLimit.query += ` LIMIT ?`;\r\n\t\t\tsortAndLimit.args.push(maxLines);\r\n\t\t}\r\n\r\n\t\t// Limit the query to only the specified rooms, treating \"global\" as a pseudo-room that checks is_global\r\n\t\t// (This is because the text modlog system gave global modlog entries their own file, as a room would have.)\r\n\t\tif (rooms !== 'all') {\r\n\t\t\tconst args: (string | number)[] = [];\r\n\t\t\tlet roomChecker = `roomid IN (${Utils.formatSQLArray(rooms, args)})`;\r\n\t\t\tif (rooms.includes('global')) {\r\n\t\t\t\tif (rooms.length > 1) {\r\n\t\t\t\t\troomChecker = `(is_global = 1 OR ${roomChecker})`;\r\n\t\t\t\t} else {\r\n\t\t\t\t\troomChecker = `is_global = 1`;\r\n\t\t\t\t\t// remove the room argument added by the initial roomChecker assignment\r\n\t\t\t\t\targs.pop();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tands.push({query: roomChecker, args});\r\n\t\t}\r\n\r\n\t\tfor (const action of search.action) {\r\n\t\t\tconst args = [action.search + '%'];\r\n\t\t\tif (action.isExclusion) {\r\n\t\t\t\tands.push({query: `action NOT LIKE ?`, args});\r\n\t\t\t} else {\r\n\t\t\t\tands.push({query: `action LIKE ?`, args});\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (onlyPunishments) {\r\n\t\t\tconst args: (string | number)[] = [];\r\n\t\t\tands.push({query: `action IN (${Utils.formatSQLArray(PUNISHMENTS, args)})`, args});\r\n\t\t}\r\n\r\n\t\tfor (const ip of search.ip) {\r\n\t\t\tconst args = [ip.search + '%'];\r\n\t\t\tif (ip.isExclusion) {\r\n\t\t\t\tands.push({query: `ip NOT LIKE ?`, args});\r\n\t\t\t} else {\r\n\t\t\t\tands.push({query: `ip LIKE ?`, args});\r\n\t\t\t}\r\n\t\t}\r\n\t\tfor (const actionTaker of search.actionTaker) {\r\n\t\t\tconst args = [actionTaker.search + '%'];\r\n\t\t\tif (actionTaker.isExclusion) {\r\n\t\t\t\tands.push({query: `action_taker_userid NOT LIKE ?`, args});\r\n\t\t\t} else {\r\n\t\t\t\tands.push({query: `action_taker_userid LIKE ?`, args});\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tfor (const noteSearch of search.note) {\r\n\t\t\tconst tester = noteSearch.isExact ? `= ?` : `LIKE ?`;\r\n\t\t\tconst args = [noteSearch.isExact ? noteSearch.search : `%${noteSearch.search}%`];\r\n\t\t\tif (noteSearch.isExclusion) {\r\n\t\t\t\tands.push({query: `note ${noteSearch.isExact ? '!' : 'NOT '}${tester}`, args});\r\n\t\t\t} else {\r\n\t\t\t\tands.push({query: `note ${tester}`, args});\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tfor (const user of search.user) {\r\n\t\t\tlet tester;\r\n\t\t\tlet param;\r\n\t\t\tif (user.isExact) {\r\n\t\t\t\ttester = user.isExclusion ? `!= ?` : `= ?`;\r\n\t\t\t\tparam = user.search.toLowerCase();\r\n\t\t\t} else {\r\n\t\t\t\ttester = user.isExclusion ? `NOT LIKE ?` : `LIKE ?`;\r\n\t\t\t\tparam = user.search.toLowerCase() + '%';\r\n\t\t\t}\r\n\r\n\t\t\tors.push({query: `(userid ${tester} OR autoconfirmed_userid ${tester})`, args: [param, param]});\r\n\t\t\tors.push({\r\n\t\t\t\tquery: `EXISTS(SELECT * FROM alts WHERE alts.modlog_id = modlog.modlog_id AND alts.userid ${tester})`,\r\n\t\t\t\targs: [param],\r\n\t\t\t});\r\n\t\t}\r\n\t\treturn this.buildParallelIndexScanQuery(select, ors, ands, sortAndLimit);\r\n\t}\r\n}\r\n\r\nexport const mainModlog = new Modlog(MODLOG_DB_PATH, {sqliteOptions: Config.modlogsqliteoptions});\r\n"], "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,iBAA6B;AAC7B,2BAAqB;AAXrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,MAAM,sBAAsB;AAE5B,MAAM,qBAAqB;AAC3B,MAAM,2BAA2B;AAE1B,MAAM,iBAAiB,4BAAO,cAAc,iBAAa,eAAG,qBAAqB,EAAE;AAE1F,MAAM,qBAAqB;AAAA,EAC1B;AAAA,EAAY;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAY;AAAA,EAAa;AAAA,EACpD;AAAA,EAAa;AAAA,EAAY;AAAA,EAAgB;AAAA,EAAY;AAAA,EAAW;AAAA,EAChE;AAAA,EAAgB;AAAA,EAAgB;AAAA,EAAiB;AAAA,EAAa;AAAA,EAC9D;AACD;AAEA,MAAM,cAAc;AAAA,EACnB,GAAG;AAAA,EAAoB;AAAA,EAAW;AAAA,EAAe;AAAA,EAAa;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAY;AAAA,EAC1F;AAAA,EAAgB;AAAA,EAAU;AAAA,EAAc;AAAA,EAAe;AAAA,EAAY;AAAA,EACnE;AAAA,EAAc;AAAA,EAAa;AAAA,EAAe;AAAA,EAAa;AAAA,EAAa;AAAA,EACpE;AAAA,EAAiB;AAAA,EAAc;AAAA,EAAe;AAAA,EAAY;AAAA,EAAgB;AAAA,EAC1E;AAAA,EAAQ;AAAA,EAAgB;AAAA,EAAkB;AAAA,EAAgB;AAAA,EAAkB;AAAA,EAAe;AAAA,EAC3F;AAAA,EAAY;AAAA,EAAc;AAC3B;AAiDO,MAAM,OAAO;AAAA,EAYnB,YAAY,cAAsB,SAA+B;AALjE,gCAA6C;AAC7C,8BAA2C;AAC3C,uBAAoC;AACpC,wCAAqD;AAGpD,SAAK,gBAAgB,CAAC;AACtB,SAAK,gBAAgB;AACrB,QAAI,CAAC,QAAQ,SAAS;AACrB,cAAQ,UAAU,CAAC,OAAO,MAAM,aAAa;AAC5C,YAAI,CAAC;AAAU;AACf,gBAAQ,SAAS,OAAO,yBAAyB;AAAA,UAChD,OAAO,KAAK,UAAU,IAAI;AAAA,QAC3B,CAAC;AAAA,MACF;AAAA,IACD;AACA,SAAK,eAAW,gBAAI,QAAQ;AAAA,MAC3B,MAAM;AAAA,MACN,WAAW;AAAA,MACX,GAAG;AAAA,IACJ,CAAC;AAED,QAAI,4BAAO,WAAW;AACrB,UAAI,KAAK,SAAS,iBAAiB;AAClC,aAAK,SAAS,MAAM,4BAAO,mBAAmB,CAAC;AAAA,MAChD,OAAO;AACN,eAAO,UAAU;AAAA,UAChB,SAAS,OAAc,SAAS,0BAA0B,UAA4B,MAAM;AAC3F,kBAAM,OAAO,KAAK,UAAU,CAAC,MAAM,MAAM,MAAM,SAAS,QAAQ,OAAO,CAAC;AACxE,oBAAQ,KAAM;AAAA,MAAc;AAAA,EAAS,MAAM,OAAO;AAAA,UACnD;AAAA,QACD;AACA,gBAAQ,GAAG,qBAAqB,SAAO;AACtC,kBAAQ,SAAS,KAAK,2BAA2B;AAAA,QAClD,CAAC;AACD,gBAAQ,GAAG,sBAAsB,SAAO;AACvC,kBAAQ,SAAS,KAAc,2BAA2B;AAAA,QAC3D,CAAC;AAAA,MACF;AAAA,IACD;AAEA,SAAK,eAAe,KAAK,cAAc,EAAE,KAAK,YAAU;AACvD,WAAK,gBAAgB;AACrB,WAAK,eAAe;AAAA,IACrB,CAAC;AAAA,EACF;AAAA,EAEA,MAAM,gBAAgB;AACrB,QAAI,CAAC,4BAAO;AAAW,aAAO;AAC9B,UAAM,KAAK,SAAS,KAAK,2BAA2B;AACpD,UAAM,KAAK,SAAS,KAAK,oCAAoC;AAG7D,UAAM,WAAW,MAAM,KAAK,SAAS,IAAI,mDAAmD;AAC5F,QAAI,CAAC,UAAU;AACd,YAAM,KAAK,SAAS,QAAQ,kBAAkB;AAAA,IAC/C;AAEA,UAAM,EAAC,UAAS,IAAI,MAAM,KAAK,SAAS;AAAA,MACvC;AAAA,IACD;AAEA,QAAI,cAAc,GAAG;AAEpB,YAAM,eAAgB,aAAa,UAAU,QAAQ,OAAQ,QAAQ,OAAO,QAAQ;AACpF,mBAAa,4EAA4E;AACzF,YAAM,KAAK,SAAS,QAAQ,wBAAwB;AACpD,mBAAa,qCAAqC;AAAA,IACnD;AAEA,SAAK,uBAAuB,MAAM,KAAK,SAAS;AAAA,MAC/C;AAAA,IAED;AACA,SAAK,qBAAqB,MAAM,KAAK,SAAS,QAAQ,oDAAoD;AAC1G,SAAK,cAAc,MAAM,KAAK,SAAS,QAAQ,+CAA+C;AAC9F,SAAK,+BAA+B,MAAM,KAAK,SAAS;AAAA,MACvD,+MAGkB,iBAAM,eAAe,oBAAoB,CAAC,CAAC;AAAA,IAC9D;AACA,UAAM,KAAK,SAAS,KAAK,aAAa;AACtC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,QAA8B;AACzC,WAAO,OAAO,SAAS,GAAG,IAAI,GAAG,KAAK,OAAO,MAAM,GAAG,EAAE,CAAC,CAAC,YAAkB;AAAA,EAC7E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,WAAW,QAAkB;AAC5B;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,MAAM,QAAgB,OAA2B,YAAqB;AAC3E,QAAI,CAAC,4BAAO,aAAa,CAAC,4BAAO;AAAiB;AAClD,UAAM,SAAS,MAAM,UAAU;AAC/B,UAAM,kBAA+B;AAAA,MACpC,QAAQ,MAAM;AAAA,MACd;AAAA,MACA,cAAc,cAAc,MAAM,gBAAgB;AAAA,MAClD,QAAQ,MAAM,UAAU;AAAA,MACxB,iBAAiB,MAAM,mBAAmB;AAAA,MAC1C,MAAM,MAAM,OAAO,CAAC,GAAG,IAAI,IAAI,MAAM,IAAI,CAAC,IAAI,CAAC;AAAA,MAC/C,IAAI,MAAM,MAAM;AAAA,MAChB,UAAU,MAAM,YAAY,WAAW,YAAY;AAAA,MACnD,UAAU,MAAM,YAAY;AAAA,MAC5B,MAAM,MAAM,QAAQ;AAAA,MACpB,MAAM,MAAM,QAAQ,KAAK,IAAI;AAAA,IAC9B;AAEA,UAAM,KAAK,SAAS,CAAC,eAAe,CAAC;AAAA,EACtC;AAAA,EAEA,MAAM,SAAS,SAAgC;AAC9C,QAAI,CAAC,4BAAO;AAAW;AACvB,QAAI,CAAC,KAAK,eAAe;AACxB,WAAK,cAAc,KAAK,GAAG,OAAO;AAClC;AAAA,IACD;AACA,UAAM,WAAiC;AAAA,MACtC;AAAA,MACA,0BAA0B,KAAK,qBAAsB,SAAS;AAAA,MAC9D,wBAAwB,KAAK,mBAAoB,SAAS;AAAA,IAC3D;AACA,UAAM,KAAK,SAAS,YAAY,aAAa,QAAQ;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAQ,QAAkB;AAC/B,WAAO,QAAQ,QAAQ,MAAS;AAAA,EACjC;AAAA,EAEA,mBAAmB;AAClB,QAAI,CAAC,KAAK;AAAU;AACpB,SAAK,KAAK,SAAS,QAAQ;AAC3B,SAAK,gBAAgB;AAAA,EACtB;AAAA,EAEA,aAAa;AACZ,SAAK,iBAAiB;AAAA,EACvB;AAAA,EAEA,MAAM,OAAO,OAAiB,OAAiB;AAC9C,QAAI,CAAC,4BAAO;AAAW;AACvB,QAAI,UAAU;AAAO;AAGrB,QAAI,KAAK;AAAc,YAAM,KAAK;AAClC,QAAI,KAAK,eAAe;AACvB,YAAM,KAAK,SAAS,IAAI,KAAK,aAAc,CAAC,OAAO,KAAK,CAAC;AAAA,IAC1D,OAAO;AAGN,YAAM,IAAI,MAAM,wEAAwE;AAAA,IACzF;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,qBAAqB,MAAqB,OAAO,IAAI;AAC1D,QAAI,CAAC,4BAAO,aAAa,CAAC,4BAAO;AAAiB,aAAO;AACzD,WAAO,KAAK,wBAAwB,KAAK,IAAI,GAAG,IAAI;AAAA,EACrD;AAAA,EAEA,MAAM,wBAAwB,QAAY,MAAc;AACvD,QAAI,KAAK;AAAc,YAAM,KAAK;AAElC,QAAI,CAAC,KAAK,8BAA8B;AACvC,YAAM,IAAI,MAAM,qFAAqF;AAAA,IACtG;AACA,UAAM,OAA4B;AAAA,MACjC;AAAA,MAAQ;AAAA,MAAQ;AAAA,MAAQ,KAAK,IAAI,IAAK,OAAO,KAAK,KAAK,KAAK;AAAA,MAAO,GAAG;AAAA,IACvE;AACA,UAAM,UAAU,MAAM,KAAK,SAAS,IAAI,KAAK,8BAA8B,IAAI;AAC/E,WAAO,QAAQ;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,OACL,SAAmB,UACnB,SAAuB,EAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,QAAQ,CAAC,GAAG,aAAa,CAAC,EAAC,GAC/E,WAAW,IACX,kBAAkB,OACc;AAChC,QAAI,CAAC,4BAAO,aAAa,CAAC,4BAAO;AAAiB,aAAO;AACzD,UAAM,YAAY,KAAK,IAAI;AAE3B,QAAI;AACJ,QAAI,WAAW,UAAU;AACxB,cAAQ,CAAC,GAAG,MAAM,MAAM,OAAO,CAAC,EAC9B,OAAO,UAAQ,CAAC,KAAK,SAAS,aAAa,CAAC,KAAK,SAAS,UAAU,EACpE,IAAI,UAAQ,KAAK,MAAM;AAAA,IAC1B,WAAW,WAAW,OAAO;AAC5B,cAAQ;AAAA,IACT,OAAO;AACN,cAAQ,CAAC,MAAM;AAAA,IAChB;AAEA,QAAI,KAAK;AAAc,YAAM,KAAK;AAClC,QAAI,CAAC,KAAK;AAAe,aAAO;AAChC,UAAM,QAAQ,KAAK,iBAAiB,OAAO,UAAU,iBAAiB,MAAM;AAC5E,UAAM,WAAW,MAAM,KAAK,SAAS,IAAI,MAAM,WAAW,MAAM,IAAI,GAClE,IAAI,CAAC,QAAa,KAAK,mBAAmB,GAAG,CAAC;AAEhD,UAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,QAAI,WAAW,qBAAqB;AACnC,cAAQ,KAAK,4BAA4B,gBAAgB,KAAK,UAAU,KAAK,GAAG;AAAA,IACjF;AACA,WAAO,EAAC,SAAS,SAAQ;AAAA,EAC1B;AAAA,EAEA,mBAAmB,KAA2C;AAC7D,WAAO;AAAA,MACN,SAAS,IAAI;AAAA,MACb,QAAQ,IAAI;AAAA,MACZ,QAAQ,IAAI;AAAA,MACZ,cAAc,IAAI;AAAA,MAClB,QAAQ,IAAI;AAAA,MACZ,iBAAiB,IAAI;AAAA,MACrB,MAAM,IAAI,MAAM,MAAM,GAAG,KAAK,CAAC;AAAA,MAC/B,IAAI,IAAI,MAAM;AAAA,MACd,UAAU,QAAQ,IAAI,SAAS;AAAA,MAC/B,UAAU,IAAI;AAAA,MACd,MAAM,IAAI;AAAA,MACV,MAAM,IAAI;AAAA,IACX;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,4BACC,QACA,KACA,MACA,cACkC;AAClC,QAAI,CAAC,KAAK;AAAU,YAAM,IAAI,MAAM,yEAAyE;AAE7G,QAAI,WAAW;AACf,UAAM,UAAU,CAAC;AACjB,eAAW,OAAO,MAAM;AACvB,UAAI,SAAS;AAAQ,oBAAY;AACjC,kBAAY,IAAI;AAChB,cAAQ,KAAK,GAAG,IAAI,IAAI;AAAA,IACzB;AAGA,QAAI,QAAQ;AACZ,UAAM,OAAO,CAAC;AACd,QAAI,CAAC,IAAI,QAAQ;AAChB,cAAQ,GAAG,UAAU,WAAW,UAAU,aAAa;AACvD,WAAK,KAAK,GAAG,OAAO;AAAA,IACrB,OAAO;AACN,iBAAW,MAAM,KAAK;AACrB,YAAI,MAAM;AAAQ,mBAAS;AAC3B,iBAAS,kBAAkB,gBAAgB,GAAG,SAAS,WAAW,QAAQ,aAAa,MAAM,aAAa;AAC1G,aAAK,KAAK,GAAG,GAAG,MAAM,GAAG,SAAS,GAAG,aAAa,IAAI;AAAA,MACvD;AAAA,IACD;AACA,aAAS,IAAI,aAAa;AAC1B,SAAK,KAAK,GAAG,aAAa,IAAI;AAE9B,WAAO;AAAA,MACN,WAAW;AAAA,MACX;AAAA,IACD;AAAA,EACD;AAAA,EAEA,iBACC,OACA,UACA,iBACA,QACkC;AAClC,UAAM,SAAS;AACf,UAAM,MAAM,CAAC;AACb,UAAM,OAAO,CAAC;AACd,UAAM,eAAe,EAAC,OAAO,2BAA2B,MAAM,CAAC,EAAC;AAChE,QAAI,UAAU;AACb,mBAAa,SAAS;AACtB,mBAAa,KAAK,KAAK,QAAQ;AAAA,IAChC;AAIA,QAAI,UAAU,OAAO;AACpB,YAAM,OAA4B,CAAC;AACnC,UAAI,cAAc,cAAc,iBAAM,eAAe,OAAO,IAAI;AAChE,UAAI,MAAM,SAAS,QAAQ,GAAG;AAC7B,YAAI,MAAM,SAAS,GAAG;AACrB,wBAAc,qBAAqB;AAAA,QACpC,OAAO;AACN,wBAAc;AAEd,eAAK,IAAI;AAAA,QACV;AAAA,MACD;AACA,WAAK,KAAK,EAAC,OAAO,aAAa,KAAI,CAAC;AAAA,IACrC;AAEA,eAAW,UAAU,OAAO,QAAQ;AACnC,YAAM,OAAO,CAAC,OAAO,SAAS,GAAG;AACjC,UAAI,OAAO,aAAa;AACvB,aAAK,KAAK,EAAC,OAAO,qBAAqB,KAAI,CAAC;AAAA,MAC7C,OAAO;AACN,aAAK,KAAK,EAAC,OAAO,iBAAiB,KAAI,CAAC;AAAA,MACzC;AAAA,IACD;AACA,QAAI,iBAAiB;AACpB,YAAM,OAA4B,CAAC;AACnC,WAAK,KAAK,EAAC,OAAO,cAAc,iBAAM,eAAe,aAAa,IAAI,MAAM,KAAI,CAAC;AAAA,IAClF;AAEA,eAAW,MAAM,OAAO,IAAI;AAC3B,YAAM,OAAO,CAAC,GAAG,SAAS,GAAG;AAC7B,UAAI,GAAG,aAAa;AACnB,aAAK,KAAK,EAAC,OAAO,iBAAiB,KAAI,CAAC;AAAA,MACzC,OAAO;AACN,aAAK,KAAK,EAAC,OAAO,aAAa,KAAI,CAAC;AAAA,MACrC;AAAA,IACD;AACA,eAAW,eAAe,OAAO,aAAa;AAC7C,YAAM,OAAO,CAAC,YAAY,SAAS,GAAG;AACtC,UAAI,YAAY,aAAa;AAC5B,aAAK,KAAK,EAAC,OAAO,kCAAkC,KAAI,CAAC;AAAA,MAC1D,OAAO;AACN,aAAK,KAAK,EAAC,OAAO,8BAA8B,KAAI,CAAC;AAAA,MACtD;AAAA,IACD;AAEA,eAAW,cAAc,OAAO,MAAM;AACrC,YAAM,SAAS,WAAW,UAAU,QAAQ;AAC5C,YAAM,OAAO,CAAC,WAAW,UAAU,WAAW,SAAS,IAAI,WAAW,SAAS;AAC/E,UAAI,WAAW,aAAa;AAC3B,aAAK,KAAK,EAAC,OAAO,QAAQ,WAAW,UAAU,MAAM,SAAS,UAAU,KAAI,CAAC;AAAA,MAC9E,OAAO;AACN,aAAK,KAAK,EAAC,OAAO,QAAQ,UAAU,KAAI,CAAC;AAAA,MAC1C;AAAA,IACD;AAEA,eAAW,QAAQ,OAAO,MAAM;AAC/B,UAAI;AACJ,UAAI;AACJ,UAAI,KAAK,SAAS;AACjB,iBAAS,KAAK,cAAc,SAAS;AACrC,gBAAQ,KAAK,OAAO,YAAY;AAAA,MACjC,OAAO;AACN,iBAAS,KAAK,cAAc,eAAe;AAC3C,gBAAQ,KAAK,OAAO,YAAY,IAAI;AAAA,MACrC;AAEA,UAAI,KAAK,EAAC,OAAO,WAAW,kCAAkC,WAAW,MAAM,CAAC,OAAO,KAAK,EAAC,CAAC;AAC9F,UAAI,KAAK;AAAA,QACR,OAAO,qFAAqF;AAAA,QAC5F,MAAM,CAAC,KAAK;AAAA,MACb,CAAC;AAAA,IACF;AACA,WAAO,KAAK,4BAA4B,QAAQ,KAAK,MAAM,YAAY;AAAA,EACxE;AACD;AAEO,MAAM,aAAa,IAAI,OAAO,gBAAgB,EAAC,eAAe,4BAAO,oBAAmB,CAAC;", "names": [] }