This commit is contained in:
Rother 2025-05-28 00:40:46 +02:00
commit b2c00edfca
17 changed files with 2423 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
demos
node_modules

167
dist/app/downloadDemoFile.js vendored Normal file
View File

@ -0,0 +1,167 @@
"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;
}

17
dist/app/fetchMatchFromSharecode.js vendored Normal file
View File

@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.fetchMatchFromShareCode = fetchMatchFromShareCode;
const csgo_sharecode_1 = require("csgo-sharecode");
async function fetchMatchFromShareCode(shareCode, session) {
const { csgo } = session;
const decoded = (0, csgo_sharecode_1.decodeMatchShareCode)(shareCode);
return new Promise((resolve, reject) => {
csgo.once('matchList', (matches) => {
const match = matches.find((m) => m.matchid === decoded.matchId.toString());
if (!match)
return reject(new Error('❌ Kein Match gefunden'));
resolve(match);
});
csgo.requestGame(shareCode);
});
}

44
dist/app/steamSession.js vendored Normal file
View File

@ -0,0 +1,44 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createSteamSession = createSteamSession;
const steam_user_1 = __importDefault(require("steam-user"));
const globaloffensive_1 = __importDefault(require("globaloffensive"));
async function createSteamSession(username, password, mfa) {
const client = new steam_user_1.default();
const csgo = new globaloffensive_1.default(client);
const loginPromise = new Promise((resolve, reject) => {
client.logOn({
accountName: username,
password,
twoFactorCode: mfa,
});
client.once('loggedOn', () => {
console.log('✅ Eingeloggt bei Steam');
client.setPersona(steam_user_1.default.EPersonaState.Online);
client.gamesPlayed(730);
});
client.on('friendRelationship', (steamID, relationship) => {
if (relationship === steam_user_1.default.EFriendRelationship.RequestRecipient) {
console.log(` Freundschaftsanfrage von ${steamID.getSteamID64()} erhalten wird akzeptiert`);
client.addFriend(steamID, (err) => {
if (err) {
console.error(`❌ Fehler beim Akzeptieren von ${steamID.getSteamID64()}:`, err);
}
else {
console.log(`✅ Freund hinzugefügt: ${steamID.getSteamID64()}`);
}
});
}
});
csgo.once('connectedToGC', () => {
console.log('🎮 Verbunden mit dem Game Coordinator');
resolve();
});
client.once('error', reject);
});
await loginPromise;
return { client, csgo };
}

BIN
dist/cs2-demo-downloader-linux vendored Normal file

Binary file not shown.

BIN
dist/cs2-demo-downloader-macos vendored Normal file

Binary file not shown.

BIN
dist/cs2-demo-downloader-win.exe vendored Normal file

Binary file not shown.

69
dist/main.js vendored Normal file
View File

@ -0,0 +1,69 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const yargs_1 = __importDefault(require("yargs"));
const helpers_1 = require("yargs/helpers");
const steamSession_1 = require("./app/steamSession");
const fetchMatchFromSharecode_1 = require("./app/fetchMatchFromSharecode");
const downloadDemoFile_1 = require("./app/downloadDemoFile");
const http_1 = __importDefault(require("http"));
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const argv = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv)).options({
username: { type: 'string', demandOption: true },
password: { type: 'string', demandOption: true },
mfa: { type: 'string', demandOption: false },
authCode: { type: 'string', demandOption: false },
port: { type: 'number', default: 4000, demandOption: false },
demoPath: {
type: 'string',
default: 'demos',
demandOption: false,
description: 'Zielverzeichnis für heruntergeladene Demos',
},
}).parseSync();
const PORT = argv.port;
const resolvedDemoPath = path_1.default.resolve(argv.demoPath);
if (!fs_1.default.existsSync(resolvedDemoPath)) {
fs_1.default.mkdirSync(resolvedDemoPath, { recursive: true });
console.log(`📂 Verzeichnis erstellt: ${resolvedDemoPath}`);
}
async function start() {
const session = await (0, steamSession_1.createSteamSession)(argv.username, argv.password, argv.mfa);
console.log(`🚀 Server läuft auf http://localhost:${PORT}`);
http_1.default
.createServer(async (req, res) => {
if (req.method === 'POST' && req.url === '/download') {
let body = '';
req.on('data', (chunk) => (body += chunk));
req.on('end', async () => {
try {
const { shareCode, steamId } = JSON.parse(body);
console.log(`📦 ShareCode empfangen: ${shareCode}`);
const match = await (0, fetchMatchFromSharecode_1.fetchMatchFromShareCode)(shareCode, session);
const path = await (0, downloadDemoFile_1.downloadDemoFile)(match, steamId, resolvedDemoPath, (percent) => {
process.stdout.write(`📶 Fortschritt: ${percent}%\r`);
if (percent === 100) {
console.log('✅ Download abgeschlossen');
}
});
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: true, path }));
}
catch (err) {
console.error('❌ Fehler:', err);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: false, error: err instanceof Error ? err.message : err }));
}
});
}
else {
res.writeHead(404);
res.end();
}
})
.listen(PORT);
}
start();

2
dist/types/types.js vendored Normal file
View File

@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

1685
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

44
package.json Normal file
View File

@ -0,0 +1,44 @@
{
"name": "cs2-demo-downloader",
"version": "1.1.0",
"description": "",
"main": "index.js",
"bin": "dist/main.js",
"scripts": {
"build": "tsc && pkg . --targets node18-win-x64,node18-linux-x64,node18-macos-x64 --out-path dist"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"csgo-sharecode": "^3.1.2",
"decompress": "^4.2.1",
"decompress-bzip2": "^4.0.0",
"dotenv": "^16.5.0",
"globaloffensive": "^3.2.0",
"lzma": "^2.3.2",
"steam-session": "^1.9.3",
"steam-totp": "^2.1.2",
"steam-user": "^5.2.1",
"unbzip2-stream": "^1.4.3",
"undici": "^6.21.2",
"yargs": "^17.7.2"
},
"devDependencies": {
"@types/decompress": "^4.2.7",
"@types/globaloffensive": "^2.3.4",
"@types/node": "^22.15.16",
"@types/steam-user": "^5.0.4",
"@types/unbzip2-stream": "^1.4.3",
"@types/yargs": "^17.0.33",
"ts-node": "^10.9.2",
"typescript": "^5.8.3"
},
"pkg": {
"assets": [
"node_modules/lzma/src/lzma_worker.js",
"node_modules/@doctormckay/steam-crypto/system.pem"
]
}
}

212
src/app/downloadDemoFile.ts Normal file
View File

@ -0,0 +1,212 @@
import fs from 'fs';
import path from 'path';
import https from 'https';
import http from 'http';
import { pipeline } from 'stream';
import { promisify } from 'util';
import bz2 from 'unbzip2-stream';
const pipe = promisify(pipeline);
/**
* Entpackt eine .bz2-Datei mithilfe von Streams nach .dem
*/
async function extractBz2Safe(srcPath: string, destPath: string) {
try {
await pipe(
fs.createReadStream(srcPath),
bz2(),
fs.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: string,
dest: string,
onProgress?: (percent: number) => void,
maxRetries = 3,
retryDelay = 3000
): Promise<boolean> {
return new Promise((resolve, reject) => {
let attempt = 0;
const tryDownload = () => {
const file = fs.createWriteStream(dest);
const client = url.startsWith('https') ? https : http;
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.
*/
export async function downloadDemoFile(
match: any,
steamId: string,
outputBaseDir = 'demos',
onProgress?: (percent: number) => void
): Promise<string> {
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.join(outputBaseDir, 'temp');
const tempFileName = `match${appId}_${mapName}_${matchId}_${matchType}.bz2`;
const baseName = path.parse(tempFileName).name;
const tempFile = path.join(tempDir, tempFileName);
const finalDir = path.join(outputBaseDir, steamId, matchDate);
const finalFile = path.join(finalDir, `${baseName}.dem`);
const finalFileName = path.basename(finalFile)
fs.mkdirSync(tempDir, { recursive: true });
fs.mkdirSync(finalDir, { recursive: true });
console.log(`📥 Lade Demo von ${demoUrl}...`);
try {
const success = await downloadWithHttps(demoUrl, tempFile, onProgress);
if (!success || !fs.existsSync(tempFile) || fs.statSync(tempFile).size === 0) {
console.warn(`⚠️ Download fehlgeschlagen oder Datei leer lösche ${tempFileName}`);
try {
if (fs.existsSync(tempFile)) fs.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.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.unlinkSync(tempFile);
console.log(`🧹 Gelöscht: ${tempFileName}`);
} catch {
console.log(`⚠️ Konnte temporäre Datei nicht löschen: ${tempFileName}`);
}
return finalFile;
}

View File

@ -0,0 +1,21 @@
import { decodeMatchShareCode } from 'csgo-sharecode';
import { createSteamSession } from './steamSession';
import { SteamSession } from '../types/types';
export async function fetchMatchFromShareCode(
shareCode: string,
session: SteamSession
): Promise<any> {
const { csgo } = session;
const decoded = decodeMatchShareCode(shareCode);
return new Promise((resolve, reject) => {
csgo.once('matchList', (matches) => {
const match = matches.find((m) => m.matchid === decoded.matchId.toString());
if (!match) return reject(new Error('❌ Kein Match gefunden'));
resolve(match);
});
csgo.requestGame(shareCode);
});
}

49
src/app/steamSession.ts Normal file
View File

@ -0,0 +1,49 @@
import SteamUser from 'steam-user';
import GlobalOffensive from 'globaloffensive';
import { SteamSession } from '../types/types';
export async function createSteamSession(
username: string,
password: string,
mfa?: string
): Promise<SteamSession> {
const client = new SteamUser();
const csgo = new GlobalOffensive(client);
const loginPromise = new Promise<void>((resolve, reject) => {
client.logOn({
accountName: username,
password,
twoFactorCode: mfa,
});
client.once('loggedOn', () => {
console.log('✅ Eingeloggt bei Steam');
client.setPersona(SteamUser.EPersonaState.Online);
client.gamesPlayed(730);
});
client.on('friendRelationship', (steamID, relationship) => {
if (relationship === SteamUser.EFriendRelationship.RequestRecipient) {
console.log(` Freundschaftsanfrage von ${steamID.getSteamID64()} erhalten wird akzeptiert`);
client.addFriend(steamID, (err) => {
if (err) {
console.error(`❌ Fehler beim Akzeptieren von ${steamID.getSteamID64()}:`, err);
} else {
console.log(`✅ Freund hinzugefügt: ${steamID.getSteamID64()}`);
}
});
}
});
csgo.once('connectedToGC', () => {
console.log('🎮 Verbunden mit dem Game Coordinator');
resolve();
});
client.once('error', reject);
});
await loginPromise;
return { client, csgo };
}

77
src/main.ts Normal file
View File

@ -0,0 +1,77 @@
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { createSteamSession } from './app/steamSession';
import { fetchMatchFromShareCode } from './app/fetchMatchFromSharecode';
import { downloadDemoFile } from './app/downloadDemoFile';
import http from 'http';
import fs from 'fs';
import path from 'path';
const argv = yargs(hideBin(process.argv)).options({
username: { type: 'string', demandOption: true },
password: { type: 'string', demandOption: true },
mfa: { type: 'string', demandOption: false },
authCode: { type: 'string', demandOption: false },
port: { type: 'number', default: 4000, demandOption: false },
demoPath: {
type: 'string',
default: 'demos',
demandOption: false,
description: 'Zielverzeichnis für heruntergeladene Demos',
},
}).parseSync();
const PORT = argv.port;
const resolvedDemoPath = path.resolve(argv.demoPath);
if (!fs.existsSync(resolvedDemoPath)) {
fs.mkdirSync(resolvedDemoPath, { recursive: true });
console.log(`📂 Verzeichnis erstellt: ${resolvedDemoPath}`);
}
async function start() {
const session = await createSteamSession(argv.username, argv.password, argv.mfa);
console.log(`🚀 Server läuft auf http://localhost:${PORT}`);
http
.createServer(async (req, res) => {
if (req.method === 'POST' && req.url === '/download') {
let body = '';
req.on('data', (chunk) => (body += chunk));
req.on('end', async () => {
try {
const { shareCode, steamId } = JSON.parse(body);
console.log(`📦 ShareCode empfangen: ${shareCode}`);
const match = await fetchMatchFromShareCode(shareCode, session);
const path = await downloadDemoFile(
match,
steamId,
resolvedDemoPath,
(percent: number) => {
process.stdout.write(`📶 Fortschritt: ${percent}%\r`);
if (percent === 100) {
console.log('✅ Download abgeschlossen');
}
}
);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: true, path }));
} catch (err) {
console.error('❌ Fehler:', err);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: false, error: err instanceof Error ? err.message : err }));
}
});
} else {
res.writeHead(404);
res.end();
}
})
.listen(PORT);
}
start();

21
src/types/types.ts Normal file
View File

@ -0,0 +1,21 @@
import SteamUser from 'steam-user';
import GlobalOffensive from 'globaloffensive';
export type DecodedShareCode = {
matchId: bigint;
reservationId: bigint;
tvPort: number;
};
export type DownloadParams = {
decoded: DecodedShareCode;
username: string;
password: string;
authCode?: string;
outputPath: string;
};
export type SteamSession = {
client: SteamUser;
csgo: GlobalOffensive;
};

13
tsconfig.json Normal file
View File

@ -0,0 +1,13 @@
{
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"target": "es2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node",
"types": ["node"]
},
"include": ["src/**/*"]
}