{ "version": 3, "sources": ["../../../../server/chat-plugins/responder.ts"], "sourcesContent": ["/**\r\n * PS Help room auto-response plugin.\r\n * Uses Regex to match room frequently asked question (RFAQ) entries,\r\n * and replies if a match is found.\r\n * Supports configuration, and works in all rooms, though intended mainly for Help.\r\n * Written by Mia.\r\n * @author mia-pi-git\r\n */\r\n\r\nimport {FS, Utils} from '../../lib';\r\nimport {LogViewer} from './chatlog';\r\nimport {roomFaqs, visualizeFaq} from './room-faqs';\r\n\r\nconst DATA_PATH = 'config/chat-plugins/responder.json';\r\nconst LOG_PATH = 'logs/responder.jsonl';\r\n\r\nexport let answererData: {[roomid: string]: PluginData} = {};\r\n\r\ntry {\r\n\tanswererData = JSON.parse(FS(DATA_PATH).readSync());\r\n} catch {}\r\n\r\n/**\r\n * A message caught by the filter.\r\n */\r\ninterface LoggedMessage {\r\n\t/** Message that's matched by the filter. */\r\n\tmessage: string;\r\n\t/** The FAQ that it's matched to. */\r\n\tfaqName: string;\r\n\t/** The regex that it's matched to. */\r\n\tregex: string;\r\n\tdate: string;\r\n}\r\ninterface PluginData {\r\n\t/** Word pairs that have been marked as a match for a specific FAQ. */\r\n\tpairs: {[k: string]: string[]};\r\n\t/** Common terms to be ignored in question parsing. */\r\n\tignore?: string[];\r\n}\r\n\r\nexport class AutoResponder {\r\n\tdata: PluginData;\r\n\troom: Room;\r\n\tconstructor(room: Room, data?: PluginData) {\r\n\t\tthis.room = room;\r\n\t\tthis.data = data || {pairs: {}, ignore: []};\r\n\t\tAutoResponder.migrateStats(this.data, this);\r\n\t}\r\n\tstatic migrateStats(data: any, responder: AutoResponder) {\r\n\t\tif (!data.stats) return data;\r\n\t\tfor (const date in data.stats) {\r\n\t\t\tfor (const entry of data.stats[date].matches) {\r\n\t\t\t\tvoid this.logMessage(responder.room.roomid, {...entry, date});\r\n\t\t\t}\r\n\t\t}\r\n\t\tdelete data.stats;\r\n\t\tresponder.data = data;\r\n\t\tresponder.writeState();\r\n\t\treturn data;\r\n\t}\r\n\tstatic logStream = FS(LOG_PATH).createAppendStream();\r\n\tstatic logMessage(roomid: RoomID, entry: LoggedMessage) {\r\n\t\treturn this.logStream.writeLine(JSON.stringify({\r\n\t\t\t...entry,\r\n\t\t\troom: roomid,\r\n\t\t\tregex: entry.regex.toString(),\r\n\t\t}));\r\n\t}\r\n\tfind(question: string, user?: User) {\r\n\t\t// sanity slice, APPARENTLY people are dumb.\r\n\t\tquestion = question.slice(0, 300);\r\n\t\tconst room = this.room;\r\n\t\tconst helpFaqs = roomFaqs[room.roomid];\r\n\t\tif (!helpFaqs) return null;\r\n\t\tconst normalized = Chat.normalize(question);\r\n\t\tif (this.data.ignore) {\r\n\t\t\tif (this.data.ignore.some(t => new RegExp(t, \"i\").test(normalized))) {\r\n\t\t\t\treturn null;\r\n\t\t\t}\r\n\t\t}\r\n\t\tconst faqs = Object.keys(helpFaqs).filter(item => !helpFaqs[item].alias);\r\n\t\tfor (const faq of faqs) {\r\n\t\t\tconst match = this.test(normalized, faq);\r\n\t\t\tif (match) {\r\n\t\t\t\tif (user) {\r\n\t\t\t\t\tconst timestamp = Chat.toTimestamp(new Date()).split(' ')[1];\r\n\t\t\t\t\tconst log = `${timestamp} |c| ${user.name}|${question}`;\r\n\t\t\t\t\tthis.log(log, faq, match.regex);\r\n\t\t\t\t}\r\n\t\t\t\treturn helpFaqs[match.faq];\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\tvisualize(question: string, hideButton?: boolean, user?: User) {\r\n\t\tconst response = this.find(question, user);\r\n\t\tif (response) {\r\n\t\t\tlet buf = '';\r\n\t\t\tbuf += Utils.html`You said: ${question}
`;\r\n\t\t\tbuf += `Our automated reply: ${Chat.collapseLineBreaksHTML(visualizeFaq(response))}`;\r\n\t\t\tif (!hideButton) {\r\n\t\t\t\tbuf += Utils.html`
`;\r\n\t\t\t}\r\n\t\t\treturn buf;\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\tgetFaqID(faq?: string) {\r\n\t\tif (!faq) throw new Chat.ErrorMessage(`Your input must be in the format [input] => [faq].`);\r\n\t\tfaq = faq.trim();\r\n\t\tif (!faq) throw new Chat.ErrorMessage(`Your FAQ ID can't be empty.`);\r\n\t\tconst room = this.room;\r\n\t\tconst entry = roomFaqs[room.roomid][faq];\r\n\t\tif (!entry) throw new Chat.ErrorMessage(`FAQ ID \"${faq}\" not found.`);\r\n\r\n\t\tif (!entry.alias) return faq; // not an alias\r\n\t\treturn entry.source;\r\n\t}\r\n\tasync getStatsFor(date: string) {\r\n\t\tconst stream = FS(LOG_PATH).createReadStream();\r\n\t\tconst buf: LoggedMessage[] = [];\r\n\t\tfor await (const raw of stream.byLine()) {\r\n\t\t\ttry {\r\n\t\t\t\tconst data = JSON.parse(raw);\r\n\t\t\t\tif (data.date !== date || data.room !== this.room.roomid) continue;\r\n\t\t\t\tbuf.push(data);\r\n\t\t\t} catch {}\r\n\t\t}\r\n\t\treturn buf;\r\n\t}\r\n\r\n\tasync listDays() {\r\n\t\tconst stream = FS(LOG_PATH).createReadStream();\r\n\t\tconst buf = new Utils.Multiset();\r\n\t\tfor await (const raw of stream.byLine()) {\r\n\t\t\ttry {\r\n\t\t\t\tconst data = JSON.parse(raw);\r\n\t\t\t\tif (!data.date || data.room !== this.room.roomid) continue;\r\n\t\t\t\tbuf.add(data.date);\r\n\t\t\t} catch {}\r\n\t\t}\r\n\t\treturn buf;\r\n\t}\r\n\r\n\t/**\r\n\t * Checks if the FAQ exists. If not, deletes all references to it.\r\n\t */\r\n\tupdateFaqData(faq: string) {\r\n\t\t// testing purposes\r\n\t\tif (Config.nofswriting) return true;\r\n\t\tconst room = this.room;\r\n\t\tif (!room) return;\r\n\t\tif (roomFaqs[room.roomid][faq]) return true;\r\n\t\tif (this.data.pairs[faq]) delete this.data.pairs[faq];\r\n\t\treturn false;\r\n\t}\r\n\tstringRegex(str: string, raw?: boolean) {\r\n\t\t[str] = Utils.splitFirst(str, '=>');\r\n\t\tconst args = str.split(',').map(item => item.trim());\r\n\t\tif (!raw && args.length > 10) {\r\n\t\t\tthrow new Chat.ErrorMessage(`Too many arguments.`);\r\n\t\t}\r\n\t\tif (str.length > 300 && !raw) throw new Chat.ErrorMessage(\"Your given string is too long.\");\r\n\t\treturn args.map(item => {\r\n\t\t\tconst split = item.split('&').map(string => {\r\n\t\t\t\t// allow raw regex for admins and users with @ in Dev\r\n\t\t\t\tif (raw) return string;\r\n\t\t\t\t// escape\r\n\t\t\t\treturn string.replace(/[\\\\^$.*+?()[\\]{}]/g, '\\\\$&').trim();\r\n\t\t\t});\r\n\t\t\treturn split.map(term => {\r\n\t\t\t\tif (term.length > 100 && !raw) {\r\n\t\t\t\t\tthrow new Chat.ErrorMessage(`One or more of your arguments is too long. Use less than 100 characters.`);\r\n\t\t\t\t}\r\n\t\t\t\tif (item.startsWith('|') || item.endsWith('|')) {\r\n\t\t\t\t\tthrow new Chat.ErrorMessage(`Invalid use of |. Make sure you have an option on either side.`);\r\n\t\t\t\t}\r\n\t\t\t\tif (term.startsWith('!')) {\r\n\t\t\t\t\treturn `^(?!.*${term.slice(1)})`;\r\n\t\t\t\t}\r\n\t\t\t\tif (!term.trim()) return null;\r\n\t\t\t\treturn `(?=.*?(${term.trim()}))`;\r\n\t\t\t}).filter(Boolean).join('');\r\n\t\t}).filter(Boolean).join('');\r\n\t}\r\n\ttest(question: string, faq: string) {\r\n\t\tif (!this.data.pairs[faq]) this.data.pairs[faq] = [];\r\n\t\tconst regexes = this.data.pairs[faq].map(item => new RegExp(item, \"i\"));\r\n\t\tif (!regexes.length) return;\r\n\t\tfor (const regex of regexes) {\r\n\t\t\tif (regex.test(question)) return {faq, regex: regex.toString()};\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\tlog(entry: string, faq: string, expression: string) {\r\n\t\tconst [day] = Utils.splitFirst(Chat.toTimestamp(new Date), ' ');\r\n\t\tvoid AutoResponder.logMessage(this.room.roomid, {\r\n\t\t\tmessage: entry,\r\n\t\t\tfaqName: faq,\r\n\t\t\tregex: expression,\r\n\t\t\tdate: day,\r\n\t\t});\r\n\t}\r\n\twriteState() {\r\n\t\tfor (const faq in this.data.pairs) {\r\n\t\t\t// while writing, clear old data. In the meantime, the rest of the data is inaccessible\r\n\t\t\t// so this is the best place to clear the data\r\n\t\t\tthis.updateFaqData(faq);\r\n\t\t}\r\n\t\tanswererData[this.room.roomid] = this.data;\r\n\t\treturn FS(DATA_PATH).writeUpdate(() => JSON.stringify(answererData));\r\n\t}\r\n\ttryAddRegex(inputString: string, raw?: boolean) {\r\n\t\tlet [args, faq] = inputString.split('=>').map(item => item.trim()) as [string, string | undefined];\r\n\t\tfaq = this.getFaqID(toID(faq));\r\n\t\tif (!this.data.pairs) this.data.pairs = {};\r\n\t\tif (!this.data.pairs[faq]) this.data.pairs[faq] = [];\r\n\t\tconst regex = raw ? args.trim() : this.stringRegex(args, raw);\r\n\t\tif (this.data.pairs[faq].includes(regex)) {\r\n\t\t\tthrow new Chat.ErrorMessage(`That regex is already stored.`);\r\n\t\t}\r\n\t\tChat.validateRegex(regex);\r\n\t\tthis.data.pairs[faq].push(regex);\r\n\t\treturn this.writeState();\r\n\t}\r\n\ttryRemoveRegex(faq: string, index: number) {\r\n\t\tfaq = this.getFaqID(faq);\r\n\t\tif (!this.data.pairs) this.data.pairs = {};\r\n\t\tif (!this.data.pairs[faq]) throw new Chat.ErrorMessage(`There are no regexes for ${faq}.`);\r\n\t\tif (!this.data.pairs[faq][index]) throw new Chat.ErrorMessage(\"Your provided index is invalid.\");\r\n\t\tthis.data.pairs[faq].splice(index, 1);\r\n\t\tthis.writeState();\r\n\t\treturn true;\r\n\t}\r\n\tstatic canOverride(user: User, room: Room) {\r\n\t\tconst devAuth = Rooms.get('development')?.auth;\r\n\t\treturn (devAuth?.atLeast(user, '%') && devAuth?.has(user.id) && room.auth.atLeast(user, '@')) || user.can('rangeban');\r\n\t}\r\n\tdestroy() {\r\n\t\tthis.writeState();\r\n\t\tthis.room.responder = null;\r\n\t\t// @ts-ignore deallocating\r\n\t\tthis.room = null;\r\n\t}\r\n\tignore(terms: string[], context: Chat.CommandContext) {\r\n\t\tconst filtered = terms.map(t => context.filter(t)).filter(Boolean);\r\n\t\tif (filtered.length !== terms.length) {\r\n\t\t\tthrow new Chat.ErrorMessage(`Invalid terms.`);\r\n\t\t}\r\n\t\tif (terms.some(t => t.length > 300)) {\r\n\t\t\tthrow new Chat.ErrorMessage(`One of your terms is too long.`);\r\n\t\t}\r\n\t\tif (!this.data.ignore) this.data.ignore = [];\r\n\t\tthis.data.ignore.push(...terms);\r\n\t\tthis.writeState();\r\n\t\treturn terms;\r\n\t}\r\n\tunignore(terms: string[]) {\r\n\t\tif (!this.data.ignore) {\r\n\t\t\tthrow new Chat.ErrorMessage(`The autoresponse filter in this room has no ignored terms.`);\r\n\t\t}\r\n\t\tthis.data.ignore = this.data.ignore.filter(item => !terms.includes(item));\r\n\t\tthis.writeState();\r\n\t\treturn true;\r\n\t}\r\n}\r\n\r\n// update all responders\r\nfor (const room of Rooms.rooms.values()) {\r\n\troom.responder?.destroy();\r\n\tif (answererData[room.roomid]) {\r\n\t\troom.responder = new AutoResponder(room, answererData[room.roomid]);\r\n\t}\r\n}\r\n\r\nconst BYPASS_TERMS = ['a:', 'A:', '!', '/'];\r\n\r\nexport const chatfilter: Chat.ChatFilter = function (message, user, room) {\r\n\tif (BYPASS_TERMS.some(t => message.startsWith(t))) {\r\n\t\t// do not return `message` or it will bypass all filters\r\n\t\t// including super important filters like against `/html`\r\n\t\treturn;\r\n\t}\r\n\tif (room?.responder && room.auth.get(user.id) === ' ') {\r\n\t\tconst responder = room.responder;\r\n\t\tconst reply = responder.visualize(message, false, user);\r\n\t\tif (!reply) {\r\n\t\t\treturn message;\r\n\t\t} else {\r\n\t\t\tthis.sendReply(`|uhtml|askhelp-${user}-${toID(message)}|
${reply}
`);\r\n\t\t\tconst trimmedMessage = `
${responder.visualize(message, true)}
`;\r\n\t\t\tsetTimeout(() => {\r\n\t\t\t\tthis.sendReply(`|uhtmlchange|askhelp-${user}-${toID(message)}|${trimmedMessage}`);\r\n\t\t\t}, 10 * 1000);\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}\r\n};\r\n\r\nexport const commands: Chat.ChatCommands = {\r\n\tquestion(target, room, user) {\r\n\t\troom = this.requireRoom();\r\n\t\tconst responder = room.responder;\r\n\t\tif (!responder) return this.errorReply(`This room does not have an autoresponder configured.`);\r\n\t\tif (!target) return this.parse(\"/help question\");\r\n\t\tconst reply = responder.visualize(target, true);\r\n\t\tif (!reply) return this.sendReplyBox(`No answer found.`);\r\n\t\tthis.runBroadcast();\r\n\t\tthis.sendReplyBox(reply);\r\n\t},\r\n\tquestionhelp: [\"/question [question] - Asks the current room's auto-response filter a question.\"],\r\n\r\n\tar: 'autoresponder',\r\n\tautoresponder: {\r\n\t\t''(target, room) {\r\n\t\t\troom = this.requireRoom();\r\n\t\t\tconst responder = room.responder;\r\n\t\t\tif (!responder) {\r\n\t\t\t\treturn this.errorReply(`This room has not configured an autoresponder.`);\r\n\t\t\t}\r\n\t\t\tif (!target) {\r\n\t\t\t\treturn this.parse('/help autoresponder');\r\n\t\t\t}\r\n\t\t\treturn this.parse(`/j view-autoresponder-${room.roomid}-${target}`);\r\n\t\t},\r\n\t\tview(target, room, user) {\r\n\t\t\troom = this.requireRoom();\r\n\t\t\treturn this.parse(`/join view-autoresponder-${room.roomid}-${target}`);\r\n\t\t},\r\n\t\ttoggle(target, room, user) {\r\n\t\t\troom = this.requireRoom();\r\n\t\t\tif (!target) {\r\n\t\t\t\treturn this.sendReply(\r\n\t\t\t\t\t`The Help auto-response filter is currently set to: ${room.responder ? 'ON' : \"OFF\"}`\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t\tthis.checkCan('ban', null, room);\r\n\t\t\tif (room.settings.isPrivate === true) {\r\n\t\t\t\treturn this.errorReply(`Secret rooms cannot enable an autoresponder.`);\r\n\t\t\t}\r\n\t\t\tif (this.meansYes(target)) {\r\n\t\t\t\tif (room.responder) return this.errorReply(`The Autoresponder for this room is already enabled.`);\r\n\t\t\t\troom.responder = new AutoResponder(room, answererData[room.roomid]);\r\n\t\t\t\troom.responder.writeState();\r\n\t\t\t}\r\n\t\t\tif (this.meansNo(target)) {\r\n\t\t\t\tif (!room.responder) return this.errorReply(`The Autoresponder for this room is already disabled.`);\r\n\t\t\t\troom.responder.destroy();\r\n\t\t\t}\r\n\t\t\tthis.privateModAction(`${user.name} ${!room.responder ? 'disabled' : 'enabled'} the auto-response filter.`);\r\n\t\t\tthis.modlog(`AUTOFILTER`, null, !room.responder ? 'OFF' : 'ON');\r\n\t\t},\r\n\t\tforceadd: 'add',\r\n\t\tadd(target, room, user, connection, cmd) {\r\n\t\t\troom = this.requireRoom();\r\n\t\t\tif (!room.responder) {\r\n\t\t\t\treturn this.errorReply(`This room has not configured an auto-response filter.`);\r\n\t\t\t}\r\n\t\t\tconst force = cmd === 'forceadd';\r\n\t\t\tif (force && !AutoResponder.canOverride(user, room)) {\r\n\t\t\t\treturn this.errorReply(`You cannot use raw regex - use /autoresponder add instead.`);\r\n\t\t\t}\r\n\t\t\tthis.checkCan('ban', null, room);\r\n\t\t\troom.responder.tryAddRegex(target, force);\r\n\t\t\tthis.privateModAction(`${user.name} added regex for \"${target.split('=>')[0]}\" to the autoresponder.`);\r\n\t\t\tthis.modlog(`AUTOFILTER ADD`, null, target);\r\n\t\t},\r\n\t\tremove(target, room, user) {\r\n\t\t\tconst [faq, index] = target.split(',');\r\n\t\t\troom = this.requireRoom();\r\n\t\t\tif (!room.responder) {\r\n\t\t\t\treturn this.errorReply(`${room.title} has not configured an auto-response filter.`);\r\n\t\t\t}\r\n\t\t\tthis.checkCan('ban', null, room);\r\n\t\t\tconst num = parseInt(index);\r\n\t\t\tif (isNaN(num)) return this.errorReply(\"Invalid index.\");\r\n\t\t\troom.responder.tryRemoveRegex(faq, num - 1);\r\n\t\t\tthis.privateModAction(`${user.name} removed regex ${num} from the usable regexes for ${faq}.`);\r\n\t\t\tthis.modlog('AUTOFILTER REMOVE', null, index);\r\n\t\t\tconst pages = [`keys`, `pairs`];\r\n\t\t\tfor (const p of pages) {\r\n\t\t\t\tthis.refreshPage(`autofilter-${room.roomid}-${p}`);\r\n\t\t\t}\r\n\t\t},\r\n\t\tignore(target, room, user) {\r\n\t\t\troom = this.requireRoom();\r\n\t\t\tif (!room.responder) {\r\n\t\t\t\treturn this.errorReply(`This room has not configured an auto-response filter.`);\r\n\t\t\t}\r\n\t\t\tthis.checkCan('ban', null, room);\r\n\t\t\tif (!toID(target)) {\r\n\t\t\t\treturn this.parse(`/help autoresponder`);\r\n\t\t\t}\r\n\t\t\tconst targets = target.split(',');\r\n\t\t\troom.responder.ignore(targets, this);\r\n\t\t\tthis.privateModAction(\r\n\t\t\t\t`${user.name} added ${Chat.count(targets.length, \"terms\")} to the autoresponder ignore list.`\r\n\t\t\t);\r\n\t\t\tthis.modlog(`AUTOFILTER IGNORE`, null, target);\r\n\t\t},\r\n\t\tunignore(target, room, user) {\r\n\t\t\troom = this.requireRoom();\r\n\t\t\tif (!room.responder) {\r\n\t\t\t\treturn this.errorReply(`${room.title} has not configured an auto-response filter.`);\r\n\t\t\t}\r\n\t\t\tthis.checkCan('ban', null, room);\r\n\t\t\tif (!toID(target)) {\r\n\t\t\t\treturn this.parse(`/help autoresponder`);\r\n\t\t\t}\r\n\t\t\tconst targets = target.split(',');\r\n\t\t\troom.responder.unignore(targets);\r\n\t\t\tthis.privateModAction(`${user.name} removed ${Chat.count(targets.length, \"terms\")} from the autoresponder ignore list.`);\r\n\t\t\tthis.modlog(`AUTOFILTER UNIGNORE`, null, target);\r\n\t\t\tif (this.connection.openPages?.has(`autoresponder-${room.roomid}-ignore`)) {\r\n\t\t\t\treturn this.parse(`/join view-autoresponder-${room.roomid}-ignore`);\r\n\t\t\t}\r\n\t\t},\r\n\t},\r\n\tautoresponderhelp() {\r\n\t\tconst help = [\r\n\t\t\t`/autoresponder view [page] - Views the Autoresponder page [page]. (options: keys, stats)`,\r\n\t\t\t`/autoresponder toggle [on | off] - Enables or disables the Autoresponder for the current room. Requires: @ # &`,\r\n\t\t\t`/autoresponder add [input] => [faq] - Adds regex made from the input string to the current room's Autoresponder, to respond with [faq] to matches.`,\r\n\t\t\t`/autoresponder remove [faq], [regex index] - removes the regex matching the [index] from the current room's responses for [faq].`,\r\n\t\t\t`Indexes can be found in /autoresponder keys.`,\r\n\t\t\t`Requires: @ # &`,\r\n\t\t];\r\n\t\treturn this.sendReplyBox(help.join('
'));\r\n\t},\r\n};\r\n\r\nexport const pages: Chat.PageTable = {\r\n\tasync autoresponder(args, user) {\r\n\t\tconst room = this.requireRoom();\r\n\t\tif (!room.responder) {\r\n\t\t\treturn this.errorReply(`${room.title} does not have a configured autoresponder.`);\r\n\t\t}\r\n\t\targs.shift();\r\n\t\tconst roomData = answererData[room.roomid];\r\n\t\tconst canChange = user.can('ban', null, room);\r\n\t\tlet buf = '';\r\n\t\tconst refresh = (type: string, extra?: string[]) => {\r\n\t\t\tif (extra) extra = extra.filter(Boolean);\r\n\t\t\tlet button = `
`;\r\n\t\t\treturn button;\r\n\t\t};\r\n\t\tconst back = `
Back to all`;\r\n\t\tswitch (args[0]) {\r\n\t\tcase 'stats':\r\n\t\t\targs.shift();\r\n\t\t\tthis.checkCan('mute', null, room);\r\n\t\t\tconst date = args.join('-') || '';\r\n\t\t\tif (!!date && isNaN(new Date(date).getTime())) {\r\n\t\t\t\treturn `

Invalid date.

`;\r\n\t\t\t}\r\n\t\t\tbuf = `
Stats for the ${room.title} auto-response filter${date ? ` on ${date}` : ''}.`;\r\n\t\t\tbuf += `${back}${refresh('stats', [date])}
`;\r\n\t\t\tif (date) {\r\n\t\t\t\tconst stats = await room.responder.getStatsFor(date);\r\n\t\t\t\tif (!stats) return `

No stats.

`;\r\n\t\t\t\tthis.title = `[Autoresponder Stats] ${date ? date : ''}`;\r\n\t\t\t\tif (!stats.length) return `

No stats for ${date}.

`;\r\n\t\t\t\tbuf += `Total messages answered: ${stats.length}
`;\r\n\t\t\t\tbuf += `
All messages and the corresponding answers (FAQs):`;\r\n\t\t\t\tfor (const entry of stats) {\r\n\t\t\t\t\tbuf += `Message:${LogViewer.renderLine(entry.message)}`;\r\n\t\t\t\t\tbuf += `FAQ: ${entry.faqName}
`;\r\n\t\t\t\t\tbuf += `Regex: ${entry.regex}
`;\r\n\t\t\t\t}\r\n\t\t\t\treturn LogViewer.linkify(buf);\r\n\t\t\t}\r\n\t\t\tbuf += ` No date specified.
`;\r\n\t\t\tconst days: string[] = [];\r\n\t\t\tlet totalCount = 0;\r\n\t\t\tconst dayKeys = await room.responder.listDays();\r\n\t\t\tfor (const [dateKey, total] of dayKeys) {\r\n\t\t\t\ttotalCount += total;\r\n\t\t\t\tdays.push(`- ${dateKey} (${total})`);\r\n\t\t\t}\r\n\t\t\tbuf += `Dates with stats:
(total matches: ${totalCount})

`;\r\n\t\t\tbuf += days.join('
');\r\n\t\t\tbreak;\r\n\t\tcase 'pairs':\r\n\t\tcase 'keys':\r\n\t\t\tthis.title = '[Autoresponder Regexes]';\r\n\t\t\tthis.checkCan('show', null, room);\r\n\t\t\tbuf = `

${room.title} responder regexes and responses:

${back}${refresh('keys')}
`;\r\n\t\t\tbuf += Object.entries(roomData.pairs).map(([item, regexes]) => {\r\n\t\t\t\tif (regexes.length < 1) return null;\r\n\t\t\t\tlet buffer = `
${item}`;\r\n\t\t\t\tbuffer += `
`;\r\n\t\t\t\tif (canChange) buffer += ``;\r\n\t\t\t\tbuffer += ``;\r\n\t\t\t\tfor (const regex of regexes) {\r\n\t\t\t\t\tconst index = regexes.indexOf(regex) + 1;\r\n\t\t\t\t\tconst button = ``;\r\n\t\t\t\t\tbuffer += ``;\r\n\t\t\t\t\tif (canChange) buffer += ``;\r\n\t\t\t\t}\r\n\t\t\t\tbuffer += ``;\r\n\t\t\t\treturn buffer;\r\n\t\t\t}).filter(Boolean).join('
');\r\n\t\t\tbreak;\r\n\t\tcase 'ignore':\r\n\t\t\tthis.title = `[${room.title} Autoresponder ignore list]`;\r\n\t\t\tbuf = `

${room.title} responder terms to ignore:

${back}${refresh('ignore')}
`;\r\n\t\t\tif (!roomData.ignore) {\r\n\t\t\t\treturn this.errorReply(`No terms on ignore list.`);\r\n\t\t\t}\r\n\t\t\tfor (const term of roomData.ignore) {\r\n\t\t\t\tbuf += `- ${term}
`;\r\n\t\t\t}\r\n\t\t\tbuf += `
`;\r\n\t\t\tbreak;\r\n\t\tdefault:\r\n\t\t\tthis.title = `[${room.title} Autoresponder]`;\r\n\t\t\tbuf = `

Specify a filter page to view.

`;\r\n\t\t\tbuf += `
Options:
`;\r\n\t\t\tbuf += `Stats
`;\r\n\t\t\tbuf += `Regex keys
`;\r\n\t\t\tbuf += `Ignore list
`;\r\n\t\t\tbuf += `
`;\r\n\t\t}\r\n\t\treturn LogViewer.linkify(buf);\r\n\t},\r\n};\r\n\r\nexport const handlers: Chat.Handlers = {\r\n\tonRenameRoom(oldID, newID) {\r\n\t\tif (answererData[oldID]) {\r\n\t\t\tif (!answererData[newID]) answererData[newID] = {pairs: {}};\r\n\t\t\tObject.assign(answererData[newID], answererData[oldID]);\r\n\t\t\tdelete answererData[oldID];\r\n\t\t\tFS(DATA_PATH).writeUpdate(() => JSON.stringify(answererData));\r\n\t\t}\r\n\t},\r\n};\r\n"], "mappings": "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", "names": ["pages"] }
IndexRegexOptions
${index}${regex}${button}