diff --git a/tvapp2/index.js b/tvapp2/index.js index 75ce43af..44d0ed45 100755 --- a/tvapp2/index.js +++ b/tvapp2/index.js @@ -12,8 +12,12 @@ import zlib from 'zlib'; import chalk from 'chalk'; import ejs from 'ejs'; import moment from 'moment'; -import * as child from 'child_process'; +import TimeAgo from 'javascript-time-ago'; +import en from 'javascript-time-ago/locale/en'; +import nconf from 'nconf'; +import crypto from 'node:crypto'; import cron, { schedule } from 'node-cron'; +import * as child from 'child_process'; import * as crons from 'cron'; /* @@ -50,6 +54,13 @@ const gitHash = child.execSync( 'git rev-parse HEAD' ).toString().trim(); chalk.level = 3; +/* + +*/ + +TimeAgo.addDefaultLocale(en); +const timeAgo = new TimeAgo( ); + /* Define > General @@ -89,6 +100,7 @@ const envHealthTimer = process.env.HEALTH_TIMER || 600000; const envTaskCronSync = process.env.TASK_CRON_SYNC || '0 0 */3 * *'; const envGitSHA1 = process.env.GIT_SHA1 || '0000000000000000000000000000000000000000'; const LOG_LEVEL = process.env.LOG_LEVEL || 4; +let TIME_STARTUP = 0; /* Define > Externals @@ -179,45 +191,45 @@ class Log static verbose( ...msg ) { if ( LOG_LEVEL >= 6 ) - console.debug( chalk.white.bgBlack.blackBright.bold( ` ${ name } ` ), chalk.white( `→` ), this.now(), chalk.gray( msg.join( ' ' ) ) ); + console.debug( chalk.white.bgBlack.blackBright.bold( ` ${ name } ` ), chalk.white( `⚙️` ), this.now(), chalk.gray( msg.join( ' ' ) ) ); } static debug( ...msg ) { if ( LOG_LEVEL >= 7 ) - console.trace( chalk.white.bgMagenta.bold( ` ${ name } ` ), chalk.white( `→` ), this.now(), chalk.magentaBright( msg.join( ' ' ) ) ); + console.trace( chalk.white.bgMagenta.bold( ` ${ name } ` ), chalk.white( `⚙️` ), this.now(), chalk.magentaBright( msg.join( ' ' ) ) ); else if ( LOG_LEVEL >= 5 ) - console.debug( chalk.white.bgGray.bold( ` ${ name } ` ), chalk.white( `→` ), this.now(), chalk.gray( msg.join( ' ' ) ) ); + console.debug( chalk.white.bgGray.bold( ` ${ name } ` ), chalk.white( `⚙️` ), this.now(), chalk.gray( msg.join( ' ' ) ) ); } static info( ...msg ) { if ( LOG_LEVEL >= 4 ) - console.info( chalk.white.bgBlueBright.bold( ` ${ name } ` ), chalk.white( `→` ), this.now(), chalk.blueBright( msg.join( ' ' ) ) ); + console.info( chalk.white.bgBlueBright.bold( ` ${ name } ` ), chalk.white( `ℹ️` ), this.now(), chalk.blueBright( msg.join( ' ' ) ) ); } static ok( ...msg ) { if ( LOG_LEVEL >= 4 ) - console.log( chalk.white.bgGreen.bold( ` ${ name } ` ), chalk.white( `→` ), this.now(), chalk.greenBright( msg.join( ' ' ) ) ); + console.log( chalk.white.bgGreen.bold( ` ${ name } ` ), chalk.white( `✅` ), this.now(), chalk.greenBright( msg.join( ' ' ) ) ); } static notice( ...msg ) { if ( LOG_LEVEL >= 3 ) - console.log( chalk.white.bgYellow.bold( ` ${ name } ` ), chalk.white( `→` ), this.now(), chalk.yellowBright( msg.join( ' ' ) ) ); + console.log( chalk.white.bgYellow.bold( ` ${ name } ` ), chalk.white( `📌` ), this.now(), chalk.yellowBright( msg.join( ' ' ) ) ); } static warn( ...msg ) { if ( LOG_LEVEL >= 2 ) - console.warn( chalk.white.bgYellow.bold( ` ${ name } ` ), chalk.white( `→` ), this.now(), chalk.yellowBright( msg.join( ' ' ) ) ); + console.warn( chalk.white.bgYellow.bold( ` ${ name } ` ), chalk.white( `⚠️` ), this.now(), chalk.yellowBright( msg.join( ' ' ) ) ); } static error( ...msg ) { if ( LOG_LEVEL >= 1 ) - console.error( chalk.white.bgRedBright.bold( ` ${ name } ` ), chalk.white( `→` ), this.now(), chalk.redBright( msg.join( ' ' ) ) ); + console.error( chalk.white.bgRedBright.bold( ` ${ name } ` ), chalk.white( `❌` ), this.now(), chalk.redBright( msg.join( ' ' ) ) ); } } @@ -231,7 +243,6 @@ if ( process.pkg ) chalk.blueBright( `` ), chalk.gray( `Starting server utilizing process.execPath` ) ); const basePath = path.dirname( process.execPath ); - FILE_URL = path.join( basePath, envWebFolder, `${ envFileURL }` ); FILE_M3U = path.join( basePath, envWebFolder, `${ envFileM3U }` ); FILE_XML = path.join( basePath, envWebFolder, `${ envFileXML }` ); @@ -249,6 +260,64 @@ else FILE_GZP = path.resolve( __dirname, envWebFolder, `${ envFileGZP }` ); } +/* + helper > sleep +*/ + +function sleep( ms ) +{ + return new Promise( ( resolve ) => + { + setTimeout( resolve, ms ); + }); +} + +/* + Semaphore > Declare + + allows multiple threads to work with the same shared resources +*/ + +class Semaphore +{ + constructor( max ) + { + this.max = max; + this.queue = []; + this.active = 0; + } + + async acquire() + { + if ( this.active < this.max ) + { + this.active++; + return; + } + + return new Promise( ( resolve ) => this.queue.push( resolve ) ); + } + + release() + { + this.active--; + if ( this.queue.length > 0 ) + { + const resolve = this.queue.shift(); + this.active++; + resolve(); + } + } +} + +/* + Semaphore > Initialize + + @arg int threads_max +*/ + +const semaphore = new Semaphore( 5 ); + /* Get Client IP @@ -266,43 +335,6 @@ const clientIp = ( req ) => req.socket?.remoteAddress ) || envIpContainer ); -/* - -/* - Semaphore > Declare - - allows multiple threads to work with the same shared resources -*/ - -class Semaphore -{ - constructor( max ) - { - this.max = max; - this.queue = []; - this.active = 0; - } - async acquire() - { - if ( this.active < this.max ) - { - this.active++; - return; - } - return new Promise( ( resolve ) => this.queue.push( resolve ) ); - } - release() - { - this.active--; - if ( this.queue.length > 0 ) - { - const resolve = this.queue.shift(); - this.active++; - resolve(); - } - } -} - /* Check Service Status @@ -318,17 +350,17 @@ async function serviceCheck( service, uri ) try { - const response = await fetch( uri ); + const resp = await fetch( uri ); /* try 1 > domain down */ - if ( response.status !== 200 ) + if ( resp.status !== 200 ) { - Log.error( `ping`, chalk.redBright( `[response]` ), chalk.white( `❌` ), chalk.redBright( `` ), chalk.gray( `Service Offline; failed to communicate with service, possibly down` ), chalk.redBright( `` ), chalk.gray( `${ response.status }` ), chalk.redBright( `` ), chalk.gray( `${ service }` ), chalk.redBright( `
` ), chalk.gray( `${ uri }` ) ); + Log.error( `ping`, chalk.redBright( `[response]` ), chalk.white( `❌` ), chalk.redBright( `` ), chalk.gray( `Service Offline; failed to communicate with service, possibly down` ), chalk.redBright( `` ), chalk.gray( `${ resp.status }` ), chalk.redBright( `` ), chalk.gray( `${ service }` ), chalk.redBright( `
` ), chalk.gray( `${ uri }` ) ); return; } /* try 1 > domain up */ - Log.ok( `ping`, chalk.yellow( `[response]` ), chalk.white( `✅` ), chalk.greenBright( `` ), chalk.gray( `Service Online` ), chalk.greenBright( `` ), chalk.gray( `${ response.status }` ), chalk.greenBright( `` ), chalk.gray( `${ service }` ), chalk.greenBright( `
` ), chalk.gray( `${ uri }` ) ); + Log.ok( `ping`, chalk.yellow( `[response]` ), chalk.white( `✅` ), chalk.greenBright( `` ), chalk.gray( `Service Online` ), chalk.greenBright( `` ), chalk.gray( `${ resp.status }` ), chalk.greenBright( `` ), chalk.gray( `${ service }` ), chalk.greenBright( `
` ), chalk.gray( `${ uri }` ) ); } catch ( err ) { @@ -343,17 +375,17 @@ async function serviceCheck( service, uri ) try { - const response = await fetch( uriRetry ); + const resp = await fetch( uriRetry ); /* try 2 > http > domain down */ - if ( response.status !== 200 ) + if ( resp.status !== 200 ) { - Log.error( `ping`, chalk.redBright( `[response]` ), chalk.white( `❌` ), chalk.redBright( `` ), chalk.gray( `Service Offline; failed to communicate with service, possibly down` ), chalk.redBright( `` ), chalk.gray( `${ response.status }` ), chalk.redBright( `` ), chalk.gray( `${ service }` ), chalk.redBright( `
` ), chalk.gray( `${ uriRetry }` ) ); + Log.error( `ping`, chalk.redBright( `[response]` ), chalk.white( `❌` ), chalk.redBright( `` ), chalk.gray( `Service Offline; failed to communicate with service, possibly down` ), chalk.redBright( `` ), chalk.gray( `${ resp.status }` ), chalk.redBright( `` ), chalk.gray( `${ service }` ), chalk.redBright( `
` ), chalk.gray( `${ uriRetry }` ) ); return; } /* try 2 > http > domain up */ - Log.ok( `ping`, chalk.yellow( `[response]` ), chalk.white( `✅` ), chalk.greenBright( `` ), chalk.gray( `Service Online` ), chalk.greenBright( `` ), chalk.gray( `${ response.status }` ), chalk.greenBright( `` ), chalk.gray( `${ service }` ), chalk.greenBright( `
` ), chalk.gray( `${ uriRetry }` ) ); + Log.ok( `ping`, chalk.yellow( `[response]` ), chalk.white( `✅` ), chalk.greenBright( `` ), chalk.gray( `Service Online` ), chalk.greenBright( `` ), chalk.gray( `${ resp.status }` ), chalk.greenBright( `` ), chalk.gray( `${ service }` ), chalk.greenBright( `
` ), chalk.gray( `${ uriRetry }` ) ); } catch ( err ) { @@ -373,17 +405,17 @@ async function serviceCheck( service, uri ) try { - const response = await fetch( uriRetry ); + const resp = await fetch( uriRetry ); /* try 2 > https > domain down */ - if ( response.status !== 200 ) + if ( resp.status !== 200 ) { - Log.error( `ping`, chalk.redBright( `[response]` ), chalk.white( `❌` ), chalk.redBright( `` ), chalk.gray( `Service Offline; failed to communicate with service, possibly down` ), chalk.redBright( `` ), chalk.gray( `${ response.status }` ), chalk.redBright( `` ), chalk.gray( `${ service }` ), chalk.redBright( `
` ), chalk.gray( `${ uriRetry }` ) ); + Log.error( `ping`, chalk.redBright( `[response]` ), chalk.white( `❌` ), chalk.redBright( `` ), chalk.gray( `Service Offline; failed to communicate with service, possibly down` ), chalk.redBright( `` ), chalk.gray( `${ resp.status }` ), chalk.redBright( `` ), chalk.gray( `${ service }` ), chalk.redBright( `
` ), chalk.gray( `${ uriRetry }` ) ); return; } /* try 2 > https > domain up */ - Log.ok( `ping`, chalk.yellow( `[response]` ), chalk.white( `✅` ), chalk.greenBright( `` ), chalk.gray( `Service Online` ), chalk.greenBright( `` ), chalk.gray( `${ response.status }` ), chalk.greenBright( `` ), chalk.gray( `${ service }` ), chalk.greenBright( `
` ), chalk.gray( `${ uriRetry }` ) ); + Log.ok( `ping`, chalk.yellow( `[response]` ), chalk.white( `✅` ), chalk.greenBright( `` ), chalk.gray( `Service Online` ), chalk.greenBright( `` ), chalk.gray( `${ resp.status }` ), chalk.greenBright( `` ), chalk.gray( `${ service }` ), chalk.greenBright( `
` ), chalk.gray( `${ uriRetry }` ) ); } catch ( err ) { @@ -394,14 +426,6 @@ async function serviceCheck( service, uri ) } } -/* - Semaphore > Initialize - - @arg int threads_max -*/ - -const semaphore = new Semaphore( 5 ); - /* Func > Download File @@ -715,7 +739,7 @@ async function fetchRemote( url, req ) { return new Promise( ( resolve, reject ) => { - Log.info( `remo`, chalk.yellow( `[generate]` ), chalk.white( `ℹ️` ), + Log.info( `live`, chalk.yellow( `[generate]` ), chalk.white( `ℹ️` ), chalk.blueBright( `` ), chalk.gray( `Preparing to fetch remote request` ), chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), chalk.blueBright( `` ), chalk.gray( `${ url }` ) ); @@ -728,7 +752,7 @@ async function fetchRemote( url, req ) } }, ( resp ) => { - Log.info( `remo`, chalk.yellow( `[retrieve]` ), chalk.white( `ℹ️` ), + Log.info( `live`, chalk.yellow( `[retrieve]` ), chalk.white( `ℹ️` ), chalk.blueBright( `` ), chalk.gray( `Getting response from remote fetch request` ), chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), chalk.blueBright( `` ), chalk.gray( `${ resp.statusCode }` ), @@ -736,7 +760,7 @@ async function fetchRemote( url, req ) if ( resp.statusCode !== 200 ) { - Log.error( `remo`, chalk.redBright( `[retrieve]` ), chalk.white( `❌` ), + Log.error( `live`, chalk.redBright( `[retrieve]` ), chalk.white( `❌` ), chalk.redBright( `` ), chalk.gray( `Remote fetch returned status code other than 200` ), chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), chalk.redBright( `` ), chalk.gray( `${ resp.statusCode }` ), @@ -759,7 +783,7 @@ async function fetchRemote( url, req ) { if ( err ) { - Log.error( `remo`, chalk.redBright( `[retrieve]` ), chalk.white( `❌` ), + Log.error( `live`, chalk.redBright( `[retrieve]` ), chalk.white( `❌` ), chalk.redBright( `` ), chalk.gray( `Remote fetch could not complete encoding type ${ encoding }` ), chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), chalk.redBright( `` ), chalk.gray( `${ err }` ), @@ -770,7 +794,7 @@ async function fetchRemote( url, req ) return reject( err ); } - Log.debug( `remo`, chalk.yellow( `[retrieve]` ), chalk.white( `⚙️` ), + Log.debug( `live`, chalk.yellow( `[retrieve]` ), chalk.white( `⚙️` ), chalk.blueBright( `` ), chalk.gray( `Remote fetch detected encoding type ${ encoding }; decoding` ), chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), chalk.blueBright( `` ), chalk.gray( `${ encoding }` ), @@ -786,7 +810,7 @@ async function fetchRemote( url, req ) { if ( err ) { - Log.error( `remo`, chalk.redBright( `[retrieve]` ), chalk.white( `❌` ), + Log.error( `live`, chalk.redBright( `[retrieve]` ), chalk.white( `❌` ), chalk.redBright( `` ), chalk.gray( `Remote fetch could not complete encoding type ${ encoding }` ), chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), chalk.redBright( `` ), chalk.gray( `${ err }` ), @@ -797,7 +821,7 @@ async function fetchRemote( url, req ) return reject( err ); } - Log.debug( `remo`, chalk.yellow( `[retrieve]` ), chalk.white( `⚙️` ), + Log.debug( `live`, chalk.yellow( `[retrieve]` ), chalk.white( `⚙️` ), chalk.blueBright( `` ), chalk.gray( `Remote fetch detected encoding type ${ encoding }; decoding` ), chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), chalk.blueBright( `` ), chalk.gray( `${ encoding }` ), @@ -813,7 +837,7 @@ async function fetchRemote( url, req ) { if ( err ) { - Log.error( `remo`, chalk.redBright( `[retrieve]` ), chalk.white( `❌` ), + Log.error( `live`, chalk.redBright( `[retrieve]` ), chalk.white( `❌` ), chalk.redBright( `` ), chalk.gray( `Remote fetch could not complete encoding type ${ encoding } (brotli decompress)` ), chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), chalk.redBright( `` ), chalk.gray( `${ err }` ), @@ -824,7 +848,7 @@ async function fetchRemote( url, req ) return reject( err ); } - Log.debug( `remo`, chalk.yellow( `[retrieve]` ), chalk.white( `⚙️` ), + Log.debug( `live`, chalk.yellow( `[retrieve]` ), chalk.white( `⚙️` ), chalk.blueBright( `` ), chalk.gray( `Remote fetch detected encoding type ${ encoding } (brotli decompress); decoding` ), chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), chalk.blueBright( `` ), chalk.gray( `${ encoding }` ), @@ -836,7 +860,7 @@ async function fetchRemote( url, req ) } else { - Log.debug( `remo`, chalk.yellow( `[retrieve]` ), chalk.white( `⚙️` ), + Log.debug( `live`, chalk.yellow( `[retrieve]` ), chalk.white( `⚙️` ), chalk.blueBright( `` ), chalk.gray( `Remote fetch contains no headers to decode; resolving buffer` ), chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), chalk.blueBright( `` ), chalk.gray( `${ encoding }` ), @@ -875,6 +899,7 @@ async function serveKey( req, res ) method: req.method || 'GET', code: 400, uptime: Math.round( process.uptime() ), + uptimeHuman: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ), timestamp: Date.now() }; @@ -930,6 +955,7 @@ async function serveKey( req, res ) method: req.method || 'GET', code: 500, uptime: Math.round( process.uptime() ), + uptimeHuman: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ), timestamp: Date.now() }; @@ -1227,6 +1253,7 @@ async function serveM3UPlaylist( req, res ) method: req.method || 'GET', code: 404, uptime: Math.round( process.uptime() ), + uptimeHuman: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ), timestamp: Date.now() }; @@ -1317,6 +1344,7 @@ async function serveM3UPlaylist( req, res ) method: req.method || 'GET', code: 500, uptime: Math.round( process.uptime() ), + uptimeHuman: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ), timestamp: Date.now() }; @@ -1375,6 +1403,7 @@ async function serveM3UPlaylist( req, res ) method: req.method || 'GET', code: 500, uptime: Math.round( process.uptime() ), + uptimeHuman: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ), timestamp: Date.now() }; @@ -1424,6 +1453,7 @@ async function serveHealthCheck( req, res ) method: req.method || 'GET', code: 200, uptime: Math.round( process.uptime() ), + uptimeHuman: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ), timestamp: Date.now() }; @@ -1431,12 +1461,16 @@ async function serveHealthCheck( req, res ) 'Content-Type': 'application/json' }); - Log.ok( `/api`, chalk.yellow( `[health]` ), chalk.white( `✅` ), - chalk.greenBright( `` ), chalk.gray( `Response` ), - chalk.greenBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.greenBright( `` ), chalk.gray( `${ statusCheck.code }` ), - chalk.greenBright( `` ), chalk.gray( `${ statusCheck.status }` ), - chalk.greenBright( `` ), chalk.gray( `${ process.uptime() }` ) ); + const paramQuery = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'query' ); + if ( paramQuery !== 'query' ) + { + Log.ok( `/api`, chalk.yellow( `[health]` ), chalk.white( `✅` ), + chalk.greenBright( `` ), chalk.gray( `Response` ), + chalk.greenBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.greenBright( `` ), chalk.gray( `${ statusCheck.code }` ), + chalk.greenBright( `` ), chalk.gray( `${ statusCheck.status }` ), + chalk.greenBright( `` ), chalk.gray( `${ process.uptime() }` ) ); + } res.end( JSON.stringify( statusCheck ) ); return; @@ -1457,6 +1491,7 @@ async function serveHealthCheck( req, res ) method: req.method || 'GET', code: 503, uptime: Math.round( process.uptime() ), + uptimeHuman: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ), timestamp: Date.now() }; @@ -1587,6 +1622,7 @@ async function serveM3U( res, req ) method: req.method || 'GET', code: 500, uptime: Math.round( process.uptime() ), + uptimeHuman: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ), timestamp: Date.now() }; @@ -1652,6 +1688,7 @@ async function serveXML( res, req ) method: req.method || 'GET', code: 500, uptime: Math.round( process.uptime() ), + uptimeHuman: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ), timestamp: Date.now() }; @@ -1717,6 +1754,7 @@ async function serveGZP( res, req ) method: req.method || 'GET', code: 500, uptime: Math.round( process.uptime() ), + uptimeHuman: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ), timestamp: Date.now() }; @@ -1904,9 +1942,10 @@ async function initialize() FILE_GZP_MODIFIED = getFileModified( FILE_GZP ); const end = performance.now(); + TIME_STARTUP = `${ end - start }`; Log.info( `core`, chalk.yellow( `[initiate]` ), chalk.white( `ℹ️` ), chalk.blueBright( `` ), chalk.gray( `TVApp2 container is ready` ), - chalk.blueBright( `