305 lines
11 KiB
JavaScript
305 lines
11 KiB
JavaScript
|
const Eris = require('eris');
|
||
|
const moment = require('moment');
|
||
|
const Queue = require('./queue');
|
||
|
const config = require('../config');
|
||
|
const utils = require('./utils');
|
||
|
const blocked = require('./blocked');
|
||
|
const threads = require('./threads');
|
||
|
const logs = require('./logs');
|
||
|
const attachments = require('./attachments');
|
||
|
const webserver = require('./webserver');
|
||
|
|
||
|
const bot = new Eris.CommandClient(config.token, {}, {
|
||
|
prefix: config.prefix || '!',
|
||
|
ignoreSelf: true,
|
||
|
ignoreBots: true,
|
||
|
defaultHelpCommand: false,
|
||
|
});
|
||
|
|
||
|
const messageQueue = new Queue();
|
||
|
|
||
|
bot.on('ready', () => {
|
||
|
bot.editStatus(null, {name: config.status || 'Message me for help'});
|
||
|
console.log('Bot started, listening to DMs');
|
||
|
});
|
||
|
|
||
|
// "Bot was mentioned in #general-discussion"
|
||
|
bot.on('messageCreate', msg => {
|
||
|
if (msg.author.id === bot.user.id) return;
|
||
|
|
||
|
if (msg.mentions.some(user => user.id === bot.user.id)) {
|
||
|
blocked.isBlocked(msg.author.id).then(isBlocked => {
|
||
|
if (isBlocked) return;
|
||
|
|
||
|
bot.createMessage(utils.getModmailGuild(bot).id, {
|
||
|
content: `@here Bot mentioned in ${msg.channel.mention} by **${msg.author.username}#${msg.author.discriminator}**: "${msg.cleanContent}"`,
|
||
|
disableEveryone: false,
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// When we get a private message, forward the contents to the corresponding modmail thread
|
||
|
bot.on('messageCreate', (msg) => {
|
||
|
if (! (msg.channel instanceof Eris.PrivateChannel)) return;
|
||
|
if (msg.author.id === bot.user.id) return;
|
||
|
|
||
|
blocked.isBlocked(msg.author.id).then(isBlocked => {
|
||
|
if (isBlocked) return;
|
||
|
|
||
|
// Download and save copies of attachments in the background
|
||
|
attachments.saveAttachments(msg);
|
||
|
|
||
|
let thread, logs;
|
||
|
|
||
|
messageQueue.add(() => {
|
||
|
threads.getForUser(bot, msg.author)
|
||
|
.then(userThread => {
|
||
|
thread = userThread;
|
||
|
return logs.getLogsByUserId(msg.author.id);
|
||
|
})
|
||
|
.then(userLogs => {
|
||
|
logs = userLogs;
|
||
|
return utils.formatUserDM(msg);
|
||
|
})
|
||
|
.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 (! thread) {
|
||
|
let warningMessage = `
|
||
|
@here Error creating modmail thread for ${msg.author.username}#${msg.author.discriminator} (${msg.author.id})!
|
||
|
|
||
|
Here's what their message contained:
|
||
|
\`\`\`${content}\`\`\`
|
||
|
`;
|
||
|
|
||
|
bot.createMessage(utils.getModmailGuild(bot).id, {
|
||
|
content: `@here Error creating modmail thread for ${msg.author.username}#${msg.author.discriminator} (${msg.author.id})!`,
|
||
|
disableEveryone: false,
|
||
|
});
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// If the thread was just created, do some extra stuff
|
||
|
if (thread._wasCreated) {
|
||
|
// Mention previous logs at the start of the thread
|
||
|
if (logs.length > 0) {
|
||
|
bot.createMessage(thread.channelId, `${logs.length} previous modmail logs with this user. Use !logs ${msg.author.id} for details.`);
|
||
|
}
|
||
|
|
||
|
// Ping mods of the new thread
|
||
|
let creationNotificationMessage = `New modmail thread: <#${channel.id}>`;
|
||
|
if (config.pingCreationNotification) creationNotificationMessage = `@here ${creationNotificationMessage}`;
|
||
|
|
||
|
bot.createMessage(utils.getModmailGuild(bot).id, {
|
||
|
content: creationNotificationMessage,
|
||
|
disableEveryone: false,
|
||
|
});
|
||
|
|
||
|
// 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) => {
|
||
|
bot.createMessage(modMailGuild.id, {
|
||
|
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();
|
||
|
bot.createMessage(channel.id, `[${timestamp}] « **${msg.author.username}#${msg.author.discriminator}:** ${content}`);
|
||
|
})
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
// Edits in DMs
|
||
|
bot.on('messageUpdate', (msg, oldMessage) => {
|
||
|
if (! (msg.channel instanceof Eris.PrivateChannel)) return;
|
||
|
if (msg.author.id === bot.user.id) return;
|
||
|
|
||
|
blocked.isBlocked(msg.author.id).then(isBlocked => {
|
||
|
if (isBlocked) return;
|
||
|
|
||
|
let oldContent = oldMessage.content;
|
||
|
const newContent = msg.content;
|
||
|
|
||
|
if (oldContent == null) oldContent = '*Unavailable due to bot restart*';
|
||
|
|
||
|
threads.getForUser(bot, msg.author).then(thread => {
|
||
|
if (! thread) return;
|
||
|
|
||
|
const editMessage = utils.disableLinkPreviews(`**The user edited their message:**\n**Before:** ${oldContent}\n**After:** ${newContent}`);
|
||
|
|
||
|
bot.createMessage(thread.channelId, editMessage);
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
// Mods can reply to modmail threads using !r or !reply
|
||
|
// These messages get relayed back to the DM thread between the bot and the user
|
||
|
bot.registerCommand('reply', (msg, args) => {
|
||
|
if (! msg.channel.guild) return;
|
||
|
if (msg.channel.guild.id !== utils.getModmailGuild(bot).id) return;
|
||
|
if (! msg.member.permission.has('manageRoles')) return;
|
||
|
|
||
|
threads.getByChannelId(msg.channel.id).then(thread => {
|
||
|
if (! thread) return;
|
||
|
|
||
|
attachments.saveAttachments(msg).then(() => {
|
||
|
bot.getDMChannel(thread.userId).then(dmChannel => {
|
||
|
const roleId = msg.member.roles[0];
|
||
|
const role = (roleId ? (modMailGuild.roles.get(roleId) || {}).name : '');
|
||
|
const roleStr = (role ? `(${role}) ` : '');
|
||
|
|
||
|
let argMsg = args.join(' ').trim();
|
||
|
let content = `**${roleStr}${msg.author.username}:** ${argMsg}`;
|
||
|
|
||
|
function sendMessage(file, attachmentUrl) {
|
||
|
dmChannel.createMessage(content, file).then(() => {
|
||
|
if (attachmentUrl) content += `\n\n**Attachment:** ${attachmentUrl}`;
|
||
|
|
||
|
const timestamp = getTimestamp();
|
||
|
msg.channel.createMessage(`[${timestamp}] » ${content}`);
|
||
|
}, (err) => {
|
||
|
if (err.resp && err.resp.statusCode === 403) {
|
||
|
msg.channel.createMessage(`Could not send reply; the user has likely blocked the bot`);
|
||
|
} else if (err.resp) {
|
||
|
msg.channel.createMessage(`Could not send reply; error code ${err.resp.statusCode}`);
|
||
|
} else {
|
||
|
msg.channel.createMessage(`Could not send reply: ${err.toString()}`);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
msg.delete();
|
||
|
};
|
||
|
|
||
|
// If the reply has an attachment, relay it as is
|
||
|
if (msg.attachments.length > 0) {
|
||
|
fs.readFile(attachments.getAttachmentPath(msg.attachments[0].id), (err, data) => {
|
||
|
const file = {file: data, name: msg.attachments[0].filename};
|
||
|
|
||
|
getAttachmentUrl(msg.attachments[0].id, msg.attachments[0].filename).then(attachmentUrl => {
|
||
|
sendMessage(file, attachmentUrl);
|
||
|
});
|
||
|
});
|
||
|
} else {
|
||
|
sendMessage();
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
bot.registerCommandAlias('r', 'reply');
|
||
|
|
||
|
bot.registerCommand('close', (msg, args) => {
|
||
|
if (! msg.channel.guild) return;
|
||
|
if (msg.channel.guild.id !== modMailGuild.id) return;
|
||
|
if (! msg.member.permission.has('manageRoles')) return;
|
||
|
|
||
|
threads.getByChannelId(msg.channel.id).then(thread => {
|
||
|
if (! thread) return;
|
||
|
|
||
|
msg.channel.createMessage('Saving logs and closing channel...');
|
||
|
msg.channel.getMessages(10000).then(messages => {
|
||
|
const log = messages.reverse().map(msg => {
|
||
|
const date = moment.utc(msg.timestamp, 'x').format('YYYY-MM-DD HH:mm:ss');
|
||
|
return `[${date}] ${msg.author.username}#${msg.author.discriminator}: ${msg.content}`;
|
||
|
}).join('\n') + '\n';
|
||
|
|
||
|
logs.getNewLogFile(thread.userId).then(logFilename => {
|
||
|
logs.saveLogFile(logFilename, log)
|
||
|
.then(() => getLogFileUrl(logFilename))
|
||
|
.then(url => {
|
||
|
const closeMessage = `Modmail thread with ${thread.username} (${thread.userId}) was closed by ${msg.author.mention}
|
||
|
Logs: <${url}>`;
|
||
|
|
||
|
bot.createMessage(utils.getModmailGuild(bot).id, closeMessage);
|
||
|
threads.close(thread.channelId).then(() => msg.channel.delete());
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
bot.registerCommand('block', (msg, args) => {
|
||
|
if (! msg.channel.guild) return;
|
||
|
if (msg.channel.guild.id !== utils.getModmailGuild(bot).id) return;
|
||
|
if (! msg.member.permission.has('manageRoles')) return;
|
||
|
|
||
|
function block(userId) {
|
||
|
blocked.block(userId).then(() => {
|
||
|
msg.channel.createMessage(`Blocked <@${userId}> (id ${userId}) from modmail`);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
if (args.length > 0) {
|
||
|
const userId = utils.getUserMention(args.join(' '));
|
||
|
if (! userId) return;
|
||
|
block(userId);
|
||
|
} else {
|
||
|
// Calling !block without args in a modmail thread blocks the user of that thread
|
||
|
threads.getByChannelId(msg.channel.id).then(thread => {
|
||
|
if (! thread) return;
|
||
|
block(userId);
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
bot.registerCommand('unblock', (msg, args) => {
|
||
|
if (! msg.channel.guild) return;
|
||
|
if (msg.channel.guild.id !== utils.getModmailGuild(bot).id) return;
|
||
|
if (! msg.member.permission.has('manageRoles')) return;
|
||
|
|
||
|
function unblock(userId) {
|
||
|
blocked.unblock(userId).then(() => {
|
||
|
msg.channel.createMessage(`Unblocked <@${userId}> (id ${userId}) from modmail`);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
if (args.length > 0) {
|
||
|
const userId = utils.getUserMention(args.join(' '));
|
||
|
if (! userId) return;
|
||
|
unblock(userId);
|
||
|
} else {
|
||
|
// Calling !unblock without args in a modmail thread unblocks the user of that thread
|
||
|
threads.getByChannelId(msg.channel.id).then(thread => {
|
||
|
if (! thread) return;
|
||
|
unblock(userId);
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
bot.registerCommand('logs', (msg, args) => {
|
||
|
if (! msg.channel.guild) return;
|
||
|
if (msg.channel.guild.id !== utils.getModmailGuild(bot).id) return;
|
||
|
if (! msg.member.permission.has('manageRoles')) return;
|
||
|
|
||
|
function getLogs(userId) {
|
||
|
getLogsWithUrlByUserId(userId).then(infos => {
|
||
|
let message = `**Log files for <@${userId}>:**\n`;
|
||
|
|
||
|
message += infos.map(info => {
|
||
|
const formattedDate = moment.utc(info.date, 'YYYY-MM-DD HH:mm:ss').format('MMM Do [at] HH:mm [UTC]');
|
||
|
return `\`${formattedDate}\`: <${info.url}>`;
|
||
|
}).join('\n');
|
||
|
|
||
|
msg.channel.createMessage(message);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
if (args.length > 0) {
|
||
|
const userId = utils.getUserMention(args.join(' '));
|
||
|
if (! userId) return;
|
||
|
getLogs(userId);
|
||
|
} else {
|
||
|
// Calling !logs without args in a modmail thread returns the logs of the user of that thread
|
||
|
threads.getByChannelId(msg.channel.id).then(thread => {
|
||
|
if (! thread) return;
|
||
|
getLogs(userId);
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
bot.connect();
|
||
|
webserver.run();
|