ramirez/src/main.js

322 lines
11 KiB
JavaScript
Raw Normal View History

const Eris = require('eris');
const moment = require('moment');
const config = require('./config');
const bot = require('./bot');
const Queue = require('./queue');
const utils = require('./utils');
const blocked = require('./data/blocked');
const threads = require('./data/threads');
const snippets = require('./data/snippets');
const webserver = require('./webserver');
const greeting = require('./greeting');
2017-12-31 19:16:05 -05:00
const Thread = require('./data/Thread');
const messageQueue = new Queue();
2017-12-31 19:16:05 -05:00
/**
* @callback CommandHandlerCB
* @interface
* @param {Eris~Message} msg
* @param {Array} args
* @param {Thread} thread
* @return void
*/
/**
* Adds a command that can only be triggered on the inbox server.
* Command handlers added with this function also get the thread the message was posted in as a third argument, if any.
* @param {String} cmd
* @param {CommandHandlerCB} commandHandler
* @param {Eris~CommandOptions} opts
*/
function addInboxServerCommand(cmd, commandHandler, opts) {
bot.registerCommand(cmd, async (msg, args) => {
if (! messageIsOnInboxServer(msg)) return;
if (! isStaff(msg.member)) return;
const thread = await threads.findByChannelId(msg.channel.id);
commandHandler(msg, args, thread);
}, opts);
}
// Once the bot has connected, set the status/"playing" message
bot.on('ready', () => {
bot.editStatus(null, {name: config.status});
console.log('Bot started, listening to DMs');
});
2017-12-31 19:16:05 -05:00
// Handle moderator messages in thread channels
bot.on('messageCreate', async msg => {
if (! utils.messageIsOnInboxServer(msg)) return;
if (! utils.isStaff(msg)) return;
if (msg.author.bot) return;
if (msg.content.startsWith(config.prefix) || msg.content.startsWith(config.snippetPrefix)) return;
const thread = await threads.findByChannelId(msg.channel.id);
if (! thread) return;
2017-12-31 19:16:05 -05:00
if (config.alwaysReply) {
// AUTO-REPLY: If config.alwaysReply is enabled, send all chat messages in thread channels as replies
await thread.replyToUser(msg.member, msg.content.trim(), msg.attachments, config.alwaysReplyAnon || false);
msg.delete();
} else {
// Otherwise just save the messages as "chat" in the logs
thread.addThreadMessageToDB({
message_type: threads.THREAD_MESSAGE_TYPE.CHAT,
user_id: msg.author.id,
user_name: `${msg.author.username}#${msg.author.discriminator}`,
body: msg.content,
original_message_id: msg.id
});
}
});
2017-12-31 19:16:05 -05:00
// If the bot is mentioned on the main server, post a log message about it
bot.on('messageCreate', async msg => {
if (! utils.messageIsOnMainServer(msg)) return;
if (! msg.mentions.some(user => user.id === bot.user.id)) return;
// If the person who mentioned the modmail bot is also on the modmail server, ignore them
if (utils.getInboxGuild().members.get(msg.author.id)) return;
// If the person who mentioned the bot is blocked, ignore them
if (await blocked.isBlocked(msg.author.id)) return;
bot.createMessage(utils.getLogChannel(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', async msg => {
if (! (msg.channel instanceof Eris.PrivateChannel)) return;
if (msg.author.id === bot.user.id) return;
if (await blocked.isBlocked(msg.author.id)) return;
// Private message handling is queued so e.g. multiple message in quick succession don't result in multiple channels being created
messageQueue.add(async () => {
2017-12-31 19:16:05 -05:00
let thread = await threads.findOpenThreadByUserId(msg.author.id);
if (! thread) {
2017-12-31 19:16:05 -05:00
thread = await threads.createNewThreadForUser(msg.author, msg);
}
2017-12-31 19:16:05 -05:00
thread.receiveUserReply(msg);
});
});
// Edits in DMs
bot.on('messageUpdate', async (msg, oldMessage) => {
if (! (msg.channel instanceof Eris.PrivateChannel)) return;
if (msg.author.id === bot.user.id) return;
if (await blocked.isBlocked(msg.author.id)) return;
let oldContent = oldMessage.content;
const newContent = msg.content;
// Old message content doesn't persist between bot restarts
if (oldContent == null) oldContent = '*Unavailable due to bot restart*';
// Ignore bogus edit events with no changes
if (newContent.trim() === oldContent.trim()) return;
2017-12-31 19:16:05 -05:00
const thread = await threads.createNewThreadForUser(msg.author);
if (! thread) return;
const editMessage = utils.disableLinkPreviews(`**The user edited their message:**\n\`B:\` ${oldContent}\n\`A:\` ${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
2017-12-31 19:16:05 -05:00
addInboxServerCommand('reply', (msg, args, thread) => {
if (! thread) return;
const text = args.join(' ').trim();
2017-12-31 19:16:05 -05:00
thread.replyToUser(msg.member, text, msg.attachments, false);
});
bot.registerCommandAlias('r', 'reply');
// Anonymous replies only show the role, not the username
2017-12-31 19:16:05 -05:00
addInboxServerCommand('anonreply', (msg, args, thread) => {
if (! thread) return;
const text = args.join(' ').trim();
2017-12-31 19:16:05 -05:00
thread.replyToUser(msg.member, text, msg.attachments, true);
});
bot.registerCommandAlias('ar', 'anonreply');
// Close a thread. Closing a thread saves a log of the channel's contents and then deletes the channel.
2017-12-31 19:16:05 -05:00
addInboxServerCommand('close', async (msg, args, thread) => {
if (! thread) return;
2017-12-31 19:16:05 -05:00
thread.close();
});
2017-12-31 19:16:05 -05:00
addInboxServerCommand('block', (msg, args, thread) => {
async function block(userId) {
await blocked.block(userId);
msg.channel.createMessage(`Blocked <@${userId}> (id ${userId}) from modmail`);
}
if (args.length > 0) {
// User mention/id as argument
const userId = utils.getUserMention(args.join(' '));
if (! userId) return;
block(userId);
} else if (thread) {
// Calling !block without args in a modmail thread blocks the user of that thread
block(thread.userId);
}
});
2017-12-31 19:16:05 -05:00
addInboxServerCommand('unblock', (msg, args, thread) => {
async function unblock(userId) {
await blocked.unblock(userId);
msg.channel.createMessage(`Unblocked <@${userId}> (id ${userId}) from modmail`);
}
if (args.length > 0) {
// User mention/id as argument
const userId = utils.getUserMention(args.join(' '));
if (! userId) return;
unblock(userId);
} else if (thread) {
// Calling !unblock without args in a modmail thread unblocks the user of that thread
unblock(thread.userId);
}
});
2017-12-31 19:16:05 -05:00
addInboxServerCommand('logs', (msg, args, thread) => {
async function getLogs(userId) {
2017-12-31 19:16:05 -05:00
const userThreads = await threads.getClosedThreadsByUserId(userId);
const threadLines = await Promise.all(userThreads.map(async thread => {
const logUrl = await thread.getLogUrl();
const formattedDate = moment.utc(thread.created_at).format('MMM Do [at] HH:mm [UTC]');
return `\`${formattedDate}\`: <${logUrl}>`;
}));
2017-12-31 19:16:05 -05:00
const message = `**Log files for <@${userId}>:**\n${threadLines.join('\n')}`;
// Send the list of logs in chunks of 15 lines per message
const lines = message.split('\n');
const chunks = utils.chunk(lines, 15);
let root = Promise.resolve();
chunks.forEach(lines => {
root = root.then(() => msg.channel.createMessage(lines.join('\n')));
});
}
if (args.length > 0) {
// User mention/id as argument
const userId = utils.getUserMention(args.join(' '));
if (! userId) return;
getLogs(userId);
} else if (thread) {
// Calling !logs without args in a modmail thread returns the logs of the user of that thread
getLogs(thread.userId);
}
});
// Snippets
bot.on('messageCreate', async msg => {
if (! utils.messageIsOnInboxServer(msg)) return;
if (! utils.isStaff(msg.member)) return;
if (msg.author.bot) return;
if (! msg.content) return;
if (! msg.content.startsWith(config.snippetPrefix)) return;
const shortcut = msg.content.replace(config.snippetPrefix, '').toLowerCase();
const snippet = await snippets.get(shortcut);
if (! snippet) return;
reply(msg, snippet.text, snippet.isAnonymous);
});
// Show or add a snippet
2017-12-31 19:16:05 -05:00
addInboxServerCommand('snippet', async (msg, args) => {
const shortcut = args[0];
if (! shortcut) return
const text = args.slice(1).join(' ').trim();
const snippet = await snippets.get(shortcut);
if (snippet) {
if (text) {
// If the snippet exists and we're trying to create a new one, inform the user the snippet already exists
msg.channel.createMessage(`Snippet "${shortcut}" already exists! You can edit or delete it with ${prefix}edit_snippet and ${prefix}delete_snippet respectively.`);
} else {
// If the snippet exists and we're NOT trying to create a new one, show info about the existing snippet
msg.channel.createMessage(`\`${config.snippetPrefix}${shortcut}\` replies ${snippet.isAnonymous ? 'anonymously ' : ''}with:\n${snippet.text}`);
}
} else {
if (text) {
// If the snippet doesn't exist and the user wants to create it, create it
await snippets.add(shortcut, text, false);
msg.channel.createMessage(`Snippet "${shortcut}" created!`);
} else {
// If the snippet doesn't exist and the user isn't trying to create it, inform them how to create it
msg.channel.createMessage(`Snippet "${shortcut}" doesn't exist! You can create it with \`${prefix}snippet ${shortcut} text\``);
}
}
});
bot.registerCommandAlias('s', 'snippet');
2017-12-31 19:16:05 -05:00
addInboxServerCommand('delete_snippet', async (msg, args) => {
const shortcut = args[0];
if (! shortcut) return;
const snippet = await snippets.get(shortcut);
if (! snippet) {
msg.channel.createMessage(`Snippet "${shortcut}" doesn't exist!`);
return;
}
await snippets.del(shortcut);
msg.channel.createMessage(`Snippet "${shortcut}" deleted!`);
});
bot.registerCommandAlias('ds', 'delete_snippet');
2017-12-31 19:16:05 -05:00
addInboxServerCommand('edit_snippet', async (msg, args) => {
const shortcut = args[0];
if (! shortcut) return;
const text = args.slice(1).join(' ').trim();
if (! text) return;
const snippet = await snippets.get(shortcut);
if (! snippet) {
msg.channel.createMessage(`Snippet "${shortcut}" doesn't exist!`);
return;
}
await snippets.del(shortcut);
await snippets.add(shortcut, text, snippet.isAnonymous);
msg.channel.createMessage(`Snippet "${shortcut}" edited!`);
});
bot.registerCommandAlias('es', 'edit_snippet');
2017-12-31 19:16:05 -05:00
addInboxServerCommand('snippets', async msg => {
const allSnippets = await snippets.all();
const shortcuts = Object.keys(allSnippets);
shortcuts.sort();
msg.channel.createMessage(`Available snippets (prefix ${config.snippetPrefix}):\n${shortcuts.join(', ')}`);
});
module.exports = {
start() {
bot.connect();
2017-12-31 19:16:05 -05:00
// webserver.run();
greeting.init(bot);
}
};