2020-08-12 17:08:37 -04:00
const Eris = require ( "eris" ) ;
const path = require ( "path" ) ;
const config = require ( "./cfg" ) ;
const bot = require ( "./bot" ) ;
const knex = require ( "./knex" ) ;
const { messageQueue } = require ( "./queue" ) ;
const utils = require ( "./utils" ) ;
const { createCommandManager } = require ( "./commands" ) ;
const { getPluginAPI , loadPlugin } = require ( "./plugins" ) ;
const { callBeforeNewThreadHooks } = require ( "./hooks/beforeNewThread" ) ;
const blocked = require ( "./data/blocked" ) ;
const threads = require ( "./data/threads" ) ;
const updates = require ( "./data/updates" ) ;
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" ) ;
const version = require ( "./modules/version" ) ;
const newthread = require ( "./modules/newthread" ) ;
const idModule = require ( "./modules/id" ) ;
const alert = require ( "./modules/alert" ) ;
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 ( ) {
2020-08-12 17:08:37 -04:00
console . log ( "Connecting to Discord..." ) ;
2019-08-13 12:58:05 -04:00
2020-08-12 17:08:37 -04:00
bot . once ( "ready" , async ( ) => {
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 )
] ) ;
2020-08-12 17:08:37 -04:00
console . log ( "Initializing..." ) ;
2019-08-13 12:58:05 -04:00
initStatus ( ) ;
initBaseMessageHandlers ( ) ;
2020-07-13 17:17:31 -04:00
2020-08-12 17:08:37 -04:00
console . log ( "Loading plugins..." ) ;
2020-07-13 17:17:31 -04:00
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
2020-08-12 17:08:37 -04:00
console . log ( "" ) ;
console . log ( "Done! Now listening to DMs." ) ;
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 => {
2020-08-12 17:08:37 -04:00
bot . on ( "guildAvailable" , guild => {
2019-08-13 12:58:05 -04:00
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
* /
2020-08-12 17:08:37 -04:00
bot . on ( "messageCreate" , async msg => {
2019-08-13 12:58:05 -04:00
if ( ! utils . messageIsOnInboxServer ( msg ) ) return ;
2019-09-28 21:59:09 -04:00
if ( msg . author . id === bot . user . id ) return ;
2019-08-13 12:58:05 -04:00
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-09-28 21:59:09 -04:00
if ( ! msg . author . bot && ( msg . content . startsWith ( config . prefix ) || msg . content . startsWith ( config . snippetPrefix ) ) ) {
2019-08-13 12:58:05 -04:00
// Save commands as "command messages"
2020-05-24 18:33:10 -04:00
thread . saveCommandMessageToLogs ( msg ) ;
2019-09-28 21:59:09 -04:00
} else if ( ! msg . author . bot && config . alwaysReply ) {
2019-08-13 12:58:05 -04:00
// 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
* /
2020-08-12 17:08:37 -04:00
bot . on ( "messageCreate" , async msg => {
2019-08-13 12:58:05 -04:00
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 , {
2020-08-12 17:08:37 -04:00
source : "dm" ,
2020-07-15 16:50:30 -04:00
} ) ;
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
* /
2020-08-12 17:08:37 -04:00
bot . on ( "messageUpdate" , async ( msg , oldMessage ) => {
2019-08-13 12:58:05 -04:00
if ( ! msg || ! msg . author ) return ;
2019-09-28 21:59:09 -04:00
if ( msg . author . id === bot . user . id ) return ;
2019-08-13 12:58:05 -04:00
if ( await blocked . isBlocked ( msg . author . id ) ) return ;
// Old message content doesn't persist between bot restarts
2020-08-12 17:08:37 -04:00
const oldContent = oldMessage && oldMessage . content || "*Unavailable due to bot restart*" ;
2019-08-13 12:58:05 -04:00
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-09-28 21:59:09 -04:00
if ( ! msg . author . bot && msg . channel instanceof Eris . PrivateChannel ) {
2019-08-13 12:58:05 -04:00
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-09-28 21:59:09 -04:00
else if ( utils . messageIsOnInboxServer ( msg ) && ( msg . author . bot || utils . isStaff ( msg . member ) ) ) {
2019-08-13 12:58:05 -04:00
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
* /
2020-08-12 17:08:37 -04:00
bot . on ( "messageDelete" , async msg => {
2019-08-13 12:58:05 -04:00
if ( ! msg . author ) return ;
2019-09-28 21:59:09 -04:00
if ( msg . author . id === bot . user . id ) return ;
2019-08-13 12:58:05 -04:00
if ( ! utils . messageIsOnInboxServer ( msg ) ) return ;
2019-09-28 21:59:09 -04:00
if ( ! msg . author . bot && ! utils . isStaff ( msg . member ) ) return ;
2019-08-13 12:58:05 -04:00
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
* /
2020-08-12 17:08:37 -04:00
bot . on ( "messageCreate" , async msg => {
2019-08-13 12:58:05 -04:00
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 ( ) ;
2020-08-12 17:08:37 -04:00
const staffMention = ( config . pingOnBotMention ? utils . getInboxMention ( ) : "" ) ;
2019-08-13 12:58:05 -04:00
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
}
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
}
2020-06-04 23:27:01 -04:00
// If configured, automatically open a new thread with a user who has pinged it
2020-08-16 11:36:51 -04:00
if ( config . createThreadOnMention ) {
2020-08-16 11:34:51 -04:00
const existingThread = await threads . findOpenThreadByUserId ( msg . author . id ) ;
if ( ! existingThread ) {
// Only open a thread if we don't already have one
2020-08-16 11:41:58 -04:00
const createdThread = await threads . createNewThreadForUser ( msg . author , { quiet : true } ) ;
await createdThread . postSystemMessage ( ` This thread was opened from a bot mention in <# ${ msg . channel . id } > ` ) ;
await createdThread . receiveUserReply ( msg ) ;
2020-08-16 11:34:51 -04:00
}
2020-06-04 23:27:01 -04:00
}
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
}