From 145a3bee151dc538fca9929db7bf243bb7243a20 Mon Sep 17 00:00:00 2001 From: Miikka Virtanen Date: Tue, 6 Dec 2016 02:29:55 +0200 Subject: [PATCH] Add log saving and serving --- index.js | 159 ++++++++++++++++++++++++++++++++++++++++++++++-- logs/.gitignore | 2 + package.json | 3 +- yarn.lock | 30 ++++++++- 4 files changed, 186 insertions(+), 8 deletions(-) create mode 100644 logs/.gitignore diff --git a/index.js b/index.js index 1b9e8f3..4379262 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,15 @@ const fs = require('fs'); +const http = require('http'); +const url = require('url'); +const crypto = require('crypto'); +const publicIp = require('public-ip'); const Eris = require('eris'); const moment = require('moment'); const Queue = require('./queue'); const config = require('./config'); +const logServerPort = config.port || 8890; + const bot = new Eris.CommandClient(config.token, {}, { prefix: config.prefix || '!', ignoreSelf: true, @@ -18,6 +24,9 @@ const messageQueue = new Queue(); const blockFile = `${__dirname}/blocked.json`; let blocked = []; +const logDir = `${__dirname}/logs`; +const logFileFormatRegex = /^([0-9\-]+?)__([0-9]+?)__([0-9a-f]+?)\.txt$/; + try { const blockedJSON = fs.readFileSync(blockFile, {encoding: 'utf8'}); blocked = JSON.parse(blockedJSON); @@ -29,6 +38,72 @@ function saveBlocked() { fs.writeFileSync(blockFile, JSON.stringify(blocked, null, 4)); } +function getLogFileInfo(logfile) { + const match = logfile.match(logFileFormatRegex); + if (! match) return null; + + return { + date: match[1], + userId: match[2], + token: match[3], + }; +} + +function getLogFilePath(logfile) { + return `${logDir}/${logfile}`; +} + +function getLogFileUrl(logfile) { + const info = getLogFileInfo(logfile); + + return publicIp.v4().then(ip => { + return `http://${ip}:${logServerPort}/logs/${info.token}`; + }); +} + +function getRandomLogFile(userId) { + return new Promise(resolve => { + crypto.randomBytes(16, (err, buf) => { + const token = buf.toString('hex'); + const date = moment.utc().format('YYYY-MM-DD-HH-mm-ss'); + + resolve(`${date}__${userId}__${token}.txt`); + }); + }); +} + +function findLogFile(token) { + return new Promise(resolve => { + fs.readdir(logDir, (err, files) => { + for (const file of files) { + if (file.endsWith(`__${token}.txt`)) { + resolve(file); + return; + } + } + + resolve(null); + }); + }); +} + +function findLogFilesByUserId(userId) { + return new Promise(resolve => { + fs.readdir(logDir, (err, files) => { + const logfiles = files.filter(file => { + const info = getLogFileInfo(file); + console.log('info:', info); + console.log('userId:', userId, typeof userId); + if (! info) return false; + + return info.userId === userId; + }); + + resolve(logfiles); + }); + }); +} + bot.on('ready', () => { modMailGuild = bot.guilds.find(g => g.id === config.mailGuildId); @@ -146,13 +221,16 @@ bot.registerCommand('close', (msg, args) => { return `[${date}] ${message.author.username}#${message.author.discriminator}: ${message.content}`; }).join('\n') + '\n'; - bot.createMessage(modMailGuild.id, `Log of modmail thread with ${channelInfo.name}:`, { - file: new Buffer(log), - name: 'log.txt', - }); + getRandomLogFile(channelInfo.userId).then(logfile => { + fs.writeFile(getLogFilePath(logfile), log, {encoding: 'utf8'}, err => { + getLogFileUrl(logfile).then(logurl => { + bot.createMessage(modMailGuild.id, `Log of modmail thread with ${channelInfo.name}:\n<${logurl}>`); - delete modMailChannels[channelInfo.userId]; - msg.channel.delete(); + delete modMailChannels[channelInfo.userId]; + msg.channel.delete(); + }); + }); + }) }); }); @@ -196,4 +274,73 @@ bot.registerCommand('unblock', (msg, args) => { msg.channel.createMessage(`Unblocked <@${userId}> (id ${userId}) from modmail`); }); +bot.registerCommand('logs', (msg, args) => { + if (msg.channel.guild.id !== modMailGuild.id) return; + if (! msg.member.permission.has('manageRoles')) return; + if (args.length !== 1) return; + + let userId; + if (args[0].match(/^[0-9]+$/)) { + userId = args[0]; + } else { + let mentionMatch = args[0].match(/^<@([0-9]+?)>$/); + if (mentionMatch) userId = mentionMatch[1]; + } + + if (! userId) return; + + findLogFilesByUserId(userId).then(logfiles => { + let message = `**Log files for <@${userId}>:**\n`; + + const urlPromises = logfiles.map(logfile => { + const info = getLogFileInfo(logfile); + return getLogFileUrl(logfile).then(url => { + info.url = url; + return info; + }); + }); + + Promise.all(urlPromises).then(infos => { + infos.sort((a, b) => { + if (a.date > b.date) return 1; + if (a.date < b.date) return -1; + return 0; + }); + + message += infos.map(info => { + const formattedDate = moment.utc(info.date, 'YYYY-MM-DD-HH-mm-ss').format('MMM Mo [at] HH:mm [UTC]'); + return `${formattedDate}: <${info.url}>`; + }).join('\n'); + + msg.channel.createMessage(message); + }); + }); +}); + bot.connect(); + +// Server for serving logs +const server = http.createServer((req, res) => { + const parsedUrl = url.parse(`http://${req.url}`); + + if (! parsedUrl.path.startsWith('/logs/')) return; + + const pathParts = parsedUrl.path.split('/').filter(v => v !== ''); + const token = pathParts[pathParts.length - 1]; + + console.log('token:', token); + + if (token.match(/^[0-9a-f]+$/) === null) return res.end(); + + findLogFile(token).then(logfile => { + console.log('logfile:', logfile); + if (logfile === null) return res.end(); + + fs.readFile(getLogFilePath(logfile), {encoding: 'utf8'}, (err, data) => { + res.setHeader('Content-Type', 'text/plain'); + res.end(data); + }); + }); +}); + +server.listen(logServerPort); diff --git a/logs/.gitignore b/logs/.gitignore new file mode 100644 index 0000000..120f485 --- /dev/null +++ b/logs/.gitignore @@ -0,0 +1,2 @@ +* +!/.gitignore diff --git a/package.json b/package.json index 3c08808..dffbdfd 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "license": "ISC", "dependencies": { "eris": "github:abalabahaha/eris#dev", - "moment": "^2.17.1" + "moment": "^2.17.1", + "public-ip": "^2.0.1" }, "devDependencies": { "eslint": "^3.9.1" diff --git a/yarn.lock b/yarn.lock index 6ed8b83..af85abb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -162,6 +162,19 @@ del@^2.0.2: pinkie-promise "^2.0.0" rimraf "^2.2.8" +dns-packet@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.1.1.tgz#2369d45038af045f3898e6fa56862aed3f40296c" + dependencies: + ip "^1.1.0" + safe-buffer "^5.0.1" + +dns-socket@^1.0.0: + version "1.4.2" + resolved "https://registry.yarnpkg.com/dns-socket/-/dns-socket-1.4.2.tgz#8e09322c1566e2cb402c322f086dfe69fd0898e5" + dependencies: + dns-packet "^1.1.0" + doctrine@^1.2.2: version "1.5.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" @@ -441,6 +454,10 @@ interpret@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.1.tgz#d579fb7f693b858004947af39fa0db49f795602c" +ip@^1.1.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.4.tgz#de8247ffef940451832550fba284945e6e039bfb" + is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" @@ -607,7 +624,7 @@ path-is-inside@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" -pify@^2.0.0: +pify@^2.0.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -637,6 +654,13 @@ progress@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" +public-ip: + version "2.0.1" + resolved "https://registry.yarnpkg.com/public-ip/-/public-ip-2.0.1.tgz#1648026a5a11fb88bee52bd4ecf4a2e6af3747f7" + dependencies: + dns-socket "^1.0.0" + pify "^2.3.0" + readable-stream@~2.0.0: version "2.0.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" @@ -700,6 +724,10 @@ rx-lite@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" +safe-buffer@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" + shelljs@^0.7.5: version "0.7.5" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.5.tgz#2eef7a50a21e1ccf37da00df767ec69e30ad0675"