const { literal, argument, greedyString, SimpleCommandExceptionType } = require('brigadier-commands') const TextMessage = require('../util/command/text_message') const fs = require('fs') const path = require('path') const zlib = require('zlib') const stream = require('stream/promises') const LOG_QUERY_ALREADY_RUNNING_ERROR = new SimpleCommandExceptionType(new TextMessage('Another log query is already running!')) module.exports = { running: false, stream: null, create () { return Object.create(this) }, register (dispatcher) { const node = dispatcher.register( literal('logquery') .then( literal('count') .then( argument('string', greedyString()) .executes(c => this.countCommand(c)) ) ) .then( literal('abort') .executes(c => this.abortCommand(c)) ) ) node.description = 'Searches for text in log files' node.permissionLevel = 0 }, cleanup () { this.abort() }, async countCommand (context) { const source = context.source const bot = source.bot const string = context.getArgument('string') source.sendFeedback([{ text: 'Searching for instances of ', ...bot.styles.primary }, { text: string, ...bot.styles.secondary }, ' in logs...'], false) let count = 0 const ran = await this.queryLogs(source, line => line.includes(string) && count++) if (ran) source.sendFeedback([{ text: 'Found ', ...bot.styles.primary }, { text: count + '', ...bot.styles.secondary }, ' instances of ', { text: string, ...bot.styles.secondary }], true) }, abortCommand (context) { const source = context.source const bot = source.bot this.abort() source.sendFeedback({ text: 'Stopped the running log query', ...bot.styles.primary }) }, abort () { this.running = false this.stream?.close() this.stream = null }, async queryLogs (source, handler) { if (this.running) throw LOG_QUERY_ALREADY_RUNNING_ERROR.create() const bot = source.bot const logsDir = bot.paths.logs this.running = true const filenames = await fs.promises.readdir(logsDir) for (let i = 0; i < filenames.length; i++) { const filename = filenames[i] const readStream = fs.createReadStream(path.join(logsDir, filename)) this.stream = readStream if (filename.endsWith('.gz')) { const gunzip = zlib.createGunzip() readStream.pipe(gunzip) this.stream = gunzip } let queue = '' this.stream.on('data', chunk => { queue += chunk // * This should convert it to a string const lastNewlineIdx = queue.lastIndexOf('\n') const lines = queue.substring(0, lastNewlineIdx) queue = queue.substring(lastNewlineIdx + 1) for (const line of lines.split('\n')) handler(line) }) try { await stream.finished(this.stream) } catch { } if (!this.running) return false // if this was aborted } this.running = false this.stream = null return true } }