import { IMessage , IEmbedData , IReactionOptions } from '../common/types' ; import { GuildChannel , Guild , TextChannel , MessageEmbed , MessageReaction , User , MessagePayload , MessageOptions , } from 'discord.js' ; import { GuildService } from './guild.service' ; import Constants from '../common/constants' ; import { LoggerService } from './logger.service' ; import ms from 'ms' ; export class MessageService { private _botReportingChannel : TextChannel ; private _guild : Guild ; private _linkPrefix : string = 'https://discord.com/channels' ; private _ARROWS = [ '⬅️' , '➡️' ] ; private _CANCEL_EMOTE = '❎' ; constructor ( private _guildService , private _loggerService ) { this . _guild = this . _guildService . get ( ) ; this . _botReportingChannel = this . _guildService . getChannel ( Constants . Channels . Admin . BotLogs ) as TextChannel ; } getChannel ( message ) { if ( message . channel . isThread ( ) ) { return message . channel . parent as GuildChannel ; } return message . channel as GuildChannel ; } sendBotReport ( payload ) { this . _sendConstructedReport ( payload ) ; } sendBotReportOnMessage ( message ) { let report = `New report on ${ message . author } from ${ message . channel }:\n` ; if ( message . content . length ) { report += `\`\`\`${ message . content . replace ( /`/g , '' ) }\`\`\`` ; } report += `${ this . _linkPrefix }/${ this . _guild . id }/${ message . channel . id }/${ message . id }` ; return this . _sendConstructedReport ( { content : report , files : message . attachments . map ( ( e ) => e . url ) , } ) ; } attemptDMUser ( message , content ) { return this . sendStringOrEmbed ( message . author , content ) . then ( ( ) => message . react ( '👍' ) ) . catch ( ( ) => this . sendStringOrEmbed ( message . channel as TextChannel , content ) ) ; } async sendStringOrEmbed ( destination , payload ) { if ( typeof payload === 'string' ) { return destination . send ( { content : payload } ) ; } return destination . send ( { embeds : [ payload ] } ) ; } async sendReactiveMessage ( message , embedData , lambda , options ) { const msg = await message . reply ( { embeds : [ embedData . embeddedMessage ] } ) ; const minEmotes = embedData . emojiData . length - ( options . reactionCutoff ?? 1 ) ; await Promise . all ( embedData . emojiData . map ( ( reaction ) => msg . react ( reaction . emoji ) ) ) ; await msg . react ( this . _CANCEL_EMOTE ) ; const collector = msg . createReactionCollector ( { filter : ( reaction , user ) => ( embedData . emojiData . some ( ( reactionKey ) => reactionKey . emoji === reaction . emoji . name ) || reaction . emoji . name === this . _CANCEL_EMOTE ) && user . id === message . author . id , time : ms ( '2m' ) , } ) ; collector . on ( 'collect' , async ( reaction ) => { if ( reaction . emoji . name === this . _CANCEL_EMOTE ) { collector . stop ( ) ; return ; } const targetData = embedData . emojiData . find ( ( e ) => e . emoji === reaction . emoji . name ) ; if ( ! targetData ) { return ; } try { lambda ( targetData . args ) ; embedData . emojiData = embedData . emojiData . filter ( ( e ) => e . emoji !== reaction . emoji . name ) ; if ( embedData . emojiData . length > minEmotes ) { return ; } if ( options . cutoffMessage ) { await msg . edit ( options . cutoffMessage ) ; if ( typeof options . cutoffMessage === 'string' ) { await msg . suppressEmbeds ( true ) ; } } collector . stop ( ) ; } catch ( e ) { this . _loggerService . warn ( e ) ; } } ) ; collector . on ( 'end' , async ( ) => { if ( ! msg . deletable ) { return ; } await msg . reactions . removeAll ( ) ; if ( options . closingMessage && ! msg . editedAt ) { await msg . edit ( options . closingMessage ) ; if ( typeof options . closingMessage === 'string' ) { await msg . suppressEmbeds ( true ) ; } } } ) ; return msg ; } async sendPagedEmbed ( message , _pages ) { const pages = _pages . map ( ( e , i ) => e . setFooter ( `Page ${ i + 1 } of ${ _pages . length }` ) ) ; const msg = await message . channel . send ( { embeds : [ pages [ 0 ] ] } ) ; await Promise . all ( this . _ARROWS . map ( ( a ) => msg . react ( a ) ) ) ; const collector = msg . createReactionCollector ( { filter : ( reaction , user ) => this . _ARROWS . includes ( reaction . emoji . name ! ) && user . id !== msg . author . id , time : 1000 * 60 * 10 , } ) ; let pageIndex = 0 ; collector . on ( 'collect' , async ( reaction ) => { reaction . emoji . name === '➡️' ? pageIndex ++ : pageIndex -- ; pageIndex = ( pageIndex + pages . length ) % pages . length ; await reaction . users . remove ( reaction . users . cache . last ( ) ) . then ( async ( ) => await msg . edit ( { embeds : [ pages [ pageIndex ] ] } ) ) ; } ) ; collector . on ( 'end' , async ( ) => { await msg . reactions . removeAll ( ) . catch ( ( ) => { } ) ; } ) ; return msg ; } generateEmbedList ( listItems ) { const maxCol = 3 ; const maxRows = 10 ; const numCols = Math . min ( maxCol , Math . ceil ( listItems . length / maxRows ) ) ; const numRows = Math . ceil ( listItems . length / numCols ) ; const embedItem = new MessageEmbed ( ) ; const temp = listItems . slice ( 0 ) ; const sortedList = temp . every ( ( val , i ) => { if ( i === 0 ) { return true ; } return val >= temp [ i - 1 ] ; } ) ; const columns = new Array ( numCols ) . fill ( 0 ) . map ( ( ) => temp . splice ( 0 , numRows ) ) ; columns . forEach ( ( column ) => { let header = '\u200B' ; if ( sortedList ) { const fromLetter = column [ 0 ] . charAt ( 0 ) || '' ; const toLetter = column [ column . length - 1 ] . charAt ( 0 ) || '' ; header = `${ fromLetter } - ${ toLetter }` ; } embedItem . addField ( header , column . join ( '\n' ) , true ) ; } ) ; return embedItem ; } private _sendConstructedReport ( payload ) { return this . _botReportingChannel . send ( payload ) ; } }