"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.downloadDemoFile = downloadDemoFile; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const https_1 = __importDefault(require("https")); const http_1 = __importDefault(require("http")); const stream_1 = require("stream"); const util_1 = require("util"); const unbzip2_stream_1 = __importDefault(require("unbzip2-stream")); const pipe = (0, util_1.promisify)(stream_1.pipeline); /** * Entpackt eine .bz2-Datei mithilfe von Streams nach .dem */ async function extractBz2Safe(srcPath, destPath) { try { await pipe(fs_1.default.createReadStream(srcPath), (0, unbzip2_stream_1.default)(), fs_1.default.createWriteStream(destPath)); } catch (err) { console.log('❌ Fehler beim Entpacken (pipe):', err); throw new Error('Entpackung fehlgeschlagen'); } } /** * Lädt eine Datei per HTTPS, speichert sie unter `dest`, zeigt optional Fortschritt. */ function downloadWithHttps(url, dest, onProgress, maxRetries = 3, retryDelay = 3000) { return new Promise((resolve, reject) => { let attempt = 0; const tryDownload = () => { const file = fs_1.default.createWriteStream(dest); const client = url.startsWith('https') ? https_1.default : http_1.default; let downloaded = 0; let total = 0; let lastPercent = -1; const request = client.get(url, (res) => { if (res.statusCode !== 200) { res.resume(); file.close(); file.destroy(); if ([502, 503, 504].includes(res.statusCode) && attempt < maxRetries) { process.stdout.write(`🔁 Retry ${attempt + 1}/${maxRetries} – HTTP ${res.statusCode}\r`); attempt++; setTimeout(tryDownload, retryDelay); return; } if (attempt >= maxRetries) { console.log(`❌ Max. Versuche erreicht (${maxRetries}), Datei wird übersprungen (HTTP ${res.statusCode})`); return resolve(false); } return reject(new Error(`HTTP ${res.statusCode}`)); } total = parseInt(res.headers['content-length'] || '0', 10); res.on('data', (chunk) => { downloaded += chunk.length; if (onProgress && total) { const percent = Math.floor((downloaded / total) * 100); if (percent !== lastPercent) { lastPercent = percent; onProgress(percent); } } }); res.pipe(file); file.on('finish', () => { file.close((err) => { if (err) return reject(err); resolve(true); }); }); res.on('error', reject); file.on('error', reject); }); request.on('error', (err) => { if (attempt < maxRetries) { console.log(`🔁 Retry ${attempt + 1}/${maxRetries} wegen Verbindungsfehler: ${err.message}`); attempt++; setTimeout(tryDownload, retryDelay); } else { reject(err); } }); }; tryDownload(); }); } /** * Hauptfunktion: lädt und entpackt eine CS2-Demo (.bz2), mit Fortschrittsanzeige. */ async function downloadDemoFile(match, outputBaseDir = 'demos', onProgress) { if (!outputBaseDir || outputBaseDir.trim() === '') { outputBaseDir = 'demos'; } const appId = 730; const matchId = match.matchid; const timestamp = match.matchtime; const matchDate = new Date(timestamp * 1000).toISOString().split('T')[0]; const lastRound = match.roundstatsall?.at(-1); const demoUrl = typeof lastRound?.map === 'string' && lastRound.map.endsWith('.bz2') ? lastRound.map : undefined; const mapName = lastRound?.reservation?.map || lastRound?.mapname || match.watchablematchinfo?.game_map || 'unknownmap'; if (!demoUrl) { throw new Error('❌ Keine Demo-URL im Match vorhanden'); } const isPremier = !!lastRound?.b_switched_teams; const matchType = isPremier ? 'premier' : 'competitive'; const tempDir = path_1.default.join(outputBaseDir, 'temp'); const tempFileName = `match${appId}_${mapName}_${matchId}_${matchType}.bz2`; const baseName = path_1.default.parse(tempFileName).name; const tempFile = path_1.default.join(tempDir, tempFileName); const finalDir = path_1.default.join(outputBaseDir, matchDate); const finalFile = path_1.default.join(finalDir, `${baseName}.dem`); const finalFileName = path_1.default.basename(finalFile); fs_1.default.mkdirSync(tempDir, { recursive: true }); fs_1.default.mkdirSync(finalDir, { recursive: true }); console.log(`📥 Lade Demo von ${demoUrl}...`); try { const success = await downloadWithHttps(demoUrl, tempFile, onProgress); if (!success || !fs_1.default.existsSync(tempFile) || fs_1.default.statSync(tempFile).size === 0) { console.warn(`⚠️ Download fehlgeschlagen oder Datei leer – lösche ${tempFileName}`); try { if (fs_1.default.existsSync(tempFile)) fs_1.default.unlinkSync(tempFile); } catch { console.warn(`⚠️ Konnte leere Datei nicht löschen: ${tempFileName}`); } return ''; } } catch (err) { throw new Error(`❌ Fehler beim Download: ${err instanceof Error ? err.message : err}`); } console.log(`✅ Gespeichert als ${tempFileName}`); const entpackZeile = `🗜️ Entpacke ${finalFileName}...`; process.stdout.write(entpackZeile); await extractBz2Safe(tempFile, finalFile); const successMessage = `✅ Entpackt: ${finalFileName}`; const failMessage = `❌ Entpackung fehlgeschlagen – Datei nicht vorhanden`; // Max-Zeichenlänge bestimmen const maxLength = Math.max(entpackZeile.length, successMessage.length, failMessage.length); if (!fs_1.default.existsSync(finalFile)) { const paddedFail = failMessage.padEnd(maxLength, ' '); process.stdout.write(`\r${paddedFail}\n`); throw new Error(failMessage); } const paddedSuccess = successMessage.padEnd(maxLength, ' '); process.stdout.write(`\r${paddedSuccess}\n`); // Aufräumen try { fs_1.default.unlinkSync(tempFile); console.log(`🧹 Gelöscht: ${tempFileName}`); } catch { console.log(`⚠️ Konnte temporäre Datei nicht löschen: ${tempFileName}`); } return finalFile; }