2021-08-23 16:10:22 +02:00
const { extend : createGotClient } = require ( 'got' ) ;
2021-08-23 22:11:32 +02:00
const { ConnectionError , SayError , TimeoutError , UserBanError , ChatClient } = require ( 'dank-twitch-irc' ) ;
2020-12-15 14:25:01 +01:00
const chalk = require ( 'chalk' ) ;
2020-12-22 07:25:43 +01:00
const WS = require ( 'ws' ) ;
2020-12-15 14:25:01 +01:00
const Config = require ( './config.json' ) ;
const clients = [ ] ;
2021-08-23 22:11:32 +02:00
let listenClient = null ;
2020-12-22 09:04:26 +01:00
const pingInterval = 120e3 ;
2020-12-15 14:25:01 +01:00
let lastIndex = 0 ;
2020-12-22 09:04:26 +01:00
const uuidRegex = /[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}/i ;
2020-12-22 07:25:43 +01:00
const msgRegex = /[\u034f\u2800\u{E0000}\u180e\ufeff\u2000-\u200d\u206D]/gu ;
2020-12-15 14:25:01 +01:00
const pnslClient = createGotClient ( {
prefixUrl : 'https://bot.tetyys.com/api/v1' ,
throwHttpErrors : false ,
headers : {
2021-08-23 16:10:22 +02:00
Authorization : ` Bearer ${ Config . PNSLToken } `
}
2020-12-15 14:25:01 +01:00
} ) ;
2021-03-23 03:02:31 +01:00
const urlClient = createGotClient ( ) ;
2020-12-22 09:04:26 +01:00
const fetchList = async ( list ) => {
const listID = uuidRegex . exec ( list ) ? . [ 0 ] ;
if ( ! listID ) {
2020-12-22 07:25:43 +01:00
throw new Error ( 'Invalid List ID!' ) ;
}
2021-08-23 16:10:22 +02:00
const { body : listMetaBody , statusCode } = await pnslClient ( 'BotLists/Properties' , { searchParams : { Guid : listID } } ) ;
2020-12-22 07:25:43 +01:00
if ( statusCode === 404 ) {
throw new Error ( 'List was not found.' ) ;
}
if ( statusCode === 401 ) {
throw new Error ( 'Authorization token is invalid' ) ;
}
if ( statusCode !== 200 ) {
throw new Error ( 'Unexpected error occurred!' ) ;
}
2021-08-23 16:10:22 +02:00
const { body : listData } = await pnslClient ( ` BotLists/ ${ listID } ` ) ;
2020-12-22 07:25:43 +01:00
const listMeta = JSON . parse ( listMetaBody ) ;
2021-08-23 16:10:22 +02:00
return { listMeta , listData } ;
2020-12-22 07:25:43 +01:00
} ;
2021-03-23 03:02:31 +01:00
const fetchUrlList = async ( list ) => {
2021-08-23 16:10:22 +02:00
const { body : listMetaBody , statusCode } = await urlClient ( list ) ;
2021-03-23 03:02:31 +01:00
if ( statusCode === 404 ) {
throw new Error ( 'List was not found.' ) ;
}
if ( statusCode !== 200 ) {
throw new Error ( 'Unexpected error occured!' ) ;
}
return listMetaBody ;
} ;
2021-09-06 23:11:35 +02:00
const runList = async ( listData , listID , channel , sourceChannel ) => {
2020-12-22 07:25:43 +01:00
const banArr = listData . split ( '\n' ) ;
2021-08-23 22:11:32 +02:00
const targetChannel = Config . UseParallelUniverse === true ? channel : ` # ${ channel } ` ;
2020-12-22 07:25:43 +01:00
if ( banArr . length > Config . MaxChunkSize ) {
2021-03-30 12:25:24 +02:00
const banChunks = [ ] ;
2020-12-22 07:25:43 +01:00
while ( banArr . length > 0 ) {
banChunks . push ( banArr . splice ( 0 , Config . MaxChunkSize ) ) ;
}
2021-03-23 11:15:06 +01:00
const chunkCount = banChunks . length ;
2020-12-22 07:25:43 +01:00
for ( const [ i , chunk ] of banChunks . entries ( ) ) {
2021-08-23 16:10:22 +02:00
const currentChunk = i + 1 ;
2020-12-22 07:25:43 +01:00
const promises = [ ] ;
for ( const entry of chunk ) {
2021-08-23 16:10:22 +02:00
let user = '' ;
let reason = '' ;
if ( entry === '' ) {
continue ;
}
2020-12-22 07:25:43 +01:00
[ , user , ... reason ] = entry . split ( ' ' ) ;
2021-08-23 22:11:32 +02:00
promises . push ( getConnection ( ) . ban ( targetChannel , user , reason . join ( ' ' ) ) ) ;
2020-12-22 07:25:43 +01:00
}
await Promise . allSettled ( promises ) ;
2021-09-06 23:11:35 +02:00
say ( sourceChannel , ` ${ Config . UseParallelUniverse === true ? '[PU]' : '' } Chunk ${ currentChunk } / ${ chunkCount } of ${ listID } executed successfully on channel ${ targetChannel } ` ) ;
2021-09-26 06:48:11 +02:00
if ( Config . SendChunks ) say ( channel , ` Chunk ${ currentChunk } / ${ chunkCount } executed successfully ` ) ;
2021-03-30 12:21:21 +02:00
if ( currentChunk === chunkCount ) {
2020-12-22 07:25:43 +01:00
break ;
}
await sleep ( Config . DelayPerChunk ) ;
}
} else {
const promises = [ ] ;
for ( const entry of banArr ) {
2021-08-23 16:10:22 +02:00
let user = '' ;
let reason = '' ;
if ( entry === '' ) {
continue ;
}
2020-12-22 07:25:43 +01:00
[ , user , ... reason ] = entry . split ( ' ' ) ;
2021-08-23 22:11:32 +02:00
promises . push ( getConnection ( ) . ban ( targetChannel , user , reason . join ( ' ' ) ) ) ;
2020-12-22 07:25:43 +01:00
}
await Promise . allSettled ( promises ) ;
}
} ;
2021-08-23 16:10:22 +02:00
const sleep = ( ms ) => new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
2020-12-15 14:25:01 +01:00
2021-08-23 16:10:22 +02:00
const createTwitchClient = ( ) => new ChatClient ( {
username : Config . Username ,
password : Config . Password ,
rateLimits : 'verifiedBot'
} ) ;
2020-12-15 14:25:01 +01:00
const getConnection = ( ) => {
2020-12-22 07:25:43 +01:00
const readyClients = clients . filter ( ( i ) => i . ready ) ;
return readyClients [ ++ lastIndex % readyClients . length ] ;
2020-12-15 14:25:01 +01:00
} ;
2020-12-22 09:10:29 +01:00
const say = ( channel , message ) => {
2021-08-23 22:36:59 +02:00
if ( channel . startsWith ( '#' ) ) {
listenClient . say ( channel , message ) ;
} else {
listenClient . say ( ` # ${ channel } ` , message ) ;
}
2020-12-22 09:10:29 +01:00
} ;
2021-08-23 16:10:22 +02:00
if ( ! Config . IgnorePNSL ) {
const pnslWebSocket = new WS ( 'wss://bot.tetyys.com/api/wss' , [ ] , { headers : { Authorization : ` Bearer ${ Config . PNSLToken } ` } } ) ;
2020-12-22 07:25:43 +01:00
2021-03-23 03:02:31 +01:00
pnslWebSocket . on ( 'open' , ( ) => {
console . log ( ` ${ chalk . green ( '[P&SL]' ) } || Connected to P&SL Websocket server ` ) ;
} ) ;
2020-12-22 07:25:43 +01:00
2021-03-23 03:02:31 +01:00
pnslWebSocket . on ( 'message' , async ( data ) => {
2021-08-23 16:10:22 +02:00
const { o : type , p : payload } = JSON . parse ( data ) ;
2021-03-23 03:02:31 +01:00
switch ( type ) {
case 0 : {
2021-08-23 16:10:22 +02:00
const { listMeta } = await fetchList ( payload . LatestBotList ) ;
2021-03-23 03:02:31 +01:00
console . log ( ` ${ chalk . green ( '[P&SL]' ) } || Latest Botlist: ${ payload . LatestBotList } [ ${ listMeta . name } ] ( ${ listMeta . count } ) ` ) ;
break ;
}
case 1 : {
console . log ( ` ${ chalk . green ( '[P&SL]' ) } || New Botlist: ${ payload . Guid } [ ${ payload . Name } ] ( ${ payload . Count } ) ( ${ payload . Tags . join ( ' ' ) } ) ` ) ;
break ;
}
case 2 : {
console . log ( ` ${ chalk . green ( '[P&SL]' ) } || New False Positive: ${ payload . UserTwitchId } [ ${ payload . BotListGuid } ] ` ) ;
break ;
}
default :
break ;
}
} ) ;
}
2020-12-22 07:25:43 +01:00
( async ( ) => {
await Promise . all ( [ ... Array ( Config . MaxConnections ) ] . map ( async ( _ , i ) => {
2020-12-15 14:25:01 +01:00
clients [ i ] = createTwitchClient ( ) ;
clients [ i ] . connect ( ) ;
2020-12-22 07:25:43 +01:00
clients [ i ] . on ( 'error' , ( error ) => {
2021-08-23 16:10:22 +02:00
if ( error instanceof SayError ) {
return ;
}
2020-12-22 10:23:52 +01:00
if ( error instanceof UserBanError ) {
2021-03-23 11:15:06 +01:00
return console . warn ( ` Failed to ban user ${ error . username } from ${ error . channelName } ` ) ;
2020-12-22 10:23:52 +01:00
}
2020-12-22 07:25:43 +01:00
if ( error instanceof ConnectionError ) {
if ( clients [ i ] . ready ) {
return ;
}
clients [ i ] . connect ( ) ;
return console . error ( ` Error in client ${ i } -> ${ error . name } || ${ error . message } ` ) ;
2020-12-15 14:25:01 +01:00
}
2020-12-22 09:04:26 +01:00
if ( error instanceof TimeoutError ) {
2020-12-22 07:40:02 +01:00
clients [ i ] . connect ( ) ;
2020-12-22 09:04:26 +01:00
return console . error ( ` Timeout in client ${ i } -> ${ error . name } || ${ error . message } | Trying to reconnect ` ) ;
2020-12-22 07:40:02 +01:00
}
2020-12-22 07:25:43 +01:00
console . error ( error ) ;
} ) ;
return await new Promise ( ( resolve ) => clients [ i ] . on ( 'ready' , ( ) => resolve ( ) ) ) ;
} ) ) ;
2020-12-15 14:25:01 +01:00
2021-08-23 22:11:32 +02:00
listenClient = createTwitchClient ( ) ;
listenClient . connect ( ) ;
listenClient . on ( 'ready' , async ( ) => {
2021-09-06 22:23:14 +02:00
if ( Config . Channels . length !== 0 ) {
2021-09-06 22:19:16 +02:00
await listenClient . joinAll ( Config . Channels ) ;
}
2021-09-06 22:23:14 +02:00
2021-08-23 22:11:32 +02:00
console . log ( ` ${ chalk . green ( '[TWITCH]' ) } || Command listener connected ` ) ;
} ) ;
2020-12-22 07:25:43 +01:00
console . log ( ` ${ chalk . green ( '[TWITCH]' ) } || ${ clients . filter ( ( i ) => i . ready ) . length } Clients connected ` ) ;
2020-12-15 14:25:01 +01:00
2021-08-23 22:11:32 +02:00
listenClient . on ( 'PRIVMSG' , async ( { messageText , senderUserID , channelName } ) => {
2021-08-23 16:10:22 +02:00
if ( ! Config . Users . includes ( senderUserID ) ) {
return ;
}
2020-12-15 14:25:01 +01:00
2021-08-23 16:10:22 +02:00
const message = messageText . replace ( msgRegex , '' ) . trimEnd ( ) ;
if ( ! message . startsWith ( Config . Prefix ) ) {
return ;
}
2020-12-22 07:25:43 +01:00
const content = message . split ( /\s+/g ) ;
const command = content [ 0 ] . slice ( Config . Prefix . length ) ;
const args = content . slice ( 1 ) ;
2020-12-15 14:25:01 +01:00
2020-12-22 07:25:43 +01:00
if ( command === 'ping' ) {
2021-09-26 06:48:11 +02:00
const channel = args [ 0 ] ;
2020-12-22 09:10:29 +01:00
return say ( channel || channelName , ` ${ clients . filter ( ( i ) => i . ready ) . length } Clients from total of ${ clients . length } are operational ` ) ;
2020-12-22 07:25:43 +01:00
}
2020-12-22 10:13:04 +01:00
if ( command === 'quit' ) {
process . exit ( 0 ) ;
}
2020-12-15 14:25:01 +01:00
2020-12-22 07:25:43 +01:00
if ( command === 'runlist' ) {
const listID = args [ 0 ] ;
2021-09-26 06:48:11 +02:00
let channel = args [ 1 ] ;
2020-12-15 14:25:01 +01:00
2021-08-23 16:10:22 +02:00
if ( ! listID ) {
2021-09-06 23:11:35 +02:00
return say ( channelName , 'No list ID provided!' ) ;
2021-08-23 16:10:22 +02:00
}
2021-09-26 06:03:24 +02:00
if ( ! channel ) {
channel = Config . DefaultChannel || channelName ;
}
if ( Config . Blacklisted . includes ( channel ) ) {
return say ( channelName , ` I am not allowed to execute lists in channel ${ channel } ! ` ) ;
}
2020-12-22 07:25:43 +01:00
try {
2021-09-26 06:03:24 +02:00
if ( channel != channelName ) {
2021-08-23 22:11:32 +02:00
const mods = await listenClient . getMods ( ` # ${ channel } ` ) ;
2021-08-23 16:10:22 +02:00
if ( ! mods . includes ( Config . Username ) ) {
2021-09-06 23:11:35 +02:00
return say ( channelName , ` I am not a moderator in channel ${ channel } ! ` ) ;
2021-08-23 16:10:22 +02:00
}
2020-12-15 14:25:01 +01:00
}
2021-09-26 06:03:24 +02:00
2021-03-23 10:56:20 +01:00
if ( ! Config . IgnorePNSL ) {
2021-03-23 03:02:31 +01:00
const data = await fetchList ( listID ) ;
2021-09-26 06:03:24 +02:00
await runList ( data . listData , listID , channel , channelName ) ;
2021-08-23 16:10:22 +02:00
} else {
2021-03-23 03:02:31 +01:00
const data = await fetchUrlList ( listID ) ;
2021-09-26 06:03:24 +02:00
await runList ( data , listID , channel , channelName ) ;
2021-03-23 03:02:31 +01:00
}
2021-09-06 23:11:35 +02:00
say ( channelName , ` ${ Config . UseParallelUniverse === true ? '[PU]' : '' } List ${ listID } executed successfully on channel ${ channel || channelName } ` ) ;
2021-09-26 06:03:24 +02:00
if ( Config . SendMeme ) say ( channel , 'FeelsGoodMan' ) ;
2021-08-23 16:10:22 +02:00
} catch ( e ) {
console . error ( e ) ;
2021-09-06 23:11:35 +02:00
return say ( channelName , ` Error Occurred! ${ e . message } ` ) ;
2020-12-15 14:25:01 +01:00
}
}
} ) ;
2020-12-22 07:25:43 +01:00
setInterval ( ( ) => {
for ( const client of clients ) {
client . ping ( ) ;
}
} , pingInterval ) ;
2020-12-15 14:25:01 +01:00
} ) ( ) ;
2020-12-22 10:23:52 +01:00
process
. on ( 'unhandledRejection' , ( err ) => {
2021-08-23 16:10:22 +02:00
if ( err . name === 'SayError' ) {
return ;
}
2020-12-22 10:23:52 +01:00
console . error ( ` ${ chalk . red ( '[UnhandledRejection]' ) } || [ ${ err . name } ] ${ err } - ${ err . stack } ` ) ;
} )
. on ( 'uncaughtException' , ( err ) => {
console . error ( ` ${ chalk . red ( '[UncaughtException]' ) } || ${ err . message } ` ) ;
process . exit ( 0 ) ;
2021-03-23 03:02:31 +01:00
} ) ;