Major refactor about done
parent
6d33e7adb4
commit
6a13724c6a
|
@ -1,14 +1,26 @@
|
||||||
|
const Eris = require('eris');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const config = require('../config');
|
const config = require('../config');
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
|
|
||||||
const attachmentDir = config.attachmentDir || `${__dirname}/attachments`;
|
const attachmentDir = config.attachmentDir || `${__dirname}/../attachments`;
|
||||||
|
|
||||||
function getAttachmentPath(id) {
|
/**
|
||||||
return `${attachmentDir}/${id}`;
|
* Returns the filesystem path for the given attachment id
|
||||||
|
* @param {String} attachmentId
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
function getPath(attachmentId) {
|
||||||
|
return `${attachmentDir}/${attachmentId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to download and save the given attachement
|
||||||
|
* @param {Object} attachment
|
||||||
|
* @param {Number=0} tries
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
function saveAttachment(attachment, tries = 0) {
|
function saveAttachment(attachment, tries = 0) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (tries > 3) {
|
if (tries > 3) {
|
||||||
|
@ -17,7 +29,7 @@ function saveAttachment(attachment, tries = 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const filepath = getAttachmentPath(attachment.id);
|
const filepath = getPath(attachment.id);
|
||||||
const writeStream = fs.createWriteStream(filepath);
|
const writeStream = fs.createWriteStream(filepath);
|
||||||
|
|
||||||
https.get(attachment.url, (res) => {
|
https.get(attachment.url, (res) => {
|
||||||
|
@ -34,19 +46,30 @@ function saveAttachment(attachment, tries = 0) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveAttachments(msg) {
|
/**
|
||||||
|
* Attempts to download and save all attachments in the given message
|
||||||
|
* @param {Eris.Message} msg
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
function saveAttachmentsInMessage(msg) {
|
||||||
if (! msg.attachments || msg.attachments.length === 0) return Promise.resolve();
|
if (! msg.attachments || msg.attachments.length === 0) return Promise.resolve();
|
||||||
return Promise.all(msg.attachments.map(saveAttachment));
|
return Promise.all(msg.attachments.map(saveAttachment));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAttachmentUrl(id, desiredName) {
|
/**
|
||||||
|
* Returns the self-hosted URL to the given attachment ID
|
||||||
|
* @param {String} attachmentId
|
||||||
|
* @param {String=null} desiredName Custom name for the attachment as a hint for the browser
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
function getUrl(attachmentId, desiredName = null) {
|
||||||
if (desiredName == null) desiredName = 'file.bin';
|
if (desiredName == null) desiredName = 'file.bin';
|
||||||
return utils.getSelfUrl(`attachments/${id}/${desiredName}`);
|
return utils.getSelfUrl(`attachments/${attachmentId}/${desiredName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getAttachmentPath,
|
getPath,
|
||||||
saveAttachment,
|
saveAttachment,
|
||||||
saveAttachments,
|
saveAttachmentsInMessage,
|
||||||
getAttachmentUrl,
|
getUrl,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,20 +1,35 @@
|
||||||
const jsonDb = require('./jsonDb');
|
const jsonDb = require('./jsonDb');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether userId is blocked
|
||||||
|
* @param {String} userId
|
||||||
|
* @returns {Promise<Boolean>}
|
||||||
|
*/
|
||||||
function isBlocked(userId) {
|
function isBlocked(userId) {
|
||||||
return jsonDb.get('blocked').then(blocked => {
|
return jsonDb.get('blocked', []).then(blocked => {
|
||||||
return blocked.indexOf(userId) !== -1;
|
return blocked.indexOf(userId) !== -1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blocks the given userId
|
||||||
|
* @param {String} userId
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
function block(userId) {
|
function block(userId) {
|
||||||
return jsonDb.get('blocked').then(blocked => {
|
return jsonDb.get('blocked', []).then(blocked => {
|
||||||
blocked.push(userId);
|
blocked.push(userId);
|
||||||
return jsonDb.save('blocked', blocked);
|
return jsonDb.save('blocked', blocked);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unblocks the given userId
|
||||||
|
* @param {String} userId
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
function unblock(userId) {
|
function unblock(userId) {
|
||||||
return jsonDb.get('blocked').then(blocked => {
|
return jsonDb.get('blocked', []).then(blocked => {
|
||||||
blocked.splice(blocked.indexOf(userId), 1);
|
blocked.splice(blocked.indexOf(userId), 1);
|
||||||
return jsonDb.save('blocked', blocked);
|
return jsonDb.save('blocked', blocked);
|
||||||
});
|
});
|
||||||
|
|
72
src/index.js
72
src/index.js
|
@ -1,7 +1,8 @@
|
||||||
const Eris = require('eris');
|
const Eris = require('eris');
|
||||||
|
const fs = require('fs');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const Queue = require('./queue');
|
|
||||||
const config = require('../config');
|
const config = require('../config');
|
||||||
|
const Queue = require('./queue');
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
const blocked = require('./blocked');
|
const blocked = require('./blocked');
|
||||||
const threads = require('./threads');
|
const threads = require('./threads');
|
||||||
|
@ -23,6 +24,29 @@ bot.on('ready', () => {
|
||||||
console.log('Bot started, listening to DMs');
|
console.log('Bot started, listening to DMs');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function formatAttachment(attachment) {
|
||||||
|
let filesize = attachment.size || 0;
|
||||||
|
filesize /= 1024;
|
||||||
|
|
||||||
|
return attachments.getUrl(attachment.id, attachment.filename).then(attachmentUrl => {
|
||||||
|
return `**Attachment:** ${attachment.filename} (${filesize.toFixed(1)}KB)\n${attachmentUrl}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatUserDM(msg) {
|
||||||
|
let content = msg.content;
|
||||||
|
|
||||||
|
// Get a local URL for all attachments so we don't rely on discord's servers (which delete attachments when the channel/DM thread is deleted)
|
||||||
|
const attachmentFormatPromise = msg.attachments.map(formatAttachment);
|
||||||
|
return Promise.all(attachmentFormatPromise).then(formattedAttachments => {
|
||||||
|
formattedAttachments.forEach(str => {
|
||||||
|
content += `\n\n${str}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
return content;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// "Bot was mentioned in #general-discussion"
|
// "Bot was mentioned in #general-discussion"
|
||||||
bot.on('messageCreate', msg => {
|
bot.on('messageCreate', msg => {
|
||||||
if (msg.author.id === bot.user.id) return;
|
if (msg.author.id === bot.user.id) return;
|
||||||
|
@ -48,19 +72,20 @@ bot.on('messageCreate', (msg) => {
|
||||||
if (isBlocked) return;
|
if (isBlocked) return;
|
||||||
|
|
||||||
// Download and save copies of attachments in the background
|
// Download and save copies of attachments in the background
|
||||||
attachments.saveAttachments(msg);
|
attachments.saveAttachmentsInMessage(msg);
|
||||||
|
|
||||||
let thread, logs;
|
let thread, userLogs;
|
||||||
|
|
||||||
|
// Private message handling is queued so e.g. multiple message in quick succession don't result in multiple channels aren't created
|
||||||
messageQueue.add(() => {
|
messageQueue.add(() => {
|
||||||
threads.getForUser(bot, msg.author)
|
return threads.getForUser(bot, msg.author)
|
||||||
.then(userThread => {
|
.then(userThread => {
|
||||||
thread = userThread;
|
thread = userThread;
|
||||||
return logs.getLogsByUserId(msg.author.id);
|
return logs.getLogsByUserId(msg.author.id);
|
||||||
})
|
})
|
||||||
.then(userLogs => {
|
.then(foundUserLogs => {
|
||||||
logs = userLogs;
|
userLogs = foundUserLogs;
|
||||||
return utils.formatUserDM(msg);
|
return formatUserDM(msg);
|
||||||
})
|
})
|
||||||
.then(content => {
|
.then(content => {
|
||||||
// If the thread does not exist and could not be created, send a warning about this to all mods so they can DM the user directly instead
|
// If the thread does not exist and could not be created, send a warning about this to all mods so they can DM the user directly instead
|
||||||
|
@ -88,7 +113,7 @@ bot.on('messageCreate', (msg) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ping mods of the new thread
|
// Ping mods of the new thread
|
||||||
let creationNotificationMessage = `New modmail thread: <#${channel.id}>`;
|
let creationNotificationMessage = `New modmail thread: <#${thread.channelId}>`;
|
||||||
if (config.pingCreationNotification) creationNotificationMessage = `@here ${creationNotificationMessage}`;
|
if (config.pingCreationNotification) creationNotificationMessage = `@here ${creationNotificationMessage}`;
|
||||||
|
|
||||||
bot.createMessage(utils.getModmailGuild(bot).id, {
|
bot.createMessage(utils.getModmailGuild(bot).id, {
|
||||||
|
@ -98,15 +123,15 @@ bot.on('messageCreate', (msg) => {
|
||||||
|
|
||||||
// Send an automatic reply to the user informing them of the successfully created modmail thread
|
// Send an automatic reply to the user informing them of the successfully created modmail thread
|
||||||
msg.channel.createMessage("Thank you for your message! Our mod team will reply to you here as soon as possible.").then(null, (err) => {
|
msg.channel.createMessage("Thank you for your message! Our mod team will reply to you here as soon as possible.").then(null, (err) => {
|
||||||
bot.createMessage(modMailGuild.id, {
|
bot.createMessage(utils.getModmailGuild(bot).id, {
|
||||||
content: `There is an issue sending messages to ${msg.author.username}#${msg.author.discriminator} (id ${msg.author.id}); consider messaging manually`
|
content: `There is an issue sending messages to ${msg.author.username}#${msg.author.discriminator} (id ${msg.author.id}); consider messaging manually`
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const timestamp = utils.getTimestamp();
|
const timestamp = utils.getTimestamp();
|
||||||
bot.createMessage(channel.id, `[${timestamp}] « **${msg.author.username}#${msg.author.discriminator}:** ${content}`);
|
bot.createMessage(thread.channelId, `[${timestamp}] « **${msg.author.username}#${msg.author.discriminator}:** ${content}`);
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -144,11 +169,10 @@ bot.registerCommand('reply', (msg, args) => {
|
||||||
threads.getByChannelId(msg.channel.id).then(thread => {
|
threads.getByChannelId(msg.channel.id).then(thread => {
|
||||||
if (! thread) return;
|
if (! thread) return;
|
||||||
|
|
||||||
attachments.saveAttachments(msg).then(() => {
|
attachments.saveAttachmentsInMessage(msg).then(() => {
|
||||||
bot.getDMChannel(thread.userId).then(dmChannel => {
|
bot.getDMChannel(thread.userId).then(dmChannel => {
|
||||||
const roleId = msg.member.roles[0];
|
const mainRole = utils.getMainRole(msg.member);
|
||||||
const role = (roleId ? (modMailGuild.roles.get(roleId) || {}).name : '');
|
const roleStr = (mainRole ? `(${mainRole.name}) ` : '');
|
||||||
const roleStr = (role ? `(${role}) ` : '');
|
|
||||||
|
|
||||||
let argMsg = args.join(' ').trim();
|
let argMsg = args.join(' ').trim();
|
||||||
let content = `**${roleStr}${msg.author.username}:** ${argMsg}`;
|
let content = `**${roleStr}${msg.author.username}:** ${argMsg}`;
|
||||||
|
@ -157,7 +181,7 @@ bot.registerCommand('reply', (msg, args) => {
|
||||||
dmChannel.createMessage(content, file).then(() => {
|
dmChannel.createMessage(content, file).then(() => {
|
||||||
if (attachmentUrl) content += `\n\n**Attachment:** ${attachmentUrl}`;
|
if (attachmentUrl) content += `\n\n**Attachment:** ${attachmentUrl}`;
|
||||||
|
|
||||||
const timestamp = getTimestamp();
|
const timestamp = utils.getTimestamp();
|
||||||
msg.channel.createMessage(`[${timestamp}] » ${content}`);
|
msg.channel.createMessage(`[${timestamp}] » ${content}`);
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
if (err.resp && err.resp.statusCode === 403) {
|
if (err.resp && err.resp.statusCode === 403) {
|
||||||
|
@ -174,10 +198,10 @@ bot.registerCommand('reply', (msg, args) => {
|
||||||
|
|
||||||
// If the reply has an attachment, relay it as is
|
// If the reply has an attachment, relay it as is
|
||||||
if (msg.attachments.length > 0) {
|
if (msg.attachments.length > 0) {
|
||||||
fs.readFile(attachments.getAttachmentPath(msg.attachments[0].id), (err, data) => {
|
fs.readFile(attachments.getPath(msg.attachments[0].id), (err, data) => {
|
||||||
const file = {file: data, name: msg.attachments[0].filename};
|
const file = {file: data, name: msg.attachments[0].filename};
|
||||||
|
|
||||||
getAttachmentUrl(msg.attachments[0].id, msg.attachments[0].filename).then(attachmentUrl => {
|
attachments.getUrl(msg.attachments[0].id, msg.attachments[0].filename).then(attachmentUrl => {
|
||||||
sendMessage(file, attachmentUrl);
|
sendMessage(file, attachmentUrl);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -193,7 +217,7 @@ bot.registerCommandAlias('r', 'reply');
|
||||||
|
|
||||||
bot.registerCommand('close', (msg, args) => {
|
bot.registerCommand('close', (msg, args) => {
|
||||||
if (! msg.channel.guild) return;
|
if (! msg.channel.guild) return;
|
||||||
if (msg.channel.guild.id !== modMailGuild.id) return;
|
if (msg.channel.guild.id !== utils.getModmailGuild(bot).id) return;
|
||||||
if (! msg.member.permission.has('manageRoles')) return;
|
if (! msg.member.permission.has('manageRoles')) return;
|
||||||
|
|
||||||
threads.getByChannelId(msg.channel.id).then(thread => {
|
threads.getByChannelId(msg.channel.id).then(thread => {
|
||||||
|
@ -208,7 +232,7 @@ bot.registerCommand('close', (msg, args) => {
|
||||||
|
|
||||||
logs.getNewLogFile(thread.userId).then(logFilename => {
|
logs.getNewLogFile(thread.userId).then(logFilename => {
|
||||||
logs.saveLogFile(logFilename, log)
|
logs.saveLogFile(logFilename, log)
|
||||||
.then(() => getLogFileUrl(logFilename))
|
.then(() => logs.getLogFileUrl(logFilename))
|
||||||
.then(url => {
|
.then(url => {
|
||||||
const closeMessage = `Modmail thread with ${thread.username} (${thread.userId}) was closed by ${msg.author.mention}
|
const closeMessage = `Modmail thread with ${thread.username} (${thread.userId}) was closed by ${msg.author.mention}
|
||||||
Logs: <${url}>`;
|
Logs: <${url}>`;
|
||||||
|
@ -240,7 +264,7 @@ bot.registerCommand('block', (msg, args) => {
|
||||||
// Calling !block without args in a modmail thread blocks the user of that thread
|
// Calling !block without args in a modmail thread blocks the user of that thread
|
||||||
threads.getByChannelId(msg.channel.id).then(thread => {
|
threads.getByChannelId(msg.channel.id).then(thread => {
|
||||||
if (! thread) return;
|
if (! thread) return;
|
||||||
block(userId);
|
block(thread.userId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -264,7 +288,7 @@ bot.registerCommand('unblock', (msg, args) => {
|
||||||
// Calling !unblock without args in a modmail thread unblocks the user of that thread
|
// Calling !unblock without args in a modmail thread unblocks the user of that thread
|
||||||
threads.getByChannelId(msg.channel.id).then(thread => {
|
threads.getByChannelId(msg.channel.id).then(thread => {
|
||||||
if (! thread) return;
|
if (! thread) return;
|
||||||
unblock(userId);
|
unblock(thread.userId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -275,7 +299,7 @@ bot.registerCommand('logs', (msg, args) => {
|
||||||
if (! msg.member.permission.has('manageRoles')) return;
|
if (! msg.member.permission.has('manageRoles')) return;
|
||||||
|
|
||||||
function getLogs(userId) {
|
function getLogs(userId) {
|
||||||
getLogsWithUrlByUserId(userId).then(infos => {
|
logs.getLogsWithUrlByUserId(userId).then(infos => {
|
||||||
let message = `**Log files for <@${userId}>:**\n`;
|
let message = `**Log files for <@${userId}>:**\n`;
|
||||||
|
|
||||||
message += infos.map(info => {
|
message += infos.map(info => {
|
||||||
|
@ -295,7 +319,7 @@ bot.registerCommand('logs', (msg, args) => {
|
||||||
// Calling !logs without args in a modmail thread returns the logs of the user of that thread
|
// Calling !logs without args in a modmail thread returns the logs of the user of that thread
|
||||||
threads.getByChannelId(msg.channel.id).then(thread => {
|
threads.getByChannelId(msg.channel.id).then(thread => {
|
||||||
if (! thread) return;
|
if (! thread) return;
|
||||||
getLogs(userId);
|
getLogs(thread.userId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const config = require('../config');
|
||||||
|
|
||||||
const dbDir = config.dbDir || `${__dirname}/db`;
|
const dbDir = config.dbDir || `${__dirname}/../db`;
|
||||||
|
|
||||||
const databases = {};
|
const databases = {};
|
||||||
|
|
||||||
class JSONDB {
|
class JSONDB {
|
||||||
constructor(path, def = {}, useCloneByDefault = true) {
|
constructor(path, def = {}, useCloneByDefault = false) {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.useCloneByDefault = useCloneByDefault;
|
this.useCloneByDefault = useCloneByDefault;
|
||||||
|
|
||||||
|
|
57
src/logs.js
57
src/logs.js
|
@ -2,10 +2,25 @@ const fs = require('fs');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const config = require('../config');
|
const config = require('../config');
|
||||||
|
const utils = require('./utils');
|
||||||
|
|
||||||
const logDir = config.logDir || `${__dirname}/logs`;
|
const logDir = config.logDir || `${__dirname}/../logs`;
|
||||||
const logFileFormatRegex = /^([0-9\-]+?)__([0-9]+?)__([0-9a-f]+?)\.txt$/;
|
const logFileFormatRegex = /^([0-9\-]+?)__([0-9]+?)__([0-9a-f]+?)\.txt$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} LogFileInfo
|
||||||
|
* @property {String} filename
|
||||||
|
* @property {String} date
|
||||||
|
* @property {String} userId
|
||||||
|
* @property {String} token
|
||||||
|
* @property {String=} url
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns information about the given logfile
|
||||||
|
* @param {String} logFilename
|
||||||
|
* @returns {LogFileInfo}
|
||||||
|
*/
|
||||||
function getLogFileInfo(logFilename) {
|
function getLogFileInfo(logFilename) {
|
||||||
const match = logFilename.match(logFileFormatRegex);
|
const match = logFilename.match(logFileFormatRegex);
|
||||||
if (! match) return null;
|
if (! match) return null;
|
||||||
|
@ -20,15 +35,30 @@ function getLogFileInfo(logFilename) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the filesystem path to the given logfile
|
||||||
|
* @param {String} logFilename
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
function getLogFilePath(logFilename) {
|
function getLogFilePath(logFilename) {
|
||||||
return `${logDir}/${logFilename}`;
|
return `${logDir}/${logFilename}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the self-hosted URL to the given logfile
|
||||||
|
* @param {String} logFilename
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
function getLogFileUrl(logFilename) {
|
function getLogFileUrl(logFilename) {
|
||||||
const info = getLogFileInfo(logFilename);
|
const info = getLogFileInfo(logFilename);
|
||||||
return utils.getSelfUrl(`logs/${info.token}`);
|
return utils.getSelfUrl(`logs/${info.token}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new, unique log file name for the given userId
|
||||||
|
* @param {String} userId
|
||||||
|
* @returns {Promise<String>}
|
||||||
|
*/
|
||||||
function getNewLogFile(userId) {
|
function getNewLogFile(userId) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
crypto.randomBytes(16, (err, buf) => {
|
crypto.randomBytes(16, (err, buf) => {
|
||||||
|
@ -40,6 +70,11 @@ function getNewLogFile(userId) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a log file name by its token
|
||||||
|
* @param {String} token
|
||||||
|
* @returns {Promise<String>}
|
||||||
|
*/
|
||||||
function findLogFile(token) {
|
function findLogFile(token) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
fs.readdir(logDir, (err, files) => {
|
fs.readdir(logDir, (err, files) => {
|
||||||
|
@ -55,9 +90,16 @@ function findLogFile(token) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all log file infos for the given userId
|
||||||
|
* @param {String} userId
|
||||||
|
* @returns {Promise<LogFileInfo[]>}
|
||||||
|
*/
|
||||||
function getLogsByUserId(userId) {
|
function getLogsByUserId(userId) {
|
||||||
return new Promise(resolve => {
|
return new Promise((resolve, reject) => {
|
||||||
fs.readdir(logDir, (err, files) => {
|
fs.readdir(logDir, (err, files) => {
|
||||||
|
if (err) return reject(err);
|
||||||
|
|
||||||
const logfileInfos = files
|
const logfileInfos = files
|
||||||
.map(file => getLogFileInfo(file))
|
.map(file => getLogFileInfo(file))
|
||||||
.filter(info => info && info.userId === userId);
|
.filter(info => info && info.userId === userId);
|
||||||
|
@ -67,6 +109,11 @@ function getLogsByUserId(userId) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all log file infos with URLs for the given userId
|
||||||
|
* @param {String} userId
|
||||||
|
* @returns {Promise<LogFileInfo[]>}
|
||||||
|
*/
|
||||||
function getLogsWithUrlByUserId(userId) {
|
function getLogsWithUrlByUserId(userId) {
|
||||||
return getLogsByUserId(userId).then(infos => {
|
return getLogsByUserId(userId).then(infos => {
|
||||||
const urlPromises = infos.map(info => {
|
const urlPromises = infos.map(info => {
|
||||||
|
@ -88,6 +135,11 @@ function getLogsWithUrlByUserId(userId) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} logFilename
|
||||||
|
* @param {String} content
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
function saveLogFile(logFilename, content) {
|
function saveLogFile(logFilename, content) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
fs.writeFile(getLogFilePath(logFilename), content, {encoding: 'utf8'}, err => {
|
fs.writeFile(getLogFilePath(logFilename), content, {encoding: 'utf8'}, err => {
|
||||||
|
@ -105,4 +157,5 @@ module.exports = {
|
||||||
getLogsByUserId,
|
getLogsByUserId,
|
||||||
getLogsWithUrlByUserId,
|
getLogsWithUrlByUserId,
|
||||||
saveLogFile,
|
saveLogFile,
|
||||||
|
getLogFileUrl,
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,10 +16,10 @@ const jsonDb = require('./jsonDb');
|
||||||
* @param {Eris.Client} bot
|
* @param {Eris.Client} bot
|
||||||
* @param {Eris.User} user
|
* @param {Eris.User} user
|
||||||
* @param {Boolean} allowCreate
|
* @param {Boolean} allowCreate
|
||||||
* @returns {Promise<ThreadInfo>}
|
* @returns {Promise<ModMailThread>}
|
||||||
*/
|
*/
|
||||||
function getForUser(bot, user, allowCreate = true) {
|
function getForUser(bot, user, allowCreate = true) {
|
||||||
return jsonDb.get('threads').then(threads => {
|
return jsonDb.get('threads', []).then(threads => {
|
||||||
const thread = threads.find(t => t.userId === user.id);
|
const thread = threads.find(t => t.userId === user.id);
|
||||||
if (thread) return thread;
|
if (thread) return thread;
|
||||||
|
|
||||||
|
@ -41,12 +41,12 @@ function getForUser(bot, user, allowCreate = true) {
|
||||||
username: `${user.username}#${user.discriminator}`,
|
username: `${user.username}#${user.discriminator}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
const threads = jsonDb.get('threads');
|
return jsonDb.get('threads', []).then(threads => {
|
||||||
threads.push(thread);
|
threads.push(thread);
|
||||||
jsonDb.save('threads', threads);
|
jsonDb.save('threads', threads);
|
||||||
|
|
||||||
thread._wasCreated = true;
|
return Object.assign({}, thread, {_wasCreated: true});
|
||||||
return thread;
|
});
|
||||||
}, err => {
|
}, err => {
|
||||||
console.error(`Error creating modmail channel for ${user.username}#${user.discriminator}!`);
|
console.error(`Error creating modmail channel for ${user.username}#${user.discriminator}!`);
|
||||||
});
|
});
|
||||||
|
@ -55,17 +55,22 @@ function getForUser(bot, user, allowCreate = true) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {String} channelId
|
* @param {String} channelId
|
||||||
* @returns {Promise<ThreadInfo>}
|
* @returns {Promise<ModMailThread>}
|
||||||
*/
|
*/
|
||||||
function getByChannelId(channelId) {
|
function getByChannelId(channelId) {
|
||||||
return jsonDb.get('threads').then(threads => {
|
return jsonDb.get('threads', []).then(threads => {
|
||||||
return threads.find(t => t.userId === user.id);
|
return threads.find(t => t.channelId === channelId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the modmail thread for the given channel id
|
||||||
|
* @param {String} channelId
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
function close(channelId) {
|
function close(channelId) {
|
||||||
return jsonDb.get('threads').then(threads => {
|
return jsonDb.get('threads', []).then(threads => {
|
||||||
const thread = threads.find(t => t.userId === user.id);
|
const thread = threads.find(t => t.channelId === channelId);
|
||||||
if (! thread) return;
|
if (! thread) return;
|
||||||
|
|
||||||
threads.splice(threads.indexOf(thread), 1);
|
threads.splice(threads.indexOf(thread), 1);
|
||||||
|
|
67
src/utils.js
67
src/utils.js
|
@ -1,37 +1,22 @@
|
||||||
|
const Eris = require('eris');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const publicIp = require('public-ip');
|
const publicIp = require('public-ip');
|
||||||
const config = require('../config');
|
const config = require('../config');
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
|
|
||||||
|
let modMailGuild = null;
|
||||||
function getModmailGuild(bot) {
|
function getModmailGuild(bot) {
|
||||||
return bot.guilds.find(g => g.id === config.mailGuildId);
|
if (! modMailGuild) modMailGuild = bot.guilds.find(g => g.id === config.mailGuildId);
|
||||||
}
|
return modMailGuild;
|
||||||
|
|
||||||
function formatAttachment(attachment) {
|
|
||||||
let filesize = attachment.size || 0;
|
|
||||||
filesize /= 1024;
|
|
||||||
|
|
||||||
return utils.getAttachmentUrl(attachment.id, attachment.filename).then(attachmentUrl => {
|
|
||||||
return `**Attachment:** ${attachment.filename} (${filesize.toFixed(1)}KB)\n${attachmentUrl}`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatUserDM(msg) {
|
|
||||||
let content = msg.content;
|
|
||||||
|
|
||||||
// Get a local URL for all attachments so we don't rely on discord's servers (which delete attachments when the channel/DM thread is deleted)
|
|
||||||
const attachmentFormatPromise = msg.attachments.map(formatAttachment);
|
|
||||||
return Promise.all(attachmentFormatPromise).then(formattedAttachments => {
|
|
||||||
formattedAttachments.forEach(str => {
|
|
||||||
content += `\n\n${str}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
return content;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const userMentionRegex = /^<@\!?([0-9]+?)>$/;
|
const userMentionRegex = /^<@\!?([0-9]+?)>$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the user ID of the user mentioned in str, if any
|
||||||
|
* @param {String} str
|
||||||
|
* @returns {String|null}
|
||||||
|
*/
|
||||||
function getUserMention(str) {
|
function getUserMention(str) {
|
||||||
str = str.trim();
|
str = str.trim();
|
||||||
|
|
||||||
|
@ -46,30 +31,56 @@ function getUserMention(str) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current timestamp in an easily readable form
|
||||||
|
* @param {String|Date|undefined} date
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
function getTimestamp(date) {
|
function getTimestamp(date) {
|
||||||
return moment.utc(date).format('HH:mm');
|
return moment.utc(date).format('HH:mm');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables link previews in the given string by wrapping links in < >
|
||||||
|
* @param {String} str
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
function disableLinkPreviews(str) {
|
function disableLinkPreviews(str) {
|
||||||
return str.replace(/(^|[^<])(https?:\/\/\S+)/ig, '$1<$2>');
|
return str.replace(/(^|[^<])(https?:\/\/\S+)/ig, '$1<$2>');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSelfUrl(path) {
|
/**
|
||||||
|
* Returns a URL to the bot's web server
|
||||||
|
* @param {String} path
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
function getSelfUrl(path = '') {
|
||||||
if (config.url) {
|
if (config.url) {
|
||||||
return Promise.resolve(`${config.url}/${path}`);
|
return Promise.resolve(`${config.url}/${path}`);
|
||||||
} else {
|
} else {
|
||||||
|
const port = config.port || 8890;
|
||||||
return publicIp.v4().then(ip => {
|
return publicIp.v4().then(ip => {
|
||||||
return `http://${ip}:${logServerPort}/${path}`;
|
return `http://${ip}:${port}/${path}`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the highest hoisted role of the given member
|
||||||
|
* @param {Eris.Member} member
|
||||||
|
* @returns {Eris.Role}
|
||||||
|
*/
|
||||||
|
function getMainRole(member) {
|
||||||
|
const roles = member.roles.map(id => member.guild.roles.get(id));
|
||||||
|
roles.sort((a, b) => a.position > b.position ? -1 : 1);
|
||||||
|
return roles.find(r => r.hoist);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getModmailGuild,
|
getModmailGuild,
|
||||||
formatAttachment,
|
|
||||||
formatUserDM,
|
|
||||||
getUserMention,
|
getUserMention,
|
||||||
getTimestamp,
|
getTimestamp,
|
||||||
disableLinkPreviews,
|
disableLinkPreviews,
|
||||||
getSelfUrl,
|
getSelfUrl,
|
||||||
|
getMainRole,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
const mime = require('mime');
|
const mime = require('mime');
|
||||||
|
const url = require('url');
|
||||||
|
const fs = require('fs');
|
||||||
const config = require('../config');
|
const config = require('../config');
|
||||||
|
const logs = require('./logs');
|
||||||
|
const attachments = require('./attachments');
|
||||||
|
|
||||||
const port = config.port || 8890;
|
const port = config.port || 8890;
|
||||||
|
|
||||||
|
@ -8,10 +12,10 @@ function serveLogs(res, pathParts) {
|
||||||
const token = pathParts[pathParts.length - 1];
|
const token = pathParts[pathParts.length - 1];
|
||||||
if (token.match(/^[0-9a-f]+$/) === null) return res.end();
|
if (token.match(/^[0-9a-f]+$/) === null) return res.end();
|
||||||
|
|
||||||
findLogFile(token).then(logfile => {
|
logs.findLogFile(token).then(logFilename => {
|
||||||
if (logfile === null) return res.end();
|
if (logFilename === null) return res.end();
|
||||||
|
|
||||||
fs.readFile(getLogFilePath(logfile), {encoding: 'utf8'}, (err, data) => {
|
fs.readFile(logs.getLogFilePath(logFilename), {encoding: 'utf8'}, (err, data) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
res.statusCode = 404;
|
res.statusCode = 404;
|
||||||
res.end('Log not found');
|
res.end('Log not found');
|
||||||
|
@ -31,7 +35,7 @@ function serveAttachments(res, pathParts) {
|
||||||
if (id.match(/^[0-9]+$/) === null) return res.end();
|
if (id.match(/^[0-9]+$/) === null) return res.end();
|
||||||
if (desiredFilename.match(/^[0-9a-z\._-]+$/i) === null) return res.end();
|
if (desiredFilename.match(/^[0-9a-z\._-]+$/i) === null) return res.end();
|
||||||
|
|
||||||
const attachmentPath = getAttachmentPath(id);
|
const attachmentPath = attachments.getPath(id);
|
||||||
fs.access(attachmentPath, (err) => {
|
fs.access(attachmentPath, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
res.statusCode = 404;
|
res.statusCode = 404;
|
||||||
|
@ -59,7 +63,7 @@ function run() {
|
||||||
if (parsedUrl.path.startsWith('/attachments/')) serveAttachments(res, pathParts);
|
if (parsedUrl.path.startsWith('/attachments/')) serveAttachments(res, pathParts);
|
||||||
});
|
});
|
||||||
|
|
||||||
server.listen(logServerPort);
|
server.listen(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
Loading…
Reference in New Issue