2017-12-24 15:04:08 -05:00
const Eris = require ( 'eris' ) ;
const transliterate = require ( 'transliteration' ) ;
const moment = require ( 'moment' ) ;
const uuid = require ( 'uuid' ) ;
2017-12-31 19:16:05 -05:00
const humanizeDuration = require ( 'humanize-duration' ) ;
2017-12-24 15:04:08 -05:00
const bot = require ( '../bot' ) ;
const knex = require ( '../knex' ) ;
const config = require ( '../config' ) ;
2017-12-31 19:16:05 -05:00
const utils = require ( '../utils' ) ;
2017-12-24 15:04:08 -05:00
2017-12-31 19:16:05 -05:00
const Thread = require ( './Thread' ) ;
const { THREAD _STATUS } = require ( './constants' ) ;
2017-12-24 15:04:08 -05:00
2018-02-14 01:53:34 -05:00
/ * *
* @ param { String } id
* @ returns { Promise < Thread > }
* /
2018-02-11 14:54:30 -05:00
async function findById ( id ) {
const thread = await knex ( 'threads' )
. where ( 'id' , id )
. first ( ) ;
return ( thread ? new Thread ( thread ) : null ) ;
}
2017-12-24 15:04:08 -05:00
/ * *
2017-12-31 19:16:05 -05:00
* @ param { String } userId
2017-12-24 15:04:08 -05:00
* @ returns { Promise < Thread > }
* /
2017-12-31 19:16:05 -05:00
async function findOpenThreadByUserId ( userId ) {
2017-12-24 15:04:08 -05:00
const thread = await knex ( 'threads' )
2017-12-31 19:16:05 -05:00
. where ( 'user_id' , userId )
2017-12-24 15:04:08 -05:00
. where ( 'status' , THREAD _STATUS . OPEN )
2018-02-11 14:54:30 -05:00
. first ( ) ;
2017-12-24 15:04:08 -05:00
2017-12-31 19:16:05 -05:00
return ( thread ? new Thread ( thread ) : null ) ;
}
2017-12-24 15:04:08 -05:00
2017-12-31 19:16:05 -05:00
/ * *
* Creates a new modmail thread for the specified user
* @ param { Eris . User } user
* @ returns { Promise < Thread > }
* @ throws { Error }
* /
async function createNewThreadForUser ( user ) {
const existingThread = await findOpenThreadByUserId ( user . id ) ;
if ( existingThread ) {
throw new Error ( 'Attempted to create a new thread for a user with an existing open thread!' ) ;
2017-12-24 15:04:08 -05:00
}
// Use the user's name+discrim for the thread channel's name
// Channel names are particularly picky about what characters they allow, so we gotta do some clean-up
let cleanName = transliterate . slugify ( user . username ) ;
if ( cleanName === '' ) cleanName = 'unknown' ;
cleanName = cleanName . slice ( 0 , 95 ) ; // Make sure the discrim fits
const channelName = ` ${ cleanName } - ${ user . discriminator } ` ;
console . log ( ` [NOTE] Creating new thread channel ${ channelName } ` ) ;
// Attempt to create the inbox channel for this thread
let createdChannel ;
try {
2018-02-11 14:54:30 -05:00
createdChannel = await utils . getInboxGuild ( ) . createChannel ( channelName , null , 'New ModMail thread' , config . newThreadCategoryId ) ;
2017-12-24 15:04:08 -05:00
} catch ( err ) {
console . error ( ` Error creating modmail channel for ${ user . username } # ${ user . discriminator } ! ` ) ;
throw err ;
}
// Save the new thread in the database
2017-12-31 19:16:05 -05:00
const newThreadId = await createThreadInDB ( {
2017-12-24 15:04:08 -05:00
status : THREAD _STATUS . OPEN ,
user _id : user . id ,
user _name : ` ${ user . username } # ${ user . discriminator } ` ,
channel _id : createdChannel . id ,
created _at : moment . utc ( ) . format ( 'YYYY-MM-DD HH:mm:ss' )
} ) ;
2017-12-31 19:16:05 -05:00
const newThread = await findById ( newThreadId ) ;
2018-03-06 18:40:38 -05:00
//If no role is set, mention @here
const mention = ( config . mentionRoleID == null ) ? "@here" : ` <@& ${ config . mentionRoleID } > ` ;
2018-02-18 16:29:24 -05:00
// Ping moderators of the new thread
await newThread . postNonLogMessage ( {
2018-03-06 18:40:38 -05:00
content : ` ${ mention } New modmail thread ( ${ newThread . user _name } ) ` ,
2018-02-18 16:29:24 -05:00
disableEveryone : false
} ) ;
2018-02-14 01:53:34 -05:00
// Send auto-reply to the user
if ( config . responseMessage ) {
newThread . postToUser ( config . responseMessage ) ;
}
2017-12-31 19:16:05 -05:00
// Post some info to the beginning of the new thread
const mainGuild = utils . getMainGuild ( ) ;
const member = ( mainGuild ? mainGuild . members . get ( user . id ) : null ) ;
if ( ! member ) console . log ( ` [INFO] Member ${ user . id } not found in main guild ${ config . mainGuildId } ` ) ;
let mainGuildNickname = null ;
if ( member && member . nick ) mainGuildNickname = member . nick ;
else if ( member && member . user ) mainGuildNickname = member . user . username ;
else if ( member == null ) mainGuildNickname = 'NOT ON SERVER' ;
2017-12-24 15:04:08 -05:00
2017-12-31 19:16:05 -05:00
if ( mainGuildNickname == null ) mainGuildNickname = 'UNKNOWN' ;
const userLogCount = await getClosedThreadCountByUserId ( user . id ) ;
const accountAge = humanizeDuration ( Date . now ( ) - user . createdAt , { largest : 2 } ) ;
const infoHeader = ` ACCOUNT AGE ** ${ accountAge } **, ID ** ${ user . id } **, NICKNAME ** ${ mainGuildNickname } **, LOGS ** ${ userLogCount } ** \n ------------------------------- ` ;
await newThread . postSystemMessage ( infoHeader ) ;
// Return the thread
return newThread ;
2017-12-24 15:04:08 -05:00
}
/ * *
* Creates a new thread row in the database
* @ param { Object } data
* @ returns { Promise < String > } The ID of the created thread
* /
2017-12-31 19:16:05 -05:00
async function createThreadInDB ( data ) {
2017-12-24 15:04:08 -05:00
const threadId = uuid . v4 ( ) ;
const now = moment . utc ( ) . format ( 'YYYY-MM-DD HH:mm:ss' ) ;
const finalData = Object . assign ( { created _at : now , is _legacy : 0 } , data , { id : threadId } ) ;
2017-12-31 19:16:05 -05:00
await knex ( 'threads' ) . insert ( finalData ) ;
2017-12-24 15:04:08 -05:00
return threadId ;
}
2017-12-31 19:16:05 -05:00
/ * *
* @ param { String } id
* @ returns { Promise < Thread > }
* /
async function findById ( id ) {
const row = await knex ( 'threads' )
. where ( 'id' , id )
. first ( ) ;
if ( ! row ) return null ;
return new Thread ( row ) ;
2017-12-24 15:04:08 -05:00
}
/ * *
* @ param { String } channelId
* @ returns { Promise < Thread > }
* /
2017-12-31 19:16:05 -05:00
async function findByChannelId ( channelId ) {
2017-12-24 15:04:08 -05:00
const thread = await knex ( 'threads' )
. where ( 'channel_id' , channelId )
. first ( ) ;
return ( thread ? new Thread ( thread ) : null ) ;
}
2018-02-11 14:54:30 -05:00
/ * *
* @ param { String } channelId
* @ returns { Promise < Thread > }
* /
async function findOpenThreadByChannelId ( channelId ) {
const thread = await knex ( 'threads' )
. where ( 'channel_id' , channelId )
. where ( 'status' , THREAD _STATUS . OPEN )
. first ( ) ;
return ( thread ? new Thread ( thread ) : null ) ;
}
2017-12-24 15:04:08 -05:00
/ * *
2017-12-31 19:16:05 -05:00
* @ param { String } userId
* @ returns { Promise < Thread [ ] > }
2017-12-24 15:04:08 -05:00
* /
2017-12-31 19:16:05 -05:00
async function getClosedThreadsByUserId ( userId ) {
const threads = await knex ( 'threads' )
. where ( 'status' , THREAD _STATUS . CLOSED )
. where ( 'user_id' , userId )
. select ( ) ;
return threads . map ( thread => new Thread ( thread ) ) ;
2017-12-24 15:04:08 -05:00
}
2017-12-31 19:16:05 -05:00
/ * *
* @ param { String } userId
* @ returns { Promise < number > }
* /
async function getClosedThreadCountByUserId ( userId ) {
const row = await knex ( 'threads' )
. where ( 'status' , THREAD _STATUS . CLOSED )
. where ( 'user_id' , userId )
. first ( knex . raw ( 'COUNT(id) AS thread_count' ) ) ;
2017-12-24 15:04:08 -05:00
2017-12-31 19:16:05 -05:00
return parseInt ( row . thread _count , 10 ) ;
}
2018-02-11 14:54:30 -05:00
async function findOrCreateThreadForUser ( user ) {
const existingThread = await findOpenThreadByUserId ( user . id ) ;
if ( existingThread ) return existingThread ;
return createNewThreadForUser ( user ) ;
}
2018-03-11 15:32:14 -04:00
async function getThreadsThatShouldBeClosed ( ) {
const now = moment . utc ( ) . format ( 'YYYY-MM-DD HH:mm:ss' ) ;
const threads = await knex ( 'threads' )
. where ( 'status' , THREAD _STATUS . OPEN )
. whereNotNull ( 'scheduled_close_at' )
. where ( 'scheduled_close_at' , '<=' , now )
. select ( ) ;
return threads . map ( thread => new Thread ( thread ) ) ;
}
2017-12-31 19:16:05 -05:00
module . exports = {
2018-02-11 14:54:30 -05:00
findById ,
2017-12-31 19:16:05 -05:00
findOpenThreadByUserId ,
findByChannelId ,
2018-02-11 14:54:30 -05:00
findOpenThreadByChannelId ,
2017-12-31 19:16:05 -05:00
createNewThreadForUser ,
getClosedThreadsByUserId ,
2018-02-11 14:54:30 -05:00
findOrCreateThreadForUser ,
2018-03-11 15:32:14 -04:00
getThreadsThatShouldBeClosed ,
2018-02-11 14:54:30 -05:00
createThreadInDB
2017-12-24 15:04:08 -05:00
} ;