ironie-cs2-demo-downloader/dist/app/downloadDemoFile.js
2025-05-28 00:40:46 +02:00

168 lines
7.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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`);
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, steamId, 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, steamId, 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;
}