2019-10-21 19:58:20 -04:00
/* eslint-disable no-param-reassign */
2019-10-29 12:24:15 -04:00
import { promisify } from 'util' ;
2019-10-14 15:46:10 -04:00
import childProcess from 'child_process' ;
import nodemailer from 'nodemailer' ;
2019-10-29 14:20:51 -04:00
import { Message , PrivateChannel , Member , User } from 'eris' ;
2019-10-29 11:41:33 -04:00
import uuid from 'uuid/v4' ;
import moment from 'moment' ;
2019-10-28 16:21:04 -04:00
import { Client } from '..' ;
import { Command , RichEmbed } from '.' ;
2019-10-30 20:49:15 -04:00
import { ModerationInterface , AccountInterface } from '../models' ;
2019-10-14 15:46:10 -04:00
export default class Util {
2019-10-14 23:32:37 -04:00
public client : Client ;
2019-10-20 22:48:48 -04:00
public transport : nodemailer.Transporter ;
2019-10-14 23:32:37 -04:00
constructor ( client : Client ) {
this . client = client ;
2019-10-20 22:48:48 -04:00
this . transport = nodemailer . createTransport ( {
2019-10-28 19:50:45 -04:00
host : 'staff.libraryofcode.org' ,
2019-10-20 22:48:48 -04:00
auth : { user : 'support' , pass : this.client.config.emailPass } ,
} ) ;
2019-10-14 23:32:37 -04:00
}
2019-10-14 15:46:10 -04:00
public async exec ( command : string ) : Promise < string > {
const ex = promisify ( childProcess . exec ) ;
let result : string ;
2019-10-15 18:44:46 -04:00
// eslint-disable-next-line no-useless-catch
2019-10-14 15:46:10 -04:00
try {
const res = await ex ( command ) ;
2019-10-15 18:44:46 -04:00
result = res . stderr || res . stdout ;
2019-10-14 15:46:10 -04:00
} catch ( err ) {
2019-10-30 13:34:02 -04:00
return Promise . reject ( new Error ( ` Command failed: ${ err . cmd } \ n ${ err . stderr || err . stdout } ` ) ) ;
2019-10-14 15:46:10 -04:00
}
return result ;
}
2019-10-14 23:32:37 -04:00
2019-10-31 18:14:34 -04:00
/ * *
* Resolves a command
* @param command Parent command label
* @param args Use to resolve subcommands
* @param message Only used to check for errors
* /
public resolveCommand ( command : string , args? : string [ ] , message? : Message ) : Promise < { cmd : Command , args : string [ ] } > {
try {
2019-10-31 19:32:58 -04:00
this . client . signale . info ( command ) ;
this . client . signale . info ( args ) ;
2019-10-31 18:14:34 -04:00
let resolvedCommand : Command ;
if ( this . client . commands . has ( command ) ) resolvedCommand = this . client . commands . get ( command ) ;
else {
for ( const cmd of this . client . commands . toArray ( ) ) {
if ( cmd . aliases . includes ( command ) ) { resolvedCommand = cmd ; break ; }
}
2019-10-14 19:04:07 -04:00
}
2019-10-31 18:14:34 -04:00
if ( ! resolvedCommand ) return Promise . resolve ( { cmd : null , args } ) ;
2019-10-31 20:20:55 -04:00
let parentLabel = ` ${ command } ` ;
2019-10-31 18:14:34 -04:00
let hasSubCommands = true ;
while ( hasSubCommands ) {
if ( ! resolvedCommand . subcommands . size ) {
hasSubCommands = false ; break ;
2019-10-31 19:29:51 -04:00
} else if ( ! args [ 0 ] ) {
hasSubCommands = false ; break ;
2019-10-31 18:14:34 -04:00
} else if ( resolvedCommand . subcommands . has ( args [ 0 ] ) ) {
resolvedCommand = resolvedCommand . subcommands . get ( args [ 0 ] ) ;
2019-10-31 20:24:08 -04:00
parentLabel += ` ${ args [ 0 ] } ` ; args . shift ( ) ;
2019-10-31 18:14:34 -04:00
} else {
for ( const subCmd of resolvedCommand . subcommands . toArray ( ) ) {
if ( subCmd . aliases . includes ( args [ 0 ] ) ) {
2019-10-31 20:24:08 -04:00
resolvedCommand = subCmd ; parentLabel += ` ${ args [ 0 ] } ` ; args . shift ( ) ; break ;
2019-10-31 18:14:34 -04:00
}
}
}
}
2019-10-31 19:54:25 -04:00
const finalCommand = resolvedCommand ;
2019-10-31 20:20:55 -04:00
finalCommand . parentName = parentLabel ;
2019-10-31 18:14:34 -04:00
2019-10-31 19:54:25 -04:00
return Promise . resolve ( { cmd : finalCommand , args } ) ;
2019-10-31 18:14:34 -04:00
} catch ( error ) {
this . handleError ( error , message ) ;
return Promise . reject ( error ) ;
2019-10-14 19:04:07 -04:00
}
}
2019-10-15 19:10:37 -04:00
2019-10-19 09:31:54 -04:00
public async handleError ( error : Error , message? : Message , command? : Command ) : Promise < void > {
2019-10-28 19:54:36 -04:00
try {
this . client . signale . error ( error ) ;
const info = { content : ` \` \` \` js \ n ${ error . stack } \ n \` \` \` ` , embed : null } ;
if ( message ) {
const embed = new RichEmbed ( ) ;
embed . setColor ( 'FF0000' ) ;
embed . setAuthor ( ` Error caused by ${ message . author . username } # ${ message . author . discriminator } ` , message . author . avatarURL ) ;
embed . setTitle ( 'Message content' ) ;
embed . setDescription ( message . content ) ;
embed . addField ( 'User' , ` ${ message . author . mention } ( \` ${ message . author . id } \` ) ` , true ) ;
embed . addField ( 'Channel' , message . channel . mention , true ) ;
let guild : string ;
if ( message . channel instanceof PrivateChannel ) guild = '@me' ;
else guild = message . channel . guild . id ;
embed . addField ( 'Message link' , ` [Click here](https://discordapp.com/channels/ ${ guild } / ${ message . channel . id } / ${ message . id } ) ` , true ) ;
embed . setTimestamp ( new Date ( message . timestamp ) ) ;
info . embed = embed ;
}
await this . client . createMessage ( '595788220764127272' , info ) ;
2019-10-31 19:00:14 -04:00
const msg = message . content . slice ( this . client . config . prefix . length ) . trim ( ) . split ( / +/g ) ;
const label = msg [ 0 ] ;
const args = msg . slice ( 1 ) ;
if ( command ) this . resolveCommand ( label , args ) . then ( ( c ) = > { c . cmd . enabled = false ; } ) ;
2019-10-28 19:54:36 -04:00
if ( message ) message . channel . createMessage ( ` *** ${ this . client . stores . emojis . error } An unexpected error has occured - please contact a member of the Engineering Team. ${ command ? ' This command has been disabled.' : '' } *** ` ) ;
} catch ( err ) {
this . client . signale . error ( err ) ;
2019-10-19 09:31:54 -04:00
}
2019-10-15 19:10:37 -04:00
}
2019-10-19 07:47:08 -04:00
public splitFields ( fields : { name : string , value : string , inline? : boolean } [ ] ) : { name : string , value : string , inline? : boolean } [ ] [ ] {
let index = 0 ;
const array : { name : string , value : string , inline? : boolean } [ ] [ ] = [ [ ] ] ;
while ( fields . length ) {
if ( array [ index ] . length >= 25 ) { index += 1 ; array [ index ] = [ ] ; }
array [ index ] . push ( fields [ 0 ] ) ; fields . shift ( ) ;
}
return array ;
}
2019-10-21 19:58:20 -04:00
public splitString ( string : string , length : number ) : string [ ] {
if ( ! string ) return [ ] ;
if ( Array . isArray ( string ) ) string = string . join ( '\n' ) ;
if ( string . length <= length ) return [ string ] ;
const arrayString : string [ ] = [ ] ;
let str : string = '' ;
let pos : number ;
while ( string . length > 0 ) {
2019-10-29 15:43:53 -04:00
pos = string . length > length ? string . lastIndexOf ( '\n' , length ) : string . length ;
2019-10-21 19:58:20 -04:00
if ( pos > length ) pos = length ;
str = string . substr ( 0 , pos ) ;
string = string . substr ( pos ) ;
arrayString . push ( str ) ;
}
return arrayString ;
}
2019-10-26 20:19:49 -04:00
2019-10-28 16:21:04 -04:00
2019-10-26 20:19:49 -04:00
public async createHash ( password : string ) {
const hashed = await this . exec ( ` mkpasswd -m sha-512 " ${ password } " ` ) ;
return hashed ;
}
public isValidEmail ( email : string ) : boolean {
const checkAt = email . indexOf ( '@' ) ;
if ( checkAt < 1 ) return false ;
const checkDomain = email . indexOf ( '.' , checkAt + 2 ) ;
if ( checkDomain < checkAt ) return false ;
return true ;
}
public randomPassword ( ) : string {
let tempPass = '' ; const passChars = [ 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , 'o' , 'p' , 'q' , 'r' , 's' , 't' , 'u' , 'v' , 'w' , 'x' , 'y' , 'z' , 'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' , 'H' , 'I' , 'J' , 'K' , 'L' , 'M' , 'N' , 'O' , 'P' , 'Q' , 'R' , 'S' , 'T' , 'U' , 'V' , 'W' , 'X' , 'Y' , 'Z' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , '0' ] ;
2019-10-30 16:15:56 -04:00
while ( tempPass . length < 5 ) { tempPass += passChars [ Math . floor ( Math . random ( ) * passChars . length ) ] ; }
2019-10-26 20:19:49 -04:00
return tempPass ;
}
2019-10-26 21:08:42 -04:00
2019-10-30 20:49:15 -04:00
public async createAccount ( hash : string , etcPasswd : string , username : string , userID : string , emailAddress : string , moderatorID : string ) : Promise < AccountInterface > {
2019-10-26 21:08:42 -04:00
await this . exec ( ` useradd -m -p ${ hash } -c ${ etcPasswd } -s /bin/bash ${ username } ` ) ;
await this . exec ( ` chage -d0 ${ username } ` ) ;
const account = await new this . client . db . Account ( {
username , userID , emailAddress , createdBy : moderatorID , createdAt : new Date ( ) , locked : false ,
} ) ;
2019-10-30 20:49:15 -04:00
return account . save ( ) ;
2019-10-26 21:08:42 -04:00
}
2019-10-27 11:15:13 -04:00
2019-10-27 12:22:49 -04:00
public async deleteAccount ( username : string ) : Promise < void > {
2019-11-01 06:51:45 -04:00
const account = await this . client . db . Account . findOne ( { username } ) ;
if ( ! account ) return Promise . reject ( new Error ( 'Account not found' ) ) ;
2019-10-27 12:22:49 -04:00
await this . exec ( ` deluser ${ username } --remove-home --backup-to /management/Archives && rm -rf -R /home/ ${ username } ` ) ;
2019-11-01 06:51:45 -04:00
await this . client . removeGuildMemberRole ( '446067825673633794' , account . userID , '546457886440685578' , 'Cloud Account Deleted' ) ;
2019-10-27 12:22:49 -04:00
await this . client . db . Account . deleteOne ( { username } ) ;
2019-11-01 06:51:45 -04:00
return Promise . resolve ( ) ;
2019-10-27 12:22:49 -04:00
}
2019-10-28 16:21:04 -04:00
public async messageCollector ( message : Message , question : string , timeout : number , shouldDelete = false , choices : string [ ] = null , filter = ( msg : Message ) : boolean | void = > { } ) : Promise < Message > {
2019-10-27 11:15:13 -04:00
const msg = await message . channel . createMessage ( question ) ;
return new Promise ( ( res , rej ) = > {
2019-10-27 12:22:49 -04:00
setTimeout ( ( ) = > { if ( shouldDelete ) msg . delete ( ) ; rej ( new Error ( 'Did not supply a valid input in time' ) ) ; } , timeout ) ;
2019-10-27 11:15:13 -04:00
this . client . on ( 'messageCreate' , ( Msg ) = > {
if ( filter ( Msg ) === false ) return ;
const verif = choices ? choices . includes ( Msg . content ) : Msg . content ;
2019-10-28 16:21:04 -04:00
if ( verif ) { if ( shouldDelete ) msg . delete ( ) ; res ( Msg ) ; }
2019-10-27 11:15:13 -04:00
} ) ;
} ) ;
}
2019-10-29 11:41:33 -04:00
/ * *
* @param type ` 0 ` - Create
* ` 1 ` - Warn
* ` 2 ` - Lock
* ` 3 ` - Unlock
* ` 4 ` - Delete
* /
2019-10-29 14:20:51 -04:00
public async createModerationLog ( user : string , moderator : Member | User , type : number , reason? : string , duration? : number ) : Promise < ModerationInterface > {
2019-10-29 11:41:33 -04:00
const moderatorID = moderator . id ;
const account = await this . client . db . Account . findOne ( { $or : [ { username : user } , { userID : user } ] } ) ;
2019-10-29 12:25:41 -04:00
if ( ! account ) return Promise . reject ( new Error ( ` Account ${ user } not found ` ) ) ;
2019-10-29 11:41:33 -04:00
const { username , userID } = account ;
const logInput : { username : string , userID : string , logID : string , moderatorID : string , reason? : string , type : number , date : Date , expiration ? : { date : Date , processed : boolean } } = {
username , userID , logID : uuid ( ) , moderatorID , type , date : new Date ( ) ,
} ;
const now : number = Date . now ( ) ;
let date : Date ;
let processed = true ;
if ( reason ) logInput . reason = reason ;
2019-10-29 13:22:06 -04:00
if ( type === 2 ) {
if ( duration ) {
date = new Date ( now + duration ) ;
processed = false ;
} else date = null ;
}
2019-10-29 11:41:33 -04:00
const expiration = { date , processed } ;
logInput . expiration = expiration ;
const log = await new this . client . db . Moderation ( logInput ) ;
await log . save ( ) ;
let embedTitle : string ;
let color : string ;
2019-10-30 20:57:10 -04:00
let archType : string ;
2019-10-29 11:41:33 -04:00
switch ( type ) {
2019-10-30 20:57:10 -04:00
default : archType = 'Moderator' ; embedTitle = 'Cloud Account | Generic' ; color = '0892e1' ; break ;
case 0 : archType = 'Administrator' ; embedTitle = 'Cloud Account | Create' ; color = '00ff00' ; break ;
case 1 : archType = 'Moderator' ; embedTitle = 'Account Warning | Warn' ; color = 'ffff00' ; break ;
case 2 : archType = 'Supervisor' ; embedTitle = 'Account Infraction | Lock' ; color = 'ff6600' ; break ;
case 3 : archType = 'Supervisor' ; embedTitle = 'Account Reclaim | Unlock' ; color = '0099ff' ; break ;
case 4 : archType = 'Administrator' ; embedTitle = 'Cloud Account | Delete' ; color = 'ff0000' ; break ;
2019-10-29 11:41:33 -04:00
}
const embed = new RichEmbed ( )
. setTitle ( embedTitle )
. setColor ( color )
. addField ( 'User' , ` ${ username } | <@ ${ userID } > ` , true )
2019-10-30 20:57:10 -04:00
. addField ( archType , moderatorID === this . client . user . id ? 'SYSTEM' : ` <@ ${ moderatorID } > ` , true )
2019-10-29 11:41:33 -04:00
. setFooter ( this . client . user . username , this . client . user . avatarURL )
. setTimestamp ( ) ;
if ( reason ) embed . addField ( 'Reason' , reason || 'Not specified' ) ;
2019-10-29 13:43:26 -04:00
if ( type === 2 ) embed . addField ( 'Lock Expiration' , ` ${ date ? moment ( date ) . format ( 'dddd, MMMM Do YYYY, h:mm:ss A' ) : 'Indefinitely' } ` ) ;
2019-10-29 11:41:33 -04:00
// @ts-ignore
this . client . createMessage ( '580950455581147146' , { embed } ) ; this . client . getDMChannel ( userID ) . then ( ( channel ) = > channel . createMessage ( { embed } ) ) . catch ( ) ;
2019-10-29 12:24:15 -04:00
return Promise . resolve ( log ) ;
2019-10-29 11:41:33 -04:00
}
2019-10-14 23:32:37 -04:00
}