Spaces:
Running
Running
{ | |
"version": 3, | |
"sources": ["../../server/roomlogs.ts"], | |
"sourcesContent": ["/**\n * Roomlogs\n * Pokemon Showdown - http://pokemonshowdown.com/\n *\n * This handles data storage for rooms.\n *\n * @license MIT\n */\n\nimport { FS, Utils, type Streams } from '../lib';\nimport { PGDatabase, SQL, type SQLStatement } from '../lib/database';\nimport type { PartialModlogEntry } from './modlog';\n\ninterface RoomlogOptions {\n\tisMultichannel?: boolean;\n\tnoAutoTruncate?: boolean;\n\tnoLogTimes?: boolean;\n}\n\ninterface RoomlogRow {\n\ttype: string;\n\troomid: string;\n\tuserid: string | null;\n\ttime: Date;\n\tlog: string;\n\t// tsvector, really don't use\n\tcontent: string | null;\n}\n\nexport const roomlogDB = (() => {\n\tif (!global.Config || !Config.replaysdb || Config.disableroomlogdb) return null;\n\treturn new PGDatabase(Config.replaysdb);\n})();\nexport const roomlogTable = roomlogDB?.getTable<RoomlogRow>('roomlogs');\n\n/**\n * Most rooms have three logs:\n * - scrollback\n * - roomlog\n * - modlog\n * This class keeps track of all three.\n *\n * The scrollback is stored in memory, and is the log you get when you\n * join the room. It does not get moderator messages.\n *\n * The modlog is stored in\n * `logs/modlog/modlog_<ROOMID>.txt`\n * It contains moderator messages, formatted for ease of search.\n * Direct modlog access is handled in server/modlog/; this file is just\n * a wrapper to make other code more readable.\n *\n * The roomlog is stored in\n * `logs/chat/<ROOMID>/<YEAR>-<MONTH>/<YEAR>-<MONTH>-<DAY>.txt`\n * It contains (nearly) everything.\n */\nexport class Roomlog {\n\t/**\n\t * Battle rooms are multichannel, which means their logs are split\n\t * into four channels, public, p1, p2, full.\n\t */\n\treadonly isMultichannel: boolean;\n\t/**\n\t * Chat rooms auto-truncate, which means it only stores the recent\n\t * messages, if there are more.\n\t */\n\treadonly noAutoTruncate: boolean;\n\t/**\n\t * Chat rooms include timestamps.\n\t */\n\treadonly noLogTimes: boolean;\n\troomid: RoomID;\n\t/**\n\t * Scrollback log\n\t */\n\tlog: string[];\n\tvisibleMessageCount = 0;\n\tbroadcastBuffer: string[];\n\t/**\n\t * undefined = uninitialized,\n\t * null = disabled\n\t */\n\troomlogStream?: Streams.WriteStream | null;\n\t/**\n\t * Takes precedence over roomlogStream if it exists.\n\t */\n\troomlogTable: typeof roomlogTable;\n\troomlogFilename: string;\n\n\tnumTruncatedLines: number;\n\tconstructor(room: BasicRoom, options: RoomlogOptions = {}) {\n\t\tthis.roomid = room.roomid;\n\n\t\tthis.isMultichannel = !!options.isMultichannel;\n\t\tthis.noAutoTruncate = !!options.noAutoTruncate;\n\t\tthis.noLogTimes = !!options.noLogTimes;\n\n\t\tthis.log = [];\n\t\tthis.broadcastBuffer = [];\n\n\t\tthis.roomlogStream = undefined;\n\t\tthis.roomlogFilename = '';\n\n\t\tthis.numTruncatedLines = 0;\n\n\t\tthis.setupRoomlogStream();\n\t}\n\tgetScrollback(channel = 0) {\n\t\tlet log = this.log;\n\t\tif (!this.noLogTimes) log = [`|:|${~~(Date.now() / 1000)}`].concat(log);\n\t\tif (!this.isMultichannel) {\n\t\t\treturn log.join('\\n') + '\\n';\n\t\t}\n\t\tlog = [];\n\t\tfor (let i = 0; i < this.log.length; ++i) {\n\t\t\tconst line = this.log[i];\n\t\t\tconst split = /\\|split\\|p(\\d)/g.exec(line);\n\t\t\tif (split) {\n\t\t\t\tconst canSeePrivileged = (channel === Number(split[1]) || channel === -1);\n\t\t\t\tconst ownLine = this.log[i + (canSeePrivileged ? 1 : 2)];\n\t\t\t\tif (ownLine) log.push(ownLine);\n\t\t\t\ti += 2;\n\t\t\t} else {\n\t\t\t\tlog.push(line);\n\t\t\t}\n\t\t}\n\t\treturn log.join('\\n') + '\\n';\n\t}\n\tsetupRoomlogStream() {\n\t\tif (this.roomlogStream === null) return;\n\t\tif (!Config.logchat || this.roomid.startsWith('battle-') || this.roomid.startsWith('game-')) {\n\t\t\tthis.roomlogStream = null;\n\t\t\treturn;\n\t\t}\n\t\tif (roomlogTable) {\n\t\t\tthis.roomlogTable = roomlogTable;\n\t\t\tthis.roomlogStream = null;\n\t\t\treturn;\n\t\t}\n\t\tconst date = new Date();\n\t\tconst dateString = Chat.toTimestamp(date).split(' ')[0];\n\t\tconst monthString = dateString.split('-', 2).join('-');\n\t\tconst basepath = `chat/${this.roomid}/`;\n\t\tconst relpath = `${monthString}/${dateString}.txt`;\n\n\t\tif (relpath === this.roomlogFilename) return;\n\n\t\tMonitor.logPath(basepath + monthString).mkdirpSync();\n\t\tthis.roomlogFilename = relpath;\n\t\tif (this.roomlogStream) void this.roomlogStream.writeEnd();\n\t\tthis.roomlogStream = Monitor.logPath(basepath + relpath).createAppendStream();\n\t\t// Create a symlink to today's lobby log.\n\t\t// These operations need to be synchronous, but it's okay\n\t\t// because this code is only executed once every 24 hours.\n\t\tconst link0 = basepath + 'today.txt.0';\n\t\tMonitor.logPath(link0).unlinkIfExistsSync();\n\t\ttry {\n\t\t\tMonitor.logPath(link0).symlinkToSync(relpath); // intentionally a relative link\n\t\t\tMonitor.logPath(link0).renameSync(basepath + 'today.txt');\n\t\t} catch {} // OS might not support symlinks or atomic rename\n\t\tif (!Roomlogs.rollLogTimer) Roomlogs.rollLogs();\n\t}\n\tadd(message: string) {\n\t\tthis.roomlog(message);\n\t\t// |uhtml gets both uhtml and uhtmlchange\n\t\t// which are visible and so should be counted\n\t\tif (['|c|', '|c:|', '|raw|', '|html|', '|uhtml'].some(k => message.startsWith(k))) {\n\t\t\tthis.visibleMessageCount++;\n\t\t}\n\t\tmessage = this.withTimestamp(message);\n\t\tthis.log.push(message);\n\t\tthis.broadcastBuffer.push(message);\n\t\treturn this;\n\t}\n\tprivate withTimestamp(message: string) {\n\t\tif (!this.noLogTimes && message.startsWith('|c|')) {\n\t\t\treturn `|c:|${Math.trunc(Date.now() / 1000)}|${message.slice(3)}`;\n\t\t} else {\n\t\t\treturn message;\n\t\t}\n\t}\n\thasUsername(username: string) {\n\t\tconst userid = toID(username);\n\t\tfor (const line of this.log) {\n\t\t\tif (line.startsWith('|c:|')) {\n\t\t\t\tconst curUserid = toID(line.split('|', 4)[3]);\n\t\t\t\tif (curUserid === userid) return true;\n\t\t\t} else if (line.startsWith('|c|')) {\n\t\t\t\tconst curUserid = toID(line.split('|', 3)[2]);\n\t\t\t\tif (curUserid === userid) return true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\tclearText(userids: ID[], lineCount = 0) {\n\t\tconst cleared: ID[] = [];\n\t\tconst clearAll = (lineCount === 0);\n\t\tthis.log = this.log.reverse().filter(line => {\n\t\t\tconst parsed = this.parseChatLine(line);\n\t\t\tif (parsed) {\n\t\t\t\tconst userid = toID(parsed.user);\n\t\t\t\tif (userids.includes(userid)) {\n\t\t\t\t\tif (!cleared.includes(userid)) cleared.push(userid);\n\t\t\t\t\t// Don't remove messages in battle rooms to preserve evidence\n\t\t\t\t\tif (!this.roomlogStream && !this.roomlogTable) return true;\n\t\t\t\t\tif (clearAll) return false;\n\t\t\t\t\tif (lineCount > 0) {\n\t\t\t\t\t\tlineCount--;\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t}).reverse();\n\t\treturn cleared;\n\t}\n\tuhtmlchange(name: string, message: string) {\n\t\tconst originalStart = '|uhtml|' + name + '|';\n\t\tconst fullMessage = originalStart + message;\n\t\tfor (const [i, line] of this.log.entries()) {\n\t\t\tif (line.startsWith(originalStart)) {\n\t\t\t\tthis.log[i] = fullMessage;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tthis.broadcastBuffer.push(fullMessage);\n\t}\n\tattributedUhtmlchange(user: User, name: string, message: string) {\n\t\tconst start = `/uhtmlchange ${name},`;\n\t\tconst fullMessage = this.withTimestamp(`|c|${user.getIdentity()}|${start}${message}`);\n\t\tlet matched = false;\n\t\tfor (const [i, line] of this.log.entries()) {\n\t\t\tif (this.parseChatLine(line)?.message.startsWith(start)) {\n\t\t\t\tthis.log[i] = fullMessage;\n\t\t\t\tmatched = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!matched) this.log.push(fullMessage);\n\t\tthis.broadcastBuffer.push(fullMessage);\n\t}\n\tparseChatLine(line: string) {\n\t\tconst prefixes: [string, number][] = [['|c:|', 4], ['|c|', 3]];\n\t\tfor (const [messageStart, section] of prefixes) {\n\t\t\t// const messageStart = !this.noLogTimes ? '|c:|' : '|c|';\n\t\t\t// const section = !this.noLogTimes ? 4 : 3; // ['', 'c' timestamp?, author, message]\n\t\t\tif (line.startsWith(messageStart)) {\n\t\t\t\tconst parts = Utils.splitFirst(line, '|', section);\n\t\t\t\treturn { user: parts[section - 1], message: parts[section] };\n\t\t\t}\n\t\t}\n\t}\n\troomlog(message: string, date = new Date()) {\n\t\tif (!Config.logchat) return;\n\t\tmessage = message.replace(/<img[^>]* src=\"data:image\\/png;base64,[^\">]+\"[^>]*>/g, '[img]');\n\t\tif (this.roomlogTable) {\n\t\t\tconst chatData = this.parseChatLine(message);\n\t\t\tconst type = message.split('|')[1] || \"\";\n\t\t\tvoid this.insertLog(SQL`INSERT INTO roomlogs (${{\n\t\t\t\ttype,\n\t\t\t\troomid: this.roomid,\n\t\t\t\tuserid: toID(chatData?.user) || null,\n\t\t\t\ttime: SQL`now()`,\n\t\t\t\tlog: message,\n\t\t\t}})`);\n\n\t\t\tconst dateStr = Chat.toTimestamp(date).split(' ')[0];\n\t\t\tvoid this.insertLog(SQL`INSERT INTO roomlog_dates (${{\n\t\t\t\troomid: this.roomid,\n\t\t\t\tmonth: dateStr.slice(0, -3),\n\t\t\t\tdate: dateStr,\n\t\t\t}}) ON CONFLICT (roomid, date) DO NOTHING;`);\n\t\t} else if (this.roomlogStream) {\n\t\t\tconst timestamp = Chat.toTimestamp(date).split(' ')[1] + ' ';\n\t\t\tvoid this.roomlogStream.write(timestamp + message + '\\n');\n\t\t}\n\t}\n\tprivate async insertLog(query: SQLStatement, ignoreFailure = false, retries = 3): Promise<void> {\n\t\ttry {\n\t\t\tawait this.roomlogTable?.query(query);\n\t\t} catch (e: any) {\n\t\t\tif (e?.code === '42P01') { // table not found\n\t\t\t\tawait roomlogDB!._query(FS('databases/schemas/roomlogs.sql').readSync(), []);\n\t\t\t\treturn this.insertLog(query, ignoreFailure, retries);\n\t\t\t}\n\t\t\t// connection terminated / transient errors\n\t\t\tif (\n\t\t\t\t!ignoreFailure &&\n\t\t\t\tretries > 0 &&\n\t\t\t\te.message?.includes('Connection terminated unexpectedly')\n\t\t\t) {\n\t\t\t\t// delay before retrying\n\t\t\t\tawait new Promise(resolve => { setTimeout(resolve, 2000); });\n\t\t\t\treturn this.insertLog(query, ignoreFailure, retries - 1);\n\t\t\t}\n\t\t\t// crashlog for all other errors\n\t\t\tconst [q, vals] = roomlogDB!._resolveSQL(query);\n\t\t\tMonitor.crashlog(e, 'a roomlog database query', {\n\t\t\t\tquery: q, values: vals,\n\t\t\t});\n\t\t}\n\t}\n\tmodlog(entry: PartialModlogEntry, overrideID?: string) {\n\t\tvoid Rooms.Modlog.write(this.roomid, entry, overrideID);\n\t}\n\tasync rename(newID: RoomID): Promise<true> {\n\t\tawait Rooms.Modlog.rename(this.roomid, newID);\n\t\tconst roomlogStreamExisted = this.roomlogStream !== null;\n\t\tawait this.destroy();\n\t\tif (this.roomlogTable) {\n\t\t\tawait this.roomlogTable.updateAll({ roomid: newID })`WHERE roomid = ${this.roomid}`;\n\t\t} else {\n\t\t\tconst roomlogPath = `chat`;\n\t\t\tconst [roomlogExists, newRoomlogExists] = await Promise.all([\n\t\t\t\tMonitor.logPath(roomlogPath + `/${this.roomid}`).exists(),\n\t\t\t\tMonitor.logPath(roomlogPath + `/${newID}`).exists(),\n\t\t\t]);\n\t\t\tif (roomlogExists && !newRoomlogExists) {\n\t\t\t\tawait Monitor.logPath(roomlogPath + `/${this.roomid}`).rename(Monitor.logPath(roomlogPath + `/${newID}`).path);\n\t\t\t}\n\t\t\tif (roomlogStreamExisted) {\n\t\t\t\tthis.roomlogStream = undefined;\n\t\t\t\tthis.roomlogFilename = \"\";\n\t\t\t\tthis.setupRoomlogStream();\n\t\t\t}\n\t\t}\n\t\tRoomlogs.roomlogs.set(newID, this);\n\t\tthis.roomid = newID;\n\t\treturn true;\n\t}\n\tstatic rollLogs(this: void) {\n\t\tif (Roomlogs.rollLogTimer === true) return;\n\t\tif (Roomlogs.rollLogTimer) {\n\t\t\tclearTimeout(Roomlogs.rollLogTimer);\n\t\t}\n\t\tRoomlogs.rollLogTimer = true;\n\t\tfor (const log of Roomlogs.roomlogs.values()) {\n\t\t\tlog.setupRoomlogStream();\n\t\t}\n\t\tconst time = Date.now();\n\t\tconst nextMidnight = new Date();\n\t\tnextMidnight.setHours(24, 0, 0, 0);\n\t\tRoomlogs.rollLogTimer = setTimeout(() => Roomlog.rollLogs(), nextMidnight.getTime() - time);\n\t}\n\ttruncate() {\n\t\tif (this.noAutoTruncate) return;\n\t\tif (this.log.length > 100) {\n\t\t\tconst truncationLength = this.log.length - 100;\n\t\t\tthis.log.splice(0, truncationLength);\n\t\t\tthis.numTruncatedLines += truncationLength;\n\t\t}\n\t}\n\t/**\n\t * Returns the total number of lines in the roomlog, including truncated lines.\n\t */\n\tgetLineCount(onlyVisible = true) {\n\t\treturn (onlyVisible ? this.visibleMessageCount : this.log.length) + this.numTruncatedLines;\n\t}\n\n\tdestroy() {\n\t\tconst promises = [];\n\t\tif (this.roomlogStream) {\n\t\t\tpromises.push(this.roomlogStream.writeEnd());\n\t\t\tthis.roomlogStream = null;\n\t\t}\n\t\tRoomlogs.roomlogs.delete(this.roomid);\n\t\treturn Promise.all(promises);\n\t}\n}\n\nconst roomlogs = new Map<string, Roomlog>();\n\nfunction createRoomlog(room: BasicRoom, options = {}) {\n\tlet roomlog = Roomlogs.roomlogs.get(room.roomid);\n\tif (roomlog) throw new Error(`Roomlog ${room.roomid} already exists`);\n\n\troomlog = new Roomlog(room, options);\n\tRoomlogs.roomlogs.set(room.roomid, roomlog);\n\treturn roomlog;\n}\n\nexport const Roomlogs = {\n\tcreate: createRoomlog,\n\tRoomlog,\n\troomlogs,\n\tdb: roomlogDB,\n\ttable: roomlogTable,\n\n\trollLogs: Roomlog.rollLogs,\n\n\trollLogTimer: null as NodeJS.Timeout | true | null,\n};\n"], | |
"mappings": "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", | |
"names": [] | |
} | |