From c8d68bdb2a8ee6eb4448aca2ca1f8f8e8189abd8 Mon Sep 17 00:00:00 2001 From: Aetherinox Date: Sun, 4 May 2025 01:47:01 -0700 Subject: [PATCH] feat: add detailed logging system --- tvapp2/index.js | 724 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 623 insertions(+), 101 deletions(-) diff --git a/tvapp2/index.js b/tvapp2/index.js index 34884f72..cfbd3dbf 100755 --- a/tvapp2/index.js +++ b/tvapp2/index.js @@ -217,7 +217,9 @@ class Log if ( process.pkg ) { - Log.info( `core`, chalk.yellow( `[init]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `Starting server utilizing process.execPath` ) ); + Log.info( `core`, chalk.yellow( `[initiate]` ), chalk.white( `ℹ️` ), + chalk.blueBright( `` ), chalk.gray( `Starting server utilizing process.execPath` ) ); + const basePath = path.dirname( process.execPath ); FILE_URL = path.join( basePath, FOLDER_WWW, `${ envFileURL }` ); @@ -228,7 +230,8 @@ if ( process.pkg ) } else { - Log.info( `core`, chalk.yellow( `[init]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `Starting server utilizing processed locals` ) ); + Log.info( `core`, chalk.yellow( `[initiate]` ), chalk.white( `ℹ️` ), + chalk.blueBright( `` ), chalk.gray( `Starting server utilizing processed locals` ) ); FILE_URL = path.resolve( __dirname, FOLDER_WWW, `${ envFileURL }` ); FILE_M3U = path.resolve( __dirname, FOLDER_WWW, `${ envFileM3U }` ); @@ -253,6 +256,8 @@ const clientIp = ( req ) => req.socket?.remoteAddress ) || envIpContainer ); +/* + /* Semaphore > Declare @@ -306,7 +311,10 @@ const semaphore = new Semaphore( 5 ); async function downloadFile( url, filePath ) { - Log.info( `netw`, chalk.yellow( `[start]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `Downloading external file` ), chalk.blueBright( `` ), chalk.gray( `${ url }` ), chalk.blueBright( `` ), chalk.gray( `${ filePath }` ) ); + Log.info( `file`, chalk.yellow( `[download]` ), chalk.white( `ℹ️` ), + chalk.blueBright( `` ), chalk.gray( `Downloading external file` ), + chalk.blueBright( `` ), chalk.gray( `${ url }` ), + chalk.blueBright( `` ), chalk.gray( `${ filePath }` ) ); return new Promise( ( resolve, reject ) => { @@ -318,19 +326,34 @@ async function downloadFile( url, filePath ) { if ( response.statusCode !== 200 ) { - Log.error( `netw`, chalk.yellow( `[error]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `Failed to download source file` ), chalk.blueBright( `` ), chalk.gray( `${ url }` ), chalk.blueBright( `` ), chalk.gray( `${ filePath }` ), chalk.blueBright( `` ), chalk.gray( `${ response.statusCode }` ) ); + Log.error( `file`, chalk.redBright( `[download]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `Attempt to download file returned non-200 status` ), + chalk.redBright( `` ), chalk.gray( `${ url }` ), + chalk.redBright( `` ), chalk.gray( `${ filePath }` ), + chalk.redBright( `` ), chalk.gray( `${ response.statusCode }` ) ); + return reject( new Error( `Failed to download file: ${ url }. Status code: ${ response.statusCode }` ) ); } response.pipe( file ); file.on( 'finish', () => { - Log.ok( `netw`, chalk.yellow( `[finish]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `Successfully downloaded and wrote new file` ), chalk.blueBright( `` ), chalk.gray( `${ url }` ), chalk.blueBright( `` ), chalk.gray( `${ filePath }` ) ); + Log.ok( `file`, chalk.yellow( `[download]` ), chalk.white( `✅` ), + chalk.greenBright( `` ), chalk.gray( `Successfully downloaded and wrote new file` ), + chalk.greenBright( `` ), chalk.gray( `${ url }` ), + chalk.greenBright( `` ), chalk.gray( `${ filePath }` ), + chalk.greenBright( `` ), chalk.gray( `${ response.statusCode }` ) ); + file.close( () => resolve( true ) ); }); }) .on( 'error', ( err ) => { - Log.error( `netw`, chalk.yellow( `[error]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `Failed to download source file` ), chalk.blueBright( `` ), chalk.gray( `${ err.message }`, chalk.blueBright( `` ), chalk.gray( `${ url }` ), chalk.blueBright( `` ), chalk.gray( `${ filePath }` ) ) ); + Log.error( `file`, chalk.redBright( `[download]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `Failed to download source file` ), + chalk.redBright( `` ), chalk.gray( `${ err.message }` ), + chalk.redBright( `` ), chalk.gray( `${ url }` ), + chalk.redBright( `` ), chalk.gray( `${ filePath }` ) ); + fs.unlink( filePath, () => reject( err ) ); }); }); @@ -411,17 +434,30 @@ async function getFile( url, filePath ) { try { + Log.debug( `file`, chalk.yellow( `[requests]` ), chalk.white( `⚙️` ), + chalk.blueBright( `` ), chalk.gray( `Requesting to download file` ), + chalk.blueBright( `` ), chalk.gray( `${ url }` ), + chalk.blueBright( `` ), chalk.gray( `${ filePath }` ) ); + await downloadFile( url, filePath ); } catch ( err ) { if ( fs.existsSync( filePath ) ) { - Log.warn( `netw`, chalk.yellow( `[get]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `Download failed - Using existing local file ${ filePath }` ), chalk.blueBright( `` ), chalk.gray( `${ url }` ), chalk.blueBright( `` ), chalk.gray( `${ filePath }` ) ); + Log.warn( `file`, chalk.yellow( `[requests]` ), chalk.white( `⚠️` ), + chalk.blueBright( `` ), chalk.gray( `Download failed - Using existing local file ${ filePath }` ), + chalk.blueBright( `` ), chalk.gray( `${ url }` ), + chalk.blueBright( `` ), chalk.gray( `${ filePath }` ) ); } else { - Log.error( `netw`, chalk.yellow( `[error]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `Download filed and no local backup exists, aborting` ), chalk.blueBright( `` ), chalk.redBright( `${ err.message }` ), chalk.blueBright( `` ), chalk.gray( `${ url }` ), chalk.blueBright( `` ), chalk.gray( `${ filePath }` ) ); + Log.error( `file`, chalk.redBright( `[requests]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `Download filed and no local backup exists, aborting` ), + chalk.redBright( `` ), chalk.gray( `${ err.message }` ), + chalk.redBright( `` ), chalk.gray( `${ url }` ), + chalk.redBright( `` ), chalk.gray( `${ filePath }` ) ); + throw err; } } @@ -435,38 +471,77 @@ async function getFile( url, filePath ) async function createGzip( ) { - Log.info( `gzip`, chalk.yellow( `[create]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `Preparing to create compressed XML gz file` ), chalk.blueBright( `` ), chalk.gray( `${ envFileXML }` ), chalk.blueBright( `` ), chalk.gray( `${ envFileGZP }` ) ); + Log.info( `gzip`, chalk.yellow( `[generate]` ), chalk.white( `ℹ️` ), + chalk.blueBright( `` ), chalk.gray( `Preparing to create compressed XML gz file` ), + chalk.blueBright( `` ), chalk.gray( `${ envFileXML }` ), + chalk.blueBright( `` ), chalk.gray( `${ envFileGZP }` ) ); + return new Promise( ( resolve, reject ) => { - Log.debug( `gzip`, chalk.yellow( `[create]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `Promise to create compressed gz started` ), chalk.blueBright( `` ), chalk.gray( `${ envFileXML }` ), chalk.blueBright( `` ), chalk.gray( `${ envFileGZP }` ) ); + Log.debug( `gzip`, chalk.yellow( `[generate]` ), chalk.white( `⚙️` ), + chalk.blueBright( `` ), chalk.gray( `Promise to create compressed gz started` ), + chalk.blueBright( `` ), chalk.gray( `${ envFileXML }` ), + chalk.blueBright( `` ), chalk.gray( `${ envFileGZP }` ) ); + fs.readFile( FILE_XML, ( err, buf ) => { - Log.debug( `gzip`, chalk.yellow( `[create]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `Reading source XML file` ), chalk.blueBright( `` ), chalk.gray( `${ envFileXML }` ), chalk.blueBright( `` ), chalk.gray( `${ envFileGZP }` ) ); + Log.debug( `gzip`, chalk.yellow( `[generate]` ), chalk.white( `⚙️` ), + chalk.blueBright( `` ), chalk.gray( `Reading source XML file` ), + chalk.blueBright( `` ), chalk.gray( `${ envFileXML }` ), + chalk.blueBright( `` ), chalk.gray( `${ envFileGZP }` ) ); + if ( err ) { - Log.error( `gzip`, chalk.yellow( `[create]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `Could not read source XML file` ), chalk.blueBright( `` ), chalk.redBright( `${ err }` ), chalk.blueBright( `` ), chalk.gray( `${ envFileXML }` ), chalk.blueBright( `` ), chalk.gray( `${ envFileGZP }` ) ); + Log.error( `gzip`, chalk.redBright( `[generate]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `Could not read source XML file` ), + chalk.redBright( `` ), chalk.gray( `${ err }` ), + chalk.redBright( `` ), chalk.gray( `${ envFileXML }` ), + chalk.redBright( `` ), chalk.gray( `${ envFileGZP }` ) ); + return reject( new Error( `Could not read file ${ envFileXML }. Error: ${ err }` ) ); } zlib.gzip( buf, ( err, buf ) => { - Log.debug( `gzip`, chalk.yellow( `[create]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `Starting zlib.gzip` ), chalk.blueBright( `` ), chalk.gray( `${ envFileXML }` ), chalk.blueBright( `` ), chalk.gray( `${ envFileGZP }` ) ); + Log.debug( `gzip`, chalk.yellow( `[generate]` ), chalk.white( `⚙️` ), + chalk.blueBright( `` ), chalk.gray( `Starting zlib.gzip` ), + chalk.blueBright( `` ), chalk.gray( `${ envFileXML }` ), + chalk.blueBright( `` ), chalk.gray( `${ envFileGZP }` ) ); + if ( err ) { - Log.error( `gzip`, chalk.yellow( `[create]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `Could not create gz archive` ), chalk.blueBright( `` ), chalk.redBright( `${ err }` ), chalk.blueBright( `` ), chalk.gray( `${ envFileXML }` ), chalk.blueBright( `` ), chalk.gray( `${ envFileGZP }` ) ); + Log.error( `gzip`, chalk.redBright( `[generate]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `Could not create gz archive` ), + chalk.redBright( `` ), chalk.gray( `${ err }` ), + chalk.redBright( `` ), chalk.gray( `${ envFileXML }` ), + chalk.redBright( `` ), chalk.gray( `${ envFileGZP }` ) ); + return reject( new Error( `Could not create ${ envFileGZP }. Error: ${ err }` ) ); } - Log.info( `gzip`, chalk.yellow( `[create]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `Started creating gz archive from XML source` ), chalk.blueBright( `` ), chalk.gray( `${ envFileXML }` ), chalk.blueBright( `` ), chalk.gray( `${ envFileGZP }` ) ); + Log.info( `gzip`, chalk.yellow( `[generate]` ), chalk.white( `ℹ️` ), + chalk.blueBright( `` ), chalk.gray( `Started creating gz archive from XML source` ), + chalk.blueBright( `` ), chalk.gray( `${ envFileXML }` ), + chalk.blueBright( `` ), chalk.gray( `${ envFileGZP }` ) ); + fs.writeFile( `${ FILE_GZP }`, buf, ( err ) => { if ( err ) { - Log.error( `gzip`, chalk.yellow( `[create]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `Could not write to and create gz archive` ), chalk.blueBright( `` ), chalk.redBright( `${ err }` ), chalk.blueBright( `` ), chalk.gray( `${ envFileXML }` ), chalk.blueBright( `` ), chalk.gray( `${ envFileGZP }` ) ); + Log.error( `gzip`, chalk.redBright( `[generate]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `Could not write to and create gz archive` ), + chalk.redBright( `` ), chalk.gray( `${ err }` ), + chalk.redBright( `` ), chalk.gray( `${ envFileXML }` ), + chalk.redBright( `` ), chalk.gray( `${ envFileGZP }` ) ); + return reject( new Error( `Could not write XML file ${ envFileXML } to ${ envFileGZP }. Error: ${ err }` ) ); } - Log.ok( `gzip`, chalk.yellow( `[create]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `Successfully created compressed gz archive from XML source file` ), chalk.blueBright( `` ), chalk.gray( `${ envFileXML }` ), chalk.blueBright( `` ), chalk.gray( `${ envFileGZP }` ) ); + Log.ok( `gzip`, chalk.yellow( `[generate]` ), chalk.white( `✅` ), + chalk.greenBright( `` ), chalk.gray( `Successfully created compressed gz archive from XML source file` ), + chalk.greenBright( `` ), chalk.gray( `${ envFileXML }` ), + chalk.greenBright( `` ), chalk.gray( `${ envFileGZP }` ) ); + resolve( true ); }); }); @@ -490,11 +565,17 @@ async function getGzip( ) { if ( fs.existsSync( FILE_XML ) ) { - Log.warn( `gzip`, chalk.yellow( `[compress]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.yellowBright( `Source xml file found, but gzip failed generate a compressed .gz fileL` ), chalk.blueBright( `` ), chalk.gray( `${ FILE_XML }` ) ); + Log.warn( `gzip`, chalk.yellow( `[compress]` ), chalk.white( `⚠️` ), + chalk.blueBright( `` ), chalk.yellowBright( `Source xml file found, but gzip failed generate a compressed .gz fileL` ), + chalk.blueBright( `` ), chalk.gray( `${ FILE_XML }` ) ); } else { - Log.error( `gzip`, chalk.yellow( `[error]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.redBright( `Source XML file not found; cannot create compressed gzip` ), chalk.blueBright( `` ), chalk.redBright( `${ err.message }` ), chalk.blueBright( `` ), chalk.gray( `${ FILE_XML }` ) ); + Log.error( `gzip`, chalk.redBright( `[compress]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `Source XML file not found; cannot create compressed gzip` ), + chalk.redBright( `` ), chalk.gray( `${ err.message }` ), + chalk.redBright( `` ), chalk.gray( `${ FILE_XML }` ) ); + throw err; } } @@ -521,7 +602,7 @@ async function getGzip( ) */ -async function fetchRemote( url ) +async function fetchRemote( url, req ) { return new Promise( ( resolve, reject ) => { @@ -533,9 +614,18 @@ async function fetchRemote( url ) } }, ( resp ) => { + Log.info( `conn`, chalk.yellow( `[retrieve]` ), chalk.white( `ℹ️` ), + chalk.blueBright( `` ), chalk.gray( `Processing encoding types for remote fetch` ), + chalk.blueBright( `` ), chalk.gray( `${ resp.statusCode }` ), + chalk.blueBright( `` ), chalk.gray( `${ url }` ) ); + if ( resp.statusCode !== 200 ) { - Log.error( `core`, chalk.yellow( `[fetch]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.redBright( `Server returned status code other than 200` ), chalk.blueBright( `` ), chalk.redBright( `${ resp.statusCode }` ), chalk.blueBright( `` ), chalk.gray( `${ url }` ) ); + Log.error( `conn`, chalk.redBright( `[retrieve]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `Server returned status code other than 200` ), + chalk.redBright( `` ), chalk.gray( `${ resp.statusCode }` ), + chalk.redBright( `` ), chalk.gray( `${ url }` ) ); + return reject( new Error( `HTTP ${ resp.statusCode } for ${ url }` ) ); } @@ -551,7 +641,24 @@ async function fetchRemote( url ) { zlib.gunzip( buffer, ( err, decoded ) => { - if ( err ) return reject( err ); + if ( err ) + { + Log.error( `conn`, chalk.redBright( `[retrieve]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `Could not complete encoding type ${ encoding }` ), + chalk.redBright( `` ), chalk.gray( `${ err }` ), + chalk.redBright( `` ), chalk.gray( `${ encoding }` ), + chalk.redBright( `` ), chalk.gray( `${ resp.statusCode }` ), + chalk.redBright( `` ), chalk.gray( `${ url }` ) ); + + return reject( err ); + } + + Log.debug( `conn`, chalk.yellow( `[retrieve]` ), chalk.white( `⚙️` ), + chalk.blueBright( `` ), chalk.gray( `Processing encoding type ${ encoding }` ), + chalk.blueBright( `` ), chalk.gray( `${ encoding }` ), + chalk.blueBright( `` ), chalk.gray( `${ resp.statusCode }` ), + chalk.blueBright( `` ), chalk.gray( `${ url }` ) ); + resolve( decoded ); }); } @@ -559,7 +666,24 @@ async function fetchRemote( url ) { zlib.inflate( buffer, ( err, decoded ) => { - if ( err ) return reject( err ); + if ( err ) + { + Log.error( `conn`, chalk.redBright( `[retrieve]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `Could not complete encoding type ${ encoding }` ), + chalk.redBright( `` ), chalk.gray( `${ err }` ), + chalk.redBright( `` ), chalk.gray( `${ encoding }` ), + chalk.redBright( `` ), chalk.gray( `${ resp.statusCode }` ), + chalk.redBright( `` ), chalk.gray( `${ url }` ) ); + + return reject( err ); + } + + Log.debug( `conn`, chalk.yellow( `[retrieve]` ), chalk.white( `⚙️` ), + chalk.blueBright( `` ), chalk.gray( `Processing encoding type ${ encoding }` ), + chalk.blueBright( `` ), chalk.gray( `${ encoding }` ), + chalk.blueBright( `` ), chalk.gray( `${ resp.statusCode }` ), + chalk.blueBright( `` ), chalk.gray( `${ url }` ) ); + resolve( decoded ); }); } @@ -567,7 +691,24 @@ async function fetchRemote( url ) { zlib.brotliDecompress( buffer, ( err, decoded ) => { - if ( err ) return reject( err ); + if ( err ) + { + Log.error( `conn`, chalk.redBright( `[retrieve]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `Could not complete encoding type ${ encoding } (brotli decompress)` ), + chalk.redBright( `` ), chalk.gray( `${ err }` ), + chalk.redBright( `` ), chalk.gray( `${ encoding }` ), + chalk.redBright( `` ), chalk.gray( `${ resp.statusCode }` ), + chalk.redBright( `` ), chalk.gray( `${ url }` ) ); + + return reject( err ); + } + + Log.debug( `conn`, chalk.yellow( `[retrieve]` ), chalk.white( `⚙️` ), + chalk.blueBright( `` ), chalk.gray( `Processing encoding type ${ encoding } (brotli decompress)` ), + chalk.blueBright( `` ), chalk.gray( `${ encoding }` ), + chalk.blueBright( `` ), chalk.gray( `${ resp.statusCode }` ), + chalk.blueBright( `` ), chalk.gray( `${ url }` ) ); + resolve( decoded ); }); } @@ -585,8 +726,8 @@ async function serveKey( req, res ) { try { - const uriParam = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'uri' ); - if ( !uriParam ) + const paramUrl = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'uri' ); + if ( !paramUrl ) { const statusCheck = { @@ -606,16 +747,38 @@ async function serveKey( req, res ) 'Content-Type': 'application/json' }); - Log.error( `key`, chalk.yellow( `[error]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.redBright( `${ statusCheck.message }` ), chalk.blueBright( `` ), chalk.gray( `${ req.url }` ), chalk.blueBright( `` ), chalk.gray( `${ statusCheck.code }` ) ); + Log.error( `keys`, chalk.redBright( `[response]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `${ statusCheck.message }` ), + chalk.redBright( `` ), chalk.gray( `serveKey` ), + chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.redBright( `` ), chalk.gray( `${ statusCheck.code }` ), + chalk.redBright( `` ), chalk.gray( `${ req.url }` ), + chalk.redBright( `` ), chalk.gray( `empty; missing var` ) ); return res.end( JSON.stringify( statusCheck ) ); } - const keyData = await fetchRemote( uriParam ); + Log.debug( `conn`, chalk.yellow( `[response]` ), chalk.white( `⚙️` ), + chalk.blueBright( `` ), chalk.gray( `Valid paramUrl specified; establishing connection` ), + chalk.blueBright( `` ), chalk.gray( `serveKey` ), + chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.blueBright( `` ), chalk.gray( `${ req.url }` ), + chalk.blueBright( `` ), chalk.gray( `${ paramUrl }` ) ); + + const keyData = await fetchRemote( paramUrl, req ); res.writeHead( 200, { 'Content-Type': 'application/octet-stream' }); + Log.ok( `key`, chalk.yellow( `[response]` ), chalk.white( `✅` ), + chalk.greenBright( `` ), chalk.greenBright( `Found valid uri parameter` ), + chalk.greenBright( `` ), chalk.gray( `serveKey` ), + chalk.greenBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.greenBright( `` ), chalk.gray( `200` ), + chalk.greenBright( `` ), chalk.gray( `${ req.url }` ), + chalk.greenBright( `` ), chalk.gray( `${ paramUrl }` ), + chalk.greenBright( `` ), chalk.gray( `${ keyData }` ) ); + res.end( keyData ); } catch ( err ) @@ -625,7 +788,7 @@ async function serveKey( req, res ) ip: envIpContainer, gateway: envIpGateway, client: clientIp( req ), - message: `Failed to serve key`, + message: `Failed to serve key; try{} failed`, error: `${ err.message }`, status: 'unhealthy', ref: req.url, @@ -639,7 +802,13 @@ async function serveKey( req, res ) 'Content-Type': 'application/json' }); - Log.error( `key`, chalk.yellow( `[error]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.redBright( `${ statusCheck.message }` ), chalk.blueBright( `` ), chalk.redBright( `${ statusCheck.error }` ), chalk.blueBright( `` ), chalk.gray( `${ req.url }` ), chalk.blueBright( `` ), chalk.gray( `${ statusCheck.code }` ) ); + Log.error( `key`, chalk.yellow( `[response]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `${ statusCheck.message }` ), + chalk.redBright( `` ), chalk.gray( `serveKey` ), + chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.redBright( `` ), chalk.gray( `${ statusCheck.code }` ), + chalk.redBright( `` ), chalk.gray( `${ statusCheck.error }` ), + chalk.redBright( `` ), chalk.gray( `${ req.url }` ) ); res.end( JSON.stringify( statusCheck ) ); } @@ -687,13 +856,29 @@ function fetchPage( url ) https .get( url, opts, ( res ) => { + Log.info( `http`, chalk.yellow( `[retrieve]` ), chalk.white( `ℹ️` ), + chalk.blueBright( `` ), chalk.gray( `status code returned` ), + chalk.blueBright( `` ), chalk.gray( `${ url }` ), + chalk.blueBright( `` ), chalk.gray( `${ res.statusCode }` ) ); + if ( res.statusCode !== 200 ) { - return reject( new Error( `Non-200 status ${ res.statusCode } => ${ url }` ) ); + Log.debug( `http`, chalk.yellow( `[retrieve]` ), chalk.white( `⚙️` ), + chalk.blueBright( `` ), chalk.gray( `failed to load url; status 200 was not returned` ), + chalk.blueBright( `` ), chalk.gray( `${ url }` ), + chalk.blueBright( `` ), chalk.gray( `${ res.statusCode }` ) ); + + return reject( new Error( `page did not return code 200 status ${ res.statusCode } => ${ url }` ) ); } if ( res.headers['set-cookie']) { + Log.debug( `http`, chalk.yellow( `[retrieve]` ), chalk.white( `⚙️` ), + chalk.blueBright( `` ), chalk.gray( `setting headers` ), + chalk.blueBright( `` ), chalk.gray( `${ url }` ), + chalk.blueBright( `
` ), chalk.gray( `set-cookie` ), + chalk.blueBright( `` ), chalk.gray( `${ res.statusCode }` ) ); + parseSetCookieHeaders( res.headers['set-cookie']); } @@ -705,7 +890,7 @@ function fetchPage( url ) }); } -async function getTokenizedUrl( channelUrl ) +async function getTokenizedUrl( channelUrl, req ) { try { @@ -714,6 +899,11 @@ async function getTokenizedUrl( channelUrl ) let streamName; let streamHost; + Log.debug( `m3u8`, chalk.yellow( `[tokenize]` ), chalk.white( `⚙️` ), + chalk.blueBright( `` ), chalk.gray( `Attempting to tokenize url ` ), + chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.blueBright( `` ), chalk.gray( `${ channelUrl }` ) ); + if ( channelUrl.includes( 'espn-' ) ) { streamName = 'ESPN'; @@ -727,10 +917,23 @@ async function getTokenizedUrl( channelUrl ) const streamNameMatch = html.match( /id="stream_name" name="([^"]+)"/ ); if ( !streamNameMatch ) { - Log.error( `playlist`, chalk.yellow( `[error]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.redBright( `Cannot find "stream_name` ), chalk.blueBright( `` ), chalk.grey( `${ channelUrl }` ) ); + Log.error( `m3u8`, chalk.yellow( `[tokenize]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `Cannot find streamNameMatch; returned empty` ), + chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.redBright( `` ), chalk.grey( `${ channelUrl }` ), + chalk.redBright( `` ), chalk.grey( `missing / var empty` ) ); + return null; } + streamName = streamNameMatch[1]; + + Log.debug( `m3u8`, chalk.yellow( `[tokenize]` ), chalk.white( `⚙️` ), + chalk.blueBright( `` ), chalk.gray( `streamName found` ), + chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.blueBright( `` ), chalk.gray( `${ channelUrl }` ), + chalk.blueBright( `` ), chalk.gray( `not yet assigned` ), + chalk.blueBright( `` ), chalk.gray( `${ streamName }` ) ); } if ( channelUrl.match( 'tvpass\.org' ) ) @@ -745,39 +948,89 @@ async function getTokenizedUrl( channelUrl ) const tokenUrl = `https://${ streamHost }/token/${ streamName }?quality=${ envStreamQuality.toLowerCase() }`; const tokenResponse = await fetchPage( tokenUrl ); - let finalUrl; + let tokenizedUrl; - Log.debug( `playlist`, chalk.yellow( `[tokenize]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `Generating tokenized final stream URL` ), chalk.blueBright( `` ), chalk.gray( `${ streamName }` ), chalk.blueBright( `` ), chalk.gray( `${ envStreamQuality }` ), chalk.blueBright( `` ), chalk.gray( `${ streamHost }` ) ); + Log.debug( `m3u8`, chalk.yellow( `[tokenize]` ), chalk.white( `⚙️` ), + chalk.blueBright( `` ), chalk.gray( `Generating tokenized final stream URL` ), + chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.blueBright( `` ), chalk.gray( `${ channelUrl }` ), + chalk.blueBright( `` ), chalk.gray( `${ streamHost }` ), + chalk.blueBright( `` ), chalk.gray( `${ streamName }` ), chalk.blueBright( `` ), chalk.gray( `${ envStreamQuality.toLowerCase() }` ), + chalk.blueBright( `` ), chalk.gray( `${ tokenUrl }` ), + chalk.blueBright( `` ), chalk.gray( `not yet assigned` ) ); try { const json = JSON.parse( tokenResponse ); - finalUrl = json.url; + tokenizedUrl = json.url; + + Log.debug( `m3u8`, chalk.yellow( `[tokenize]` ), chalk.white( `⚙️` ), + chalk.blueBright( `` ), chalk.gray( `Returned token response in json format` ), + chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.blueBright( `` ), chalk.gray( `${ channelUrl }` ), + chalk.blueBright( `` ), chalk.gray( `${ streamHost }` ), + chalk.blueBright( `` ), chalk.gray( `${ streamName }` ), chalk.blueBright( `` ), chalk.gray( `${ envStreamQuality.toLowerCase() }` ), + chalk.blueBright( `` ), chalk.gray( `${ tokenUrl }` ), + chalk.blueBright( `` ), chalk.gray( `${ json }` ), + chalk.blueBright( `` ), chalk.gray( `${ tokenizedUrl }` ) ); } catch ( err ) { - Log.error( `playlist`, chalk.yellow( `[error]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.redBright( `Failed to parse token JSON for channel` ), chalk.blueBright( `` ), chalk.redBright( `${ err.message }` ), chalk.blueBright( `` ), chalk.gray( `${ channelUrl }` ) ); + Log.error( `m3u8`, chalk.redBright( `[tokenize]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `Failed to parse token JSON for channel` ), + chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.redBright( `` ), chalk.gray( `${ channelUrl }` ), + chalk.redBright( `` ), chalk.gray( `${ streamHost }` ), + chalk.redBright( `` ), chalk.gray( `${ streamName }` ), chalk.redBright( `` ), chalk.gray( `${ envStreamQuality.toLowerCase() }` ), + chalk.redBright( `` ), chalk.gray( `${ tokenUrl }` ), + chalk.redBright( `` ), chalk.gray( `not yet assigned` ) ); + return null; } - if ( !finalUrl ) + if ( !tokenizedUrl ) { - Log.error( `playlist`, chalk.yellow( `[error]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.redBright( `No URL found in token JSON for channel` ), chalk.blueBright( `` ), chalk.gray( `${ channelUrl }` ) ); + Log.error( `m3u8`, chalk.redBright( `[tokenize]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `No URL found in token JSON for channel` ), + chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.redBright( `` ), chalk.gray( `${ channelUrl }` ), + chalk.redBright( `` ), chalk.gray( `${ streamHost }` ), + chalk.redBright( `` ), chalk.gray( `${ streamName }` ), chalk.redBright( `` ), chalk.gray( `${ envStreamQuality.toLowerCase() }` ), + chalk.redBright( `` ), chalk.gray( `${ tokenUrl }` ), + chalk.redBright( `` ), chalk.gray( `missing` ) ); + return null; } - Log.debug( `playlist`, chalk.yellow( `[tokenize]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `Completed generated tokenized final stream URL` ), chalk.blueBright( `` ), chalk.gray( `${ streamName }` ), chalk.blueBright( `` ), chalk.gray( `${ envStreamQuality }` ), chalk.blueBright( `` ), chalk.gray( `${ streamHost }` ), chalk.blueBright( `` ), chalk.gray( `${ finalUrl }` ) ); - chalk.blueBright( `` ), chalk.gray( `${ envStreamQuality.toLowerCase() }` ), - return finalUrl; + Log.ok( `m3u8`, chalk.yellow( `[tokenize]` ), chalk.white( `✅` ), + chalk.greenBright( `` ), chalk.gray( `Successfully generated token for stream` ), + chalk.greenBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.greenBright( `` ), chalk.gray( `${ channelUrl }` ), + chalk.greenBright( `` ), chalk.gray( `${ streamHost }` ), + chalk.greenBright( `` ), chalk.gray( `${ streamName }` ), + chalk.greenBright( `` ), chalk.gray( `${ envStreamQuality.toLowerCase() }` ), + chalk.greenBright( `` ), chalk.gray( `${ streamHost }` ), + chalk.greenBright( `` ), chalk.gray( `${ tokenizedUrl }` ), + chalk.greenBright( `` ), chalk.gray( `${ tokenUrl }` ) ); + + return tokenizedUrl; } catch ( err ) { - Log.error( `playlist`, chalk.yellow( `[error]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.redBright( `Fatal error fetching token` ), chalk.blueBright( `` ), chalk.redBright( `${ err.message }` ), chalk.blueBright( `` ), chalk.grey( `${ channelUrl }` ) ); - chalk.redBright( `` ), chalk.gray( `${ envStreamQuality.toLowerCase() }` ), + Log.error( `m3u8`, chalk.redBright( `[tokenize]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `Fatal error fetching token` ), + chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.redBright( `` ), chalk.gray( `${ err.message }` ), + chalk.redBright( `` ), chalk.gray( `${ channelUrl }` ), + chalk.redBright( `` ), chalk.gray( `${ streamHost }` ), + chalk.redBright( `` ), chalk.gray( `${ streamName }` ), + chalk.redBright( `` ), chalk.gray( `${ envStreamQuality.toLowerCase() }` ), + chalk.redBright( `` ), chalk.gray( `${ tokenUrl }` ) ); + return null; } } @@ -787,15 +1040,19 @@ async function serveM3UPlaylist( req, res ) await semaphore.acquire(); try { - const urlParam = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'url' ); - if ( !urlParam ) + /* + paramUrl > decodedUrl > tokenizedUrl + */ + + const paramUrl = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'url' ); + if ( !paramUrl ) { const statusCheck = { ip: envIpContainer, gateway: envIpGateway, client: clientIp( req ), - message: `Missing ?url= parameter`, + message: `Missing ?url= parameter for var paramUrl`, status: `unhealthy`, ref: req.url, method: req.method || 'GET', @@ -808,23 +1065,37 @@ async function serveM3UPlaylist( req, res ) 'Content-Type': 'application/json' }); - Log.error( `channel`, chalk.yellow( `[error]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.redBright( `${ statusCheck.message }` ), chalk.blueBright( `` ), chalk.grey( `http://${ req.headers.host }/channel?url=XXXX` ), chalk.blueBright( `` ), chalk.gray( `${ statusCheck.code }` ) ); + Log.error( `m3u8`, chalk.redBright( `[response]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `${ statusCheck.message }` ), + chalk.redBright( `` ), chalk.gray( `serveM3UPlaylist` ), + chalk.redBright( `` ), chalk.gray( `${ statusCheck.code }` ), + chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.redBright( `` ), chalk.gray( `http://${ req.headers.host }/channel?url=XXXX` ) ); res.end( JSON.stringify( statusCheck ) ); return; } - const decodedUrl = decodeURIComponent( urlParam ); + const decodedUrl = decodeURIComponent( paramUrl ); if ( decodedUrl.endsWith( '.ts' ) ) { res.writeHead( 302, { Location: decodedUrl }); + + Log.notice( `m3u8`, chalk.yellow( `[response]` ), chalk.white( `📌` ), + chalk.yellowBright( `` ), chalk.gray( `decodedUrl ends with .ts script; (302) redirecting` ), + chalk.yellowBright( `` ), chalk.gray( `serveM3UPlaylist` ), + chalk.yellowBright( `` ), chalk.gray( `302` ), + chalk.yellowBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.yellowBright( `` ), chalk.gray( `${ paramUrl }` ), + chalk.yellowBright( `` ), chalk.gray( `${ decodedUrl }` ) ); + res.end(); return; } - const cachedUrl = getCache( decodedUrl ); + const cachedUrl = getCache( decodedUrl, req ); if ( cachedUrl ) { const rewrittenPlaylist = await rewriteM3U( cachedUrl, req ); @@ -834,16 +1105,32 @@ async function serveM3UPlaylist( req, res ) 'Content-Disposition': 'inline; filename="' + envFileM3U }); - Log.debug( `playlist`, chalk.yellow( `[fetch]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.redBright( `retrieving cached playlist` ), chalk.blueBright( `` ), chalk.gray( `${ cachedUrl }` ), chalk.blueBright( `` ), chalk.gray( `${ urlParam }` ), chalk.blueBright( `` ), chalk.gray( `200` ) ); + Log.debug( `m3u8`, chalk.yellow( `[response]` ), chalk.white( `⚙️` ), + chalk.blueBright( `` ), chalk.gray( `serving already cached m3u playlist to client` ), + chalk.blueBright( `` ), chalk.gray( `serveM3UPlaylist` ), + chalk.blueBright( `` ), chalk.gray( `200` ), + chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.blueBright( `` ), chalk.gray( `${ paramUrl }` ), + chalk.blueBright( `` ), chalk.gray( `${ decodedUrl }` ), + chalk.blueBright( `` ), chalk.gray( `${ cachedUrl }` ) ); res.end( rewrittenPlaylist ); return; } - Log.info( `playlist`, chalk.yellow( `[fetch]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `${ urlParam }` ) ); + Log.info( `m3u8`, chalk.yellow( `[response]` ), chalk.white( `ℹ️` ), + chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.blueBright( `` ), chalk.gray( `serveM3UPlaylist` ), + chalk.blueBright( `` ), chalk.gray( `${ paramUrl }` ), + chalk.blueBright( `` ), chalk.gray( `${ decodedUrl }` ), + chalk.blueBright( `` ), chalk.gray( `${ cachedUrl }` ) ); - const finalUrl = await getTokenizedUrl( decodedUrl ); - if ( !finalUrl ) + /* + get tokenized url + */ + + const tokenizedUrl = await getTokenizedUrl( decodedUrl, req ); + if ( !tokenizedUrl ) { const statusCheck = { @@ -863,23 +1150,40 @@ async function serveM3UPlaylist( req, res ) 'Content-Type': 'application/json' }); - Log.error( `playlist`, chalk.yellow( `[error]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.redBright( `${ statusCheck.message }` ), chalk.blueBright( `` ), chalk.gray( `${ urlParam }` ), chalk.blueBright( `` ), chalk.gray( `${ statusCheck.code }` ) ); + Log.error( `m3u8`, chalk.redBright( `[response]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `${ statusCheck.message }` ), + chalk.redBright( `` ), chalk.gray( `serveM3UPlaylist` ), + chalk.redBright( `` ), chalk.gray( `${ statusCheck.code }` ), + chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.redBright( `` ), chalk.gray( `${ decodedUrl }` ), + chalk.redBright( `` ), chalk.gray( `${ tokenizedUrl }` ) ); res.end( JSON.stringify( statusCheck ) ); return; } - setCache( decodedUrl, finalUrl, 4 * 60 * 60 * 1000 ); - const hdUrl = finalUrl.replace( 'tracks-v2a1', 'tracks-v1a1' ); + setCache( decodedUrl, tokenizedUrl, 4 * 60 * 60 * 1000, req ); + const hdUrl = tokenizedUrl.replace( 'tracks-v2a1', 'tracks-v1a1' ); const rewrittenPlaylist = await rewriteM3U( hdUrl, req ); + res.writeHead( 200, { 'Content-Type': 'application/vnd.apple.mpegurl', 'Content-Disposition': 'inline; filename="' + envFileM3U }); res.end( rewrittenPlaylist ); - Log.ok( `Served playlist` ); + + Log.ok( `m3u8`, chalk.yellow( `[response]` ), chalk.white( `✅` ), + chalk.greenBright( `` ), chalk.gray( `Serving new playlist to client` ), + chalk.greenBright( `` ), chalk.gray( `serveM3UPlaylist` ), + chalk.greenBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.greenBright( `` ), chalk.gray( `200` ), + chalk.greenBright( `` ), chalk.gray( `${ envFileM3U }` ), + chalk.greenBright( `` ), chalk.gray( `${ paramUrl }` ), + chalk.greenBright( `` ), chalk.gray( `${ decodedUrl }` ), + chalk.greenBright( `` ), chalk.gray( `${ tokenizedUrl }` ), + chalk.greenBright( `` ), chalk.gray( `${ hdUrl }` ) ); } catch ( err ) { @@ -904,7 +1208,11 @@ async function serveM3UPlaylist( req, res ) 'Content-Type': 'application/json' }); - Log.error( `playlist`, chalk.yellow( `[error]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.redBright( `${ statusCheck.message }` ), chalk.blueBright( `` ), chalk.redBright( `${ statusCheck.message }` ), chalk.blueBright( `` ), chalk.gray( `${ statusCheck.code }` ) ); + Log.error( `m3u8`, chalk.redBright( `[response]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `Failed to serve m3u playlist` ), + chalk.redBright( `` ), chalk.gray( `serveM3UPlaylist` ), + chalk.redBright( `` ), chalk.gray( `${ statusCheck.message }` ), + chalk.redBright( `` ), chalk.gray( `${ statusCheck.code }` ) ); res.end( JSON.stringify( statusCheck ) ); } @@ -920,10 +1228,11 @@ async function serveHealthCheck( req, res ) await semaphore.acquire(); try { - const urlParam = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'api' ); - if ( !urlParam ) + const paramUrl = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'api' ); + if ( !paramUrl ) { - Log.debug( `health`, chalk.yellow( `[api]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `No API key passed to health check` ) ); + Log.debug( `/api`, chalk.yellow( `[health]` ), chalk.white( `⚙️` ), + chalk.blueBright( `` ), chalk.gray( `No API key passed to health check` ) ); } const statusCheck = @@ -944,7 +1253,12 @@ async function serveHealthCheck( req, res ) 'Content-Type': 'application/json' }); - Log.ok( `health`, chalk.yellow( `[api]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `health check returned` ), chalk.greenBright( `${ statusCheck.status }` ), chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), chalk.blueBright( `` ), chalk.gray( `${ statusCheck.code }` ), chalk.blueBright( `` ), chalk.gray( Math.round( process.uptime() ) ) ); + Log.ok( `/api`, chalk.yellow( `[health]` ), chalk.white( `✅` ), + chalk.greenBright( `` ), chalk.gray( `health check response` ), + chalk.greenBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.greenBright( `` ), chalk.gray( `${ statusCheck.status }` ), + chalk.greenBright( `` ), chalk.gray( `${ statusCheck.code }` ), + chalk.greenBright( `` ), chalk.gray( `${ process.uptime() }` ) ); res.end( JSON.stringify( statusCheck ) ); return; @@ -972,7 +1286,13 @@ async function serveHealthCheck( req, res ) 'Content-Type': 'application/json' }); - Log.error( `health`, chalk.yellow( `[error]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `${ statusCheck.message }; returned` ), chalk.redBright( `${ statusCheck.status }` ), chalk.blueBright( `` ), chalk.redBright( `${ err.message }` ), chalk.blueBright( `` ), chalk.gray( `${ statusCheck.code }` ), chalk.blueBright( `` ), chalk.gray( `${ process.uptime() }` ) ); + Log.error( `/api`, chalk.redBright( `[health]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `${ statusCheck.message } response` ), + chalk.redBright( `` ), chalk.gray( `${ statusCheck.status }` ), + chalk.redBright( `` ), chalk.gray( `${ statusCheck.code }` ), + chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.redBright( `` ), chalk.gray( `${ err.message }` ), + chalk.redBright( `` ), chalk.gray( `${ process.uptime() }` ) ); res.end( JSON.stringify( statusCheck ) ); } @@ -1039,6 +1359,15 @@ async function serveM3U( res, req ) 'Content-Disposition': 'inline; filename="' + envFileM3U }); + Log.ok( `m3u8`, chalk.yellow( `[response]` ), chalk.white( `✅` ), + chalk.greenBright( `` ), chalk.gray( `Successfully served m3u8 channel playlist data` ), + chalk.greenBright( `` ), chalk.gray( `serveM3U` ), + chalk.greenBright( `` ), chalk.gray( `200` ), + chalk.greenBright( `` ), chalk.gray( `${ host }` ), + chalk.greenBright( `` ), chalk.gray( `${ req.url }` ), + chalk.greenBright( `` ), chalk.gray( `${ FILE_M3U }` ), + chalk.greenBright( `` ), chalk.gray( `${ envFileM3U }` ) ); + res.end( updatedContent ); } catch ( err ) @@ -1048,7 +1377,7 @@ async function serveM3U( res, req ) ip: envIpContainer, gateway: envIpGateway, client: clientIp( req ), - message: `Fatal error serving playlist`, + message: `Fatal serving m3u8 channel playlist data`, error: `${ err.message }`, status: 'unhealthy', ref: req.url, @@ -1058,12 +1387,19 @@ async function serveM3U( res, req ) timestamp: Date.now() }; - Log.error( `playlist`, chalk.yellow( `[serve]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.redBright( `${ statusCheck.message }` ), chalk.blueBright( `` ), chalk.redBright( `${ statusCheck.message }` ), chalk.blueBright( `` ), chalk.gray( `${ req.url }` ), chalk.blueBright( `` ), chalk.gray( `${ statusCheck.code }` ) ); - res.writeHead( statusCheck.code, { 'Content-Type': 'application/json' }); + Log.error( `m3u8`, chalk.yellow( `[response]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `${ statusCheck.message }` ), + chalk.redBright( `` ), chalk.gray( `serveM3U` ), + chalk.redBright( `` ), chalk.gray( `${ statusCheck.code }` ), + chalk.redBright( `` ), chalk.gray( `${ err.message }` ), + chalk.redBright( `` ), chalk.gray( `${ req.url }` ), + chalk.redBright( `` ), chalk.gray( `${ FILE_XML }` ), + chalk.redBright( `` ), chalk.gray( `${ envFileXML }` ) ); + res.end( JSON.stringify( statusCheck ) ); } } @@ -1086,17 +1422,49 @@ async function serveXML( response, req ) 'Content-Disposition': 'inline; filename="' + envFileXML }); + Log.ok( `xml`, chalk.yellow( `[response]` ), chalk.white( `✅` ), + chalk.greenBright( `` ), chalk.gray( `Successfully served uncompressed xml / epg guide data` ), + chalk.greenBright( `` ), chalk.gray( `serveXML` ), + chalk.greenBright( `` ), chalk.gray( `200` ), + chalk.greenBright( `` ), chalk.gray( `${ host }` ), + chalk.greenBright( `` ), chalk.gray( `${ req.url }` ), + chalk.greenBright( `` ), chalk.gray( `${ FILE_XML }` ), + chalk.greenBright( `` ), chalk.gray( `${ envFileXML }` ) ); + response.end( formattedContent ); } catch ( err ) { - Log.error( `playlist`, chalk.yellow( `[serve]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.redBright( `Fatal serving xml / epg guide data` ), chalk.blueBright( `` ), chalk.redBright( `${ err.message }` ), chalk.blueBright( `` ), chalk.gray( `${ req.url }` ) ); - response.writeHead( 500, { + const statusCheck = + { + ip: envIpContainer, + gateway: envIpGateway, + client: clientIp( req ), + message: `Fatal serving uncompressed xml / epg guide data`, + error: `${ err.message }`, + status: 'unhealthy', + ref: req.url, + method: req.method || 'GET', + code: 500, + uptime: Math.round( process.uptime() ), + timestamp: Date.now() + }; + + response.writeHead( statusCheck.code, { 'Content-Type': 'text/plain' }); - response.end( `Error serving xml/epg guide data: ${ err.message }` ); + Log.error( `xml`, chalk.yellow( `[response]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `${ statusCheck.message }` ), + chalk.redBright( `` ), chalk.gray( `serveXML` ), + chalk.redBright( `` ), chalk.gray( `${ statusCheck.code }` ), + chalk.redBright( `` ), chalk.gray( `${ err.message }` ), + chalk.redBright( `` ), chalk.gray( `${ req.url }` ), + chalk.redBright( `` ), chalk.gray( `${ FILE_XML }` ), + chalk.redBright( `` ), chalk.gray( `${ envFileXML }` ) ); + + res.end( JSON.stringify( statusCheck ) ); } }; @@ -1118,21 +1486,57 @@ async function serveGZP( response, req ) 'Content-Disposition': 'inline; filename="' + envFileGZP }); + Log.ok( `gzip`, chalk.yellow( `[response]` ), chalk.white( `✅` ), + chalk.greenBright( `` ), chalk.gray( `Successfully served compressed gzip xml/epg guide data` ), + chalk.greenBright( `` ), chalk.gray( `serveGZP` ), + chalk.greenBright( `` ), chalk.gray( `200` ), + chalk.greenBright( `` ), chalk.gray( `${ host }` ), + chalk.greenBright( `` ), chalk.gray( `${ req.url }` ), + chalk.greenBright( `` ), chalk.gray( `${ FILE_GZP }` ), + chalk.greenBright( `` ), chalk.gray( `${ envFileGZP }` ) ); + response.end( formattedContent ); } catch ( err ) { - Log.error( `playlist`, chalk.yellow( `[serve]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.redBright( `Fatal serving compressed gzip file` ), chalk.blueBright( `` ), chalk.redBright( `${ err.message }` ), chalk.blueBright( `` ), chalk.gray( `${ req.url }` ) ); - response.writeHead( 500, { + const statusCheck = + { + ip: envIpContainer, + gateway: envIpGateway, + client: clientIp( req ), + message: `Fatal serving compressed gzip xml/epg guide data`, + error: `${ err.message }`, + status: 'unhealthy', + ref: req.url, + method: req.method || 'GET', + code: 500, + uptime: Math.round( process.uptime() ), + timestamp: Date.now() + }; + + response.writeHead( statusCheck.code, { 'Content-Type': 'text/plain' }); - response.end( `Error serving gzip: ${ err.message }` ); + Log.error( `gzip`, chalk.yellow( `[response]` ), chalk.white( `❌` ), + chalk.redBright( `` ), chalk.gray( `${ statusCheck.message }` ), + chalk.redBright( `` ), chalk.gray( `serveGZP` ), + chalk.redBright( `` ), chalk.gray( `${ statusCheck.code }` ), + chalk.redBright( `` ), chalk.gray( `${ err.message }` ), + chalk.redBright( `` ), chalk.gray( `${ req.url }` ), + chalk.redBright( `` ), chalk.gray( `${ FILE_GZP }` ), + chalk.redBright( `` ), chalk.gray( `${ envFileGZP }` ) ); + + res.end( JSON.stringify( statusCheck ) ); } }; -function setCache( key, value, ttl ) +/* + cache > set +*/ + +function setCache( key, value, ttl, req ) { const expiry = Date.now() + ttl; cache.set( key, { @@ -1140,10 +1544,19 @@ function setCache( key, value, ttl ) expiry }); - Log.debug( `cache`, chalk.yellow( `[set]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `new key created` ), chalk.blueBright( `` ), chalk.gray( `${ key }` ), chalk.blueBright( `` ), chalk.gray( `${ ttl / 1000 } seconds` ) ); + Log.debug( `cache`, chalk.yellow( `[assigner]` ), chalk.white( `⚙️` ), + chalk.blueBright( `` ), chalk.gray( `new key created` ), + chalk.blueBright( `` ), chalk.gray( `setCache` ), + chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.blueBright( `` ), chalk.gray( `${ key }` ), + chalk.blueBright( `` ), chalk.gray( `${ ttl / 1000 } seconds` ) ); } -function getCache( key ) +/* + cache > get +*/ + +function getCache( key, req ) { const cached = cache.get( key ); if ( cached && cached.expiry > Date.now() ) @@ -1153,7 +1566,11 @@ function getCache( key ) else { if ( cached ) - Log.debug( `cache`, chalk.yellow( `[get]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `key has expired, marked for deletion` ), chalk.blueBright( `` ), chalk.gray( `${ key }` ) ); + Log.debug( `cache`, chalk.yellow( `[get]` ), chalk.white( `⚙️` ), + chalk.blueBright( `` ), chalk.gray( `key has expired, marked for deletion` ), + chalk.blueBright( `` ), chalk.gray( `getCache` ), + chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), + chalk.blueBright( `` ), chalk.gray( `${ key }` ) ); cache.delete( key ); return null; @@ -1171,12 +1588,28 @@ async function initialize() try { const start = performance.now(); - Log.info( `core`, chalk.yellow( `[init]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `Starting TVApp2 container. Assigning bound IP to host network adapter` ), chalk.blueBright( `` ), chalk.gray( `${ envWebIP }` ), chalk.blueBright( `` ), chalk.gray( `${ envIpContainer }` ), chalk.blueBright( `` ), chalk.gray( `${ envWebPort }` ) ); - Log.debug( `.env`, chalk.yellow( `[set]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `FILE_URL` ), chalk.blueBright( `` ), chalk.gray( `${ FILE_URL }` ) ); - Log.debug( `.env`, chalk.yellow( `[set]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `FILE_M3U` ), chalk.blueBright( `` ), chalk.gray( `${ FILE_M3U }` ) ); - Log.debug( `.env`, chalk.yellow( `[set]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `FILE_XML` ), chalk.blueBright( `` ), chalk.gray( `${ FILE_XML }` ) ); - Log.debug( `.env`, chalk.yellow( `[set]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `FILE_GZP` ), chalk.blueBright( `` ), chalk.gray( `${ FILE_GZP }` ) ); + Log.info( `core`, chalk.yellow( `[initiate]` ), chalk.white( `ℹ️` ), + chalk.blueBright( `` ), chalk.gray( `Starting TVApp2 container. Assigning bound IP to host network adapter` ), + chalk.blueBright( `` ), chalk.gray( `${ envWebIP }` ), + chalk.blueBright( `` ), chalk.gray( `${ envIpContainer }` ), + chalk.blueBright( `` ), chalk.gray( `${ envWebPort }` ) ); + + Log.debug( `.env`, chalk.yellow( `[assigner]` ), chalk.white( `⚙️` ), + chalk.blueBright( `` ), chalk.gray( `FILE_URL` ), + chalk.blueBright( `` ), chalk.gray( `${ FILE_URL }` ) ); + + Log.debug( `.env`, chalk.yellow( `[assigner]` ), chalk.white( `⚙️` ), + chalk.blueBright( `` ), chalk.gray( `FILE_M3U` ), + chalk.blueBright( `` ), chalk.gray( `${ FILE_M3U }` ) ); + + Log.debug( `.env`, chalk.yellow( `[assigner]` ), chalk.white( `⚙️` ), + chalk.blueBright( `` ), chalk.gray( `FILE_XML` ), + chalk.blueBright( `` ), chalk.gray( `${ FILE_XML }` ) ); + + Log.debug( `.env`, chalk.yellow( `[assigner]` ), chalk.white( `⚙️` ), + chalk.blueBright( `` ), chalk.gray( `FILE_GZP` ), + chalk.blueBright( `` ), chalk.gray( `${ FILE_GZP }` ) ); await getFile( extURL, FILE_URL ); await getFile( extXML, FILE_XML ); @@ -1200,11 +1633,23 @@ async function initialize() FILE_GZP_MODIFIED = getFileModified( FILE_GZP ); const end = performance.now(); - Log.info( `core`, chalk.yellow( `[init]` ), chalk.white( `→` ), chalk.blueBright( `` ), chalk.gray( `TVApp2 container is ready` ), chalk.blueBright( `took ${ end - start }ms` ), chalk.blueBright( `` ), chalk.gray( `${ envIpContainer }` ), chalk.blueBright( `` ), chalk.gray( `${ envIpGateway }` ), chalk.blueBright( `` ), chalk.gray( `${ envWebPort }` ) ); + Log.info( `core`, chalk.yellow( `[initiate]` ), chalk.white( `ℹ️` ), + chalk.blueBright( `` ), chalk.gray( `TVApp2 container is ready` ), + chalk.blueBright( `