2017-12-24 15:04:08 -05:00
const Eris = require ( 'eris' ) ;
2019-06-09 10:53:49 -04:00
const path = require ( 'path' ) ;
2017-12-24 15:04:08 -05:00
2020-07-19 06:35:54 -04:00
const config = require ( './cfg' ) ;
2017-12-24 15:04:08 -05:00
const bot = require ( './bot' ) ;
2019-06-09 10:53:49 -04:00
const knex = require ( './knex' ) ;
2018-09-20 16:31:14 -04:00
const { messageQueue } = require ( './queue' ) ;
2017-12-24 15:04:08 -05:00
const utils = require ( './utils' ) ;
2019-06-16 15:27:30 -04:00
const { createCommandManager } = require ( './commands' ) ;
2019-09-17 19:15:22 -04:00
const { getPluginAPI , loadPlugin } = require ( './plugins' ) ;
2020-07-15 16:50:30 -04:00
const { callBeforeNewThreadHooks } = require ( './hooks/beforeNewThread' ) ;
2019-06-16 15:27:30 -04:00
2017-12-24 15:04:08 -05:00
const blocked = require ( './data/blocked' ) ;
const threads = require ( './data/threads' ) ;
2019-06-09 10:31:17 -04:00
const updates = require ( './data/updates' ) ;
2017-12-24 15:04:08 -05:00
2018-03-13 01:45:31 -04:00
const reply = require ( './modules/reply' ) ;
const close = require ( './modules/close' ) ;
const snippets = require ( './modules/snippets' ) ;
const logs = require ( './modules/logs' ) ;
const move = require ( './modules/move' ) ;
const block = require ( './modules/block' ) ;
const suspend = require ( './modules/suspend' ) ;
const webserver = require ( './modules/webserver' ) ;
const greeting = require ( './modules/greeting' ) ;
const typingProxy = require ( './modules/typingProxy' ) ;
2018-03-13 01:59:27 -04:00
const version = require ( './modules/version' ) ;
2018-04-07 19:56:30 -04:00
const newthread = require ( './modules/newthread' ) ;
2018-04-21 08:41:03 -04:00
const idModule = require ( './modules/id' ) ;
2018-04-21 09:39:38 -04:00
const alert = require ( './modules/alert' ) ;
2018-04-07 19:56:30 -04:00
2018-02-18 15:52:37 -05:00
const { ACCIDENTAL _THREAD _MESSAGES } = require ( './data/constants' ) ;
2017-12-31 19:16:05 -05:00
2019-08-13 12:58:05 -04:00
module . exports = {
async start ( ) {
console . log ( 'Connecting to Discord...' ) ;
bot . once ( 'ready' , async ( ) => {
2019-12-02 19:00:00 -05:00
console . log ( 'Connected! Waiting for guilds to become available...' ) ;
2019-08-13 12:58:05 -04:00
await Promise . all ( [
... config . mainGuildId . map ( id => waitForGuild ( id ) ) ,
waitForGuild ( config . mailGuildId )
] ) ;
2019-12-02 19:00:00 -05:00
console . log ( 'Initializing...' ) ;
2019-08-13 12:58:05 -04:00
initStatus ( ) ;
initBaseMessageHandlers ( ) ;
2020-07-13 17:17:31 -04:00
console . log ( 'Loading plugins...' ) ;
const pluginResult = await initPlugins ( ) ;
console . log ( ` Loaded ${ pluginResult . loadedCount } plugins ( ${ pluginResult . builtInCount } built-in plugins, ${ pluginResult . externalCount } external plugins) ` ) ;
2019-08-13 12:58:05 -04:00
2019-12-02 19:00:00 -05:00
console . log ( '' ) ;
2019-08-13 12:58:05 -04:00
console . log ( 'Done! Now listening to DMs.' ) ;
2019-12-02 19:00:00 -05:00
console . log ( '' ) ;
2019-08-13 12:58:05 -04:00
} ) ;
bot . connect ( ) ;
2017-12-31 19:16:05 -05:00
}
2019-08-13 12:58:05 -04:00
} ;
2018-02-18 15:52:37 -05:00
2019-08-13 12:58:05 -04:00
function waitForGuild ( guildId ) {
if ( bot . guilds . has ( guildId ) ) {
return Promise . resolve ( ) ;
}
return new Promise ( resolve => {
bot . on ( 'guildAvailable' , guild => {
if ( guild . id === guildId ) {
resolve ( ) ;
}
} ) ;
2017-12-24 15:04:08 -05:00
} ) ;
2019-08-13 12:58:05 -04:00
}
function initStatus ( ) {
2019-08-13 12:59:33 -04:00
function applyStatus ( ) {
bot . editStatus ( null , { name : config . status } ) ;
}
// Set the bot status initially, then reapply it every hour since in some cases it gets unset
applyStatus ( ) ;
setInterval ( applyStatus , 60 * 60 * 1000 ) ;
2019-08-13 12:58:05 -04:00
}
function initBaseMessageHandlers ( ) {
/ * *
* When a moderator posts in a modmail thread ...
* 1 ) If alwaysReply is enabled , reply to the user
* 2 ) If alwaysReply is disabled , save that message as a chat message in the thread
* /
bot . on ( 'messageCreate' , async msg => {
if ( ! utils . messageIsOnInboxServer ( msg ) ) return ;
if ( msg . author . bot ) return ;
const thread = await threads . findByChannelId ( msg . channel . id ) ;
2018-05-03 07:22:38 -04:00
if ( ! thread ) return ;
2019-06-09 08:56:04 -04:00
2019-08-13 12:58:05 -04:00
if ( msg . content . startsWith ( config . prefix ) || msg . content . startsWith ( config . snippetPrefix ) ) {
// Save commands as "command messages"
2020-05-24 18:33:10 -04:00
thread . saveCommandMessageToLogs ( msg ) ;
2019-08-13 12:58:05 -04:00
} else if ( config . alwaysReply ) {
// AUTO-REPLY: If config.alwaysReply is enabled, send all chat messages in thread channels as replies
if ( ! utils . isStaff ( msg . member ) ) return ; // Only staff are allowed to reply
const replied = await thread . replyToUser ( msg . member , msg . content . trim ( ) , msg . attachments , config . alwaysReplyAnon || false ) ;
if ( replied ) msg . delete ( ) ;
} else {
// Otherwise just save the messages as "chat" in the logs
2020-05-24 18:33:10 -04:00
thread . saveChatMessageToLogs ( msg ) ;
2019-08-13 12:58:05 -04:00
}
} ) ;
2018-02-11 14:54:30 -05:00
2019-08-13 12:58:05 -04:00
/ * *
* When we get a private message ...
* 1 ) Find the open modmail thread for this user , or create a new one
* 2 ) Post the message as a user reply in the thread
* /
bot . on ( 'messageCreate' , async msg => {
if ( ! ( msg . channel instanceof Eris . PrivateChannel ) ) return ;
if ( msg . author . bot ) return ;
if ( msg . type !== 0 ) return ; // Ignore pins etc.
2018-02-11 14:54:30 -05:00
2019-08-13 12:58:05 -04:00
if ( await blocked . isBlocked ( msg . author . id ) ) return ;
2018-02-11 14:54:30 -05:00
2019-08-13 12:58:05 -04:00
// Private message handling is queued so e.g. multiple message in quick succession don't result in multiple channels being created
messageQueue . add ( async ( ) => {
let thread = await threads . findOpenThreadByUserId ( msg . author . id ) ;
2018-02-11 14:54:30 -05:00
2019-08-13 12:58:05 -04:00
// New thread
if ( ! thread ) {
// Ignore messages that shouldn't usually open new threads, such as "ok", "thanks", etc.
if ( config . ignoreAccidentalThreads && msg . content && ACCIDENTAL _THREAD _MESSAGES . includes ( msg . content . trim ( ) . toLowerCase ( ) ) ) return ;
2018-04-21 08:54:41 -04:00
2020-07-15 16:50:30 -04:00
thread = await threads . createNewThreadForUser ( msg . author , {
source : 'dm' ,
} ) ;
2019-08-13 12:58:05 -04:00
}
2018-04-21 08:54:41 -04:00
2020-05-24 18:33:10 -04:00
if ( thread ) {
await thread . receiveUserReply ( msg ) ;
}
2019-08-13 12:58:05 -04:00
} ) ;
2018-02-11 14:54:30 -05:00
} ) ;
2018-09-20 15:07:38 -04:00
2019-08-13 12:58:05 -04:00
/ * *
* When a message is edited ...
* 1 ) If that message was in DMs , and we have a thread open with that user , post the edit as a system message in the thread
* 2 ) If that message was moderator chatter in the thread , update the corresponding chat message in the DB
* /
bot . on ( 'messageUpdate' , async ( msg , oldMessage ) => {
if ( ! msg || ! msg . author ) return ;
if ( msg . author . bot ) return ;
if ( await blocked . isBlocked ( msg . author . id ) ) return ;
// Old message content doesn't persist between bot restarts
const oldContent = oldMessage && oldMessage . content || '*Unavailable due to bot restart*' ;
const newContent = msg . content ;
2020-05-24 18:33:10 -04:00
// Ignore edit events with changes only in embeds etc.
2019-08-13 12:58:05 -04:00
if ( newContent . trim ( ) === oldContent . trim ( ) ) return ;
2020-05-24 18:33:10 -04:00
// 1) If this edit was in DMs
2019-08-13 12:58:05 -04:00
if ( msg . channel instanceof Eris . PrivateChannel ) {
const thread = await threads . findOpenThreadByUserId ( msg . author . id ) ;
if ( ! thread ) return ;
const editMessage = utils . disableLinkPreviews ( ` **The user edited their message:** \n \` B: \` ${ oldContent } \n \` A: \` ${ newContent } ` ) ;
thread . postSystemMessage ( editMessage ) ;
}
2017-12-24 15:04:08 -05:00
2020-05-24 18:33:10 -04:00
// 2) If this edit was a chat message in the thread
2019-08-13 12:58:05 -04:00
else if ( utils . messageIsOnInboxServer ( msg ) && utils . isStaff ( msg . member ) ) {
const thread = await threads . findOpenThreadByChannelId ( msg . channel . id ) ;
if ( ! thread ) return ;
2019-06-16 15:27:30 -04:00
2020-05-24 18:33:10 -04:00
thread . updateChatMessageInLogs ( msg ) ;
2019-06-16 15:27:30 -04:00
}
2019-08-13 12:58:05 -04:00
} ) ;
2019-06-16 15:27:30 -04:00
2019-08-13 12:58:05 -04:00
/ * *
* When a staff message is deleted in a modmail thread , delete it from the database as well
* /
bot . on ( 'messageDelete' , async msg => {
if ( ! msg . author ) return ;
if ( msg . author . bot ) return ;
if ( ! utils . messageIsOnInboxServer ( msg ) ) return ;
if ( ! utils . isStaff ( msg . member ) ) return ;
const thread = await threads . findOpenThreadByChannelId ( msg . channel . id ) ;
if ( ! thread ) return ;
2020-05-24 18:33:10 -04:00
thread . deleteChatMessageFromLogs ( msg . id ) ;
2019-08-13 12:58:05 -04:00
} ) ;
/ * *
* When the bot is mentioned on the main server , ping staff in the log channel about it
* /
bot . on ( 'messageCreate' , async msg => {
if ( ! utils . messageIsOnMainServer ( msg ) ) return ;
if ( ! msg . mentions . some ( user => user . id === bot . user . id ) ) return ;
if ( msg . author . bot ) return ;
if ( utils . messageIsOnInboxServer ( msg ) ) {
// For same server setups, check if the person who pinged modmail is staff. If so, ignore the ping.
if ( utils . isStaff ( msg . member ) ) return ;
} else {
// For separate server setups, check if the member is staff on the modmail server
const inboxMember = utils . getInboxGuild ( ) . members . get ( msg . author . id ) ;
if ( inboxMember && utils . isStaff ( inboxMember ) ) return ;
}
// If the person who mentioned the bot is blocked, ignore them
if ( await blocked . isBlocked ( msg . author . id ) ) return ;
let content ;
const mainGuilds = utils . getMainGuilds ( ) ;
const staffMention = ( config . pingOnBotMention ? utils . getInboxMention ( ) : '' ) ;
if ( mainGuilds . length === 1 ) {
2020-01-19 14:31:29 -05:00
content = ` ${ staffMention } Bot mentioned in ${ msg . channel . mention } by ** ${ msg . author . username } # ${ msg . author . discriminator } ( ${ msg . author . id } )**: " ${ msg . cleanContent } " \n \n <https: \/ \/ discordapp.com \/ channels \/ ${ msg . channel . guild . id } \/ ${ msg . channel . id } \/ ${ msg . id } > ` ;
2019-08-13 12:58:05 -04:00
} else {
2020-01-19 14:31:29 -05:00
content = ` ${ staffMention } Bot mentioned in ${ msg . channel . mention } ( ${ msg . channel . guild . name } ) by ** ${ msg . author . username } # ${ msg . author . discriminator } ( ${ msg . author . id } )**: " ${ msg . cleanContent } " \n \n <https: \/ \/ discordapp.com \/ channels \/ ${ msg . channel . guild . id } \/ ${ msg . channel . id } \/ ${ msg . id } > ` ;
2019-06-09 10:53:49 -04:00
}
2020-01-19 14:31:29 -05:00
2019-08-13 12:58:05 -04:00
bot . createMessage ( utils . getLogChannel ( ) . id , {
content ,
disableEveryone : false ,
2019-06-16 15:27:30 -04:00
} ) ;
2019-08-13 12:58:05 -04:00
// Send an auto-response to the mention, if enabled
if ( config . botMentionResponse ) {
2020-01-19 14:25:10 -05:00
const botMentionResponse = utils . readMultilineConfigValue ( config . botMentionResponse ) ;
bot . createMessage ( msg . channel . id , botMentionResponse . replace ( /{userMention}/g , ` <@ ${ msg . author . id } > ` ) ) ;
2019-08-13 12:58:05 -04:00
}
} ) ;
}
2019-06-16 15:27:30 -04:00
2020-07-13 17:17:31 -04:00
async function initPlugins ( ) {
2019-08-13 12:58:05 -04:00
// Initialize command manager
const commands = createCommandManager ( bot ) ;
// Register command aliases
if ( config . commandAliases ) {
for ( const alias in config . commandAliases ) {
commands . addAlias ( config . commandAliases [ alias ] , alias ) ;
2019-06-09 10:53:49 -04:00
}
2019-08-13 12:58:05 -04:00
}
2019-06-09 10:31:17 -04:00
2019-08-13 12:58:05 -04:00
// Load plugins
const builtInPlugins = [
reply ,
close ,
logs ,
block ,
move ,
snippets ,
suspend ,
greeting ,
webserver ,
typingProxy ,
version ,
newthread ,
idModule ,
alert
] ;
const plugins = [ ... builtInPlugins ] ;
if ( config . plugins && config . plugins . length ) {
for ( const plugin of config . plugins ) {
const pluginFn = require ( ` ../ ${ plugin } ` ) ;
plugins . push ( pluginFn ) ;
}
2017-12-24 15:04:08 -05:00
}
2019-08-13 12:58:05 -04:00
2019-09-17 19:15:22 -04:00
const pluginApi = getPluginAPI ( { bot , knex , config , commands } ) ;
2020-07-13 17:17:31 -04:00
for ( const plugin of plugins ) {
await loadPlugin ( plugin , pluginApi ) ;
}
2019-08-13 12:58:05 -04:00
if ( config . updateNotifications ) {
updates . startVersionRefreshLoop ( ) ;
}
2020-07-13 17:17:31 -04:00
return {
loadedCount : plugins . length ,
builtInCount : builtInPlugins . length ,
externalCount : plugins . length - builtInPlugins . length ,
} ;
2019-08-13 12:58:05 -04:00
}