2017-12-24 15:04:08 -05:00
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' ) ;
2017-12-24 15:04:08 -05:00
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 ) ;
}
2017-12-24 15:04:08 -05:00
// 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-24 15:04:08 -05:00
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-24 15:04:08 -05:00
2017-12-31 19:16:05 -05:00
// If the bot is mentioned on the main server, post a log message about it
2017-12-24 15:04:08 -05:00
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 ) ;
2017-12-24 15:04:08 -05:00
if ( ! thread ) {
2017-12-31 19:16:05 -05:00
thread = await threads . createNewThreadForUser ( msg . author , msg ) ;
2017-12-24 15:04:08 -05:00
}
2017-12-31 19:16:05 -05:00
thread . receiveUserReply ( msg ) ;
2017-12-24 15:04:08 -05:00
} ) ;
} ) ;
// 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 ) ;
2017-12-24 15:04:08 -05:00
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 ;
2017-12-24 15:04:08 -05:00
const text = args . join ( ' ' ) . trim ( ) ;
2017-12-31 19:16:05 -05:00
thread . replyToUser ( msg . member , text , msg . attachments , false ) ;
2017-12-24 15:04:08 -05:00
} ) ;
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 ;
2017-12-24 15:04:08 -05:00
const text = args . join ( ' ' ) . trim ( ) ;
2017-12-31 19:16:05 -05:00
thread . replyToUser ( msg . member , text , msg . attachments , true ) ;
2017-12-24 15:04:08 -05:00
} ) ;
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 ) => {
2017-12-24 15:04:08 -05:00
if ( ! thread ) return ;
2017-12-31 19:16:05 -05:00
thread . close ( ) ;
2017-12-24 15:04:08 -05:00
} ) ;
2017-12-31 19:16:05 -05:00
addInboxServerCommand ( 'block' , ( msg , args , thread ) => {
2017-12-24 15:04:08 -05:00
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 ) => {
2017-12-24 15:04:08 -05:00
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 ) => {
2017-12-24 15:04:08 -05:00
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-24 15:04:08 -05:00
2017-12-31 19:16:05 -05:00
const message = ` **Log files for <@ ${ userId } >:** \n ${ threadLines . join ( '\n' ) } ` ;
2017-12-24 15:04:08 -05:00
// 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 ) => {
2017-12-24 15:04:08 -05:00
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 ) => {
2017-12-24 15:04:08 -05:00
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 ) => {
2017-12-24 15:04:08 -05:00
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 => {
2017-12-24 15:04:08 -05:00
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();
2017-12-24 15:04:08 -05:00
greeting . init ( bot ) ;
}
} ;