improve diskmap snatching

This commit is contained in:
zefie
2025-08-10 20:03:39 -04:00
parent 393067e1fc
commit 23570f8a04

View File

@@ -19,11 +19,12 @@ const AdmZip = require('adm-zip');
* using the WTVP protocol with proper authentication and service discovery. * using the WTVP protocol with proper authentication and service discovery.
*/ */
class WebTVClientSimulator { class WebTVClientSimulator {
constructor(host, port, ssid, url, outputFile = null, maxRedirects = 10, useEncryption = false, request_type_download = false, debug = false, tricks = false, followImages = false, followAll = false, maxDepth = 5, maxRetries = 5, requestDelay = 250, boxType = null, username = null) { constructor(host, port, ssid, url, outputFile = null, maxRedirects = 10, useEncryption = false, request_type_download = false, debug = false, tricks = false, followImages = false, followAll = false, maxDepth = 5, maxRetries = 5, requestDelay = 250, boxType = null, username = null, keepgz = false) {
this.host = host; this.host = host;
this.port = port; this.port = port;
this.ssid = ssid; this.ssid = ssid;
this.url = url; this.url = url;
this.keepgz = keepgz;
this.request_type_download = request_type_download; this.request_type_download = request_type_download;
this.outputFile = outputFile; this.outputFile = outputFile;
this.followImages = followImages; this.followImages = followImages;
@@ -1211,7 +1212,9 @@ class WebTVClientSimulator {
const match = this.url.match(/^([\w-]+):\/?(.*)/); const match = this.url.match(/^([\w-]+):\/?(.*)/);
if (match) { if (match) {
const serviceName = match[1]; const serviceName = match[1];
const path = '/' + (match[2] || ''); let path = '/' + (match[2] || '');
this.debugLog(`Parsed target service: ${serviceName}, path: ${path}`); this.debugLog(`Parsed target service: ${serviceName}, path: ${path}`);
try { try {
@@ -1338,21 +1341,36 @@ class WebTVClientSimulator {
const contentType = headers['content-type'] || ''; const contentType = headers['content-type'] || '';
const normalizedContentType = contentType.split(';')[0].trim().toLowerCase(); const normalizedContentType = contentType.split(';')[0].trim().toLowerCase();
// Handle gzip decompression if content-type is application/gzip and keepgz is false
let processedContent = content;
if (normalizedContentType === 'application/gzip' && !this.keepgz) {
this.debugLog('Decompressing gzip content...');
try {
const buffer = Buffer.isBuffer(content) ? content : Buffer.from(content, 'binary');
processedContent = zlib.gunzipSync(buffer);
this.debugLog('Gzip decompression successful');
} catch (error) {
console.error('Error decompressing gzip content:', error);
this.debugLog('Falling back to original content');
processedContent = content;
}
}
const isHtml = /text\/html/i.test(contentType) || const isHtml = /text\/html/i.test(contentType) ||
(typeof content === 'string' && /<html/i.test(content)) || (typeof processedContent === 'string' && /<html/i.test(processedContent)) ||
(Buffer.isBuffer(content) && /<html/i.test(content.toString('utf8'))); (Buffer.isBuffer(processedContent) && /<html/i.test(processedContent.toString('utf8')));
const isDownloadList = normalizedContentType === 'wtv/download-list'; const isDownloadList = normalizedContentType === 'wtv/download-list';
if (this.followImages && isHtml) { if (this.followImages && isHtml) {
this.debugLog('HTML content detected with --follow enabled, creating archive...'); this.debugLog('HTML content detected with --follow enabled, creating archive...');
await this.createHtmlArchive(content, headers); await this.createHtmlArchive(processedContent, headers);
} else if (this.followImages && isDownloadList) { } else if (this.followImages && isDownloadList) {
this.debugLog('Download-list content detected with --follow enabled, creating archive...'); this.debugLog('Download-list content detected with --follow enabled, creating archive...');
await this.createDownloadListArchive(content, headers); await this.createDownloadListArchive(processedContent, headers);
} else { } else {
// Regular file save // Regular file save
await fs.writeFile(this.outputFile, Buffer.isBuffer(content) ? content : Buffer.from(content, 'utf8')); await fs.writeFile(this.outputFile, Buffer.isBuffer(processedContent) ? processedContent : Buffer.from(processedContent, 'utf8'));
} }
} catch (error) { } catch (error) {
console.error('Error saving to file:', error); console.error('Error saving to file:', error);
@@ -1395,7 +1413,26 @@ class WebTVClientSimulator {
const imageResult = await this.downloadImage(imageUrl, this.url); const imageResult = await this.downloadImage(imageUrl, this.url);
if (imageResult && imageResult.body && !downloadedImages.has(imageUrl)) { if (imageResult && imageResult.body && !downloadedImages.has(imageUrl)) {
const imagePath = this.getServicePath(imageUrl, imageResult.headers || {}); const imagePath = this.getServicePath(imageUrl, imageResult.headers || {});
zip.addFile(imagePath, imageResult.body);
// Handle gzip decompression if content-type is application/gzip and keepgz is false
let imageContent = imageResult.body;
const contentType = imageResult.headers ? imageResult.headers['content-type'] : '';
const normalizedContentType = contentType.split(';')[0].trim().toLowerCase();
if (normalizedContentType === 'application/gzip' && !this.keepgz) {
this.debugLog(`Decompressing gzip image for ${imageUrl}...`);
try {
const buffer = Buffer.isBuffer(imageResult.body) ? imageResult.body : Buffer.from(imageResult.body, 'binary');
imageContent = zlib.gunzipSync(buffer);
this.debugLog(`Gzip decompression successful for ${imageUrl}`);
} catch (error) {
console.error(`Error decompressing gzip image for ${imageUrl}:`, error);
this.debugLog(`Falling back to original content for ${imageUrl}`);
imageContent = imageResult.body;
}
}
zip.addFile(imagePath, imageContent);
downloadedImages.add(imageUrl); downloadedImages.add(imageUrl);
this.debugLog(`Added image: ${imagePath} (from ${imageUrl})`); this.debugLog(`Added image: ${imagePath} (from ${imageUrl})`);
} }
@@ -1454,8 +1491,29 @@ class WebTVClientSimulator {
// Validate checksum if we have one // Validate checksum if we have one
this.validateDownloadChecksum(fileUrl, fileResult.body); this.validateDownloadChecksum(fileUrl, fileResult.body);
const filePath = this.getServicePath(fileUrl, fileResult.headers || {}); // Handle gzip decompression if content-type is application/gzip and keepgz is false
zip.addFile(filePath, fileResult.body); let fileContent = fileResult.body;
const contentType = fileResult.headers ? fileResult.headers['content-type'] : '';
const normalizedContentType = contentType.split(';')[0].trim().toLowerCase();
var isgzip = false;
if (normalizedContentType === 'application/gzip' && !this.keepgz) {
this.debugLog(`Decompressing gzip file for ${fileUrl}...`);
try {
const buffer = Buffer.isBuffer(fileResult.body) ? fileResult.body : Buffer.from(fileResult.body, 'binary');
fileContent = zlib.gunzipSync(buffer);
isgzip = true;
this.debugLog(`Gzip decompression successful for ${fileUrl}`);
} catch (error) {
console.error(`Error decompressing gzip file for ${fileUrl}:`, error);
this.debugLog(`Falling back to original content for ${fileUrl}`);
fileContent = fileResult.body;
}
}
var filePath = this.getServicePath(fileUrl, fileResult.headers || {});
if (isgzip) filePath = filePath.slice(0, -3);
zip.addFile(filePath, fileContent);
downloadedFiles.add(fileUrl); downloadedFiles.add(fileUrl);
this.debugLog(`Added referenced file: ${filePath}`); this.debugLog(`Added referenced file: ${filePath}`);
} }
@@ -2011,12 +2069,30 @@ class WebTVClientSimulator {
for (const [url, response] of this.allContent) { for (const [url, response] of this.allContent) {
try { try {
const servicePath = this.getServicePath(url, response.headers || {}); const servicePath = this.getServicePath(url, response.headers || {});
zip.addFile(servicePath, response.body);
// Handle gzip decompression if content-type is application/gzip and keepgz is false
let contentToAdd = response.body;
const contentType = response.headers ? response.headers['content-type'] : '';
const normalizedContentType = contentType.split(';')[0].trim().toLowerCase();
if (normalizedContentType === 'application/gzip' && !this.keepgz) {
this.debugLog(`Decompressing gzip content for ${url}...`);
try {
const buffer = Buffer.isBuffer(response.body) ? response.body : Buffer.from(response.body, 'binary');
contentToAdd = zlib.gunzipSync(buffer);
this.debugLog(`Gzip decompression successful for ${url}`);
} catch (error) {
console.error(`Error decompressing gzip content for ${url}:`, error);
this.debugLog(`Falling back to original content for ${url}`);
contentToAdd = response.body;
}
}
zip.addFile(servicePath, contentToAdd);
addedFiles++; addedFiles++;
this.debugLog(`Added to archive: ${servicePath} (from ${url})`); this.debugLog(`Added to archive: ${servicePath} (from ${url})`);
// Log content type for debugging // Log content type for debugging
const contentType = response.headers ? response.headers['content-type'] : 'unknown';
if (contentType === 'text/tellyscript' || contentType === 'text/dialscript') { if (contentType === 'text/tellyscript' || contentType === 'text/dialscript') {
this.debugLog(` -> TellyScript/DialScript content detected, saved as .tok file`); this.debugLog(` -> TellyScript/DialScript content detected, saved as .tok file`);
} }
@@ -2552,7 +2628,8 @@ function parseArgs() {
maxRetries: 5, maxRetries: 5,
requestDelay: 250, requestDelay: 250,
debug: false, debug: false,
username: null username: null,
keepgz: false
}; };
for (let i = 0; i < args.length; i++) { for (let i = 0; i < args.length; i++) {
@@ -2632,6 +2709,9 @@ function parseArgs() {
config.username = args[++i]; config.username = args[++i];
} }
break; break;
case '--keepgz':
config.keepgz = true;
break;
case '--help': case '--help':
console.log(` console.log(`
WebTV Client Simulator WebTV Client Simulator
@@ -2655,6 +2735,7 @@ Options:
--depth <num> Maximum crawl depth for --follow-all mode (default: 5) --depth <num> Maximum crawl depth for --follow-all mode (default: 5)
--retries <num> Maximum number of retries for ECONNREFUSED errors (default: 5) --retries <num> Maximum number of retries for ECONNREFUSED errors (default: 5)
--delay <num> Delay between requests in milliseconds (default: 250) --delay <num> Delay between requests in milliseconds (default: 250)
--keepgz Keep .gz files compressed when following wtv/download-list (default: false)
--debug Enable debug logging --debug Enable debug logging
--help Show this help message --help Show this help message
@@ -2675,7 +2756,7 @@ Example:
*/ */
async function main() { async function main() {
const config = parseArgs(); const config = parseArgs();
const simulator = new WebTVClientSimulator(config.host, config.port, config.ssid, config.url, config.outputFile, config.maxRedirects, config.useEncryption, config.request_type_download, config.debug, config.useTricksAccess, config.followImages, config.followAll, config.maxDepth, config.maxRetries, config.requestDelay, config.boxType, config.username); const simulator = new WebTVClientSimulator(config.host, config.port, config.ssid, config.url, config.outputFile, config.maxRedirects, config.useEncryption, config.request_type_download, config.debug, config.useTricksAccess, config.followImages, config.followAll, config.maxDepth, config.maxRetries, config.requestDelay, config.boxType, config.username, config.keepgz);
// Handle graceful shutdown // Handle graceful shutdown
process.on('SIGINT', () => { process.on('SIGINT', () => {