flux-dl 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +454 -0
- package/package.json +50 -0
- package/src/VideoDownloader.js +197 -0
- package/src/index.js +11 -0
- package/src/platforms/dailymotion.js +71 -0
- package/src/platforms/examplePlatform.js +45 -0
- package/src/platforms/index.js +13 -0
- package/src/platforms/vimeo.js +66 -0
- package/src/platforms/youtube-deno.js +209 -0
- package/src/platforms/youtube.js +449 -0
- package/src/utils/browserEmulation.js +241 -0
- package/src/utils/cookieManager.js +110 -0
- package/src/utils/encryption.js +102 -0
- package/src/utils/requestSpoofing.js +141 -0
- package/src/utils/signatureDecoder.js +164 -0
- package/src/utils/youtubeInnerTube.js +201 -0
- package/src/utils/youtubeParser.js +283 -0
- package/src/utils/youtubeSearch.js +94 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
class CookieManager {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.cookies = {};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Lade Cookies aus Netscape-Format (cookies.txt)
|
|
10
|
+
loadFromFile(filepath) {
|
|
11
|
+
try {
|
|
12
|
+
if (!fs.existsSync(filepath)) {
|
|
13
|
+
console.log('No cookies.txt found, continuing without cookies');
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const content = fs.readFileSync(filepath, 'utf8');
|
|
18
|
+
const lines = content.split('\n');
|
|
19
|
+
|
|
20
|
+
for (const line of lines) {
|
|
21
|
+
// Überspringe Kommentare und leere Zeilen
|
|
22
|
+
if (line.startsWith('#') || line.trim() === '') {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Parse Netscape cookie format (Tab-getrennt)
|
|
27
|
+
const parts = line.split('\t');
|
|
28
|
+
if (parts.length >= 7) {
|
|
29
|
+
const domain = parts[0];
|
|
30
|
+
const name = parts[5];
|
|
31
|
+
let value = parts[6];
|
|
32
|
+
|
|
33
|
+
// Entferne ungültige Zeichen (Newlines, etc.)
|
|
34
|
+
value = value.replace(/[\r\n]/g, '').trim();
|
|
35
|
+
|
|
36
|
+
// Überspringe leere oder ungültige Cookies
|
|
37
|
+
if (!name || !value) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Speichere Cookie
|
|
42
|
+
if (!this.cookies[domain]) {
|
|
43
|
+
this.cookies[domain] = {};
|
|
44
|
+
}
|
|
45
|
+
this.cookies[domain][name] = value;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const cookieCount = Object.values(this.cookies).reduce((sum, domain) =>
|
|
50
|
+
sum + Object.keys(domain).length, 0
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
console.log(`✓ Loaded ${cookieCount} cookies from ${filepath}`);
|
|
54
|
+
return true;
|
|
55
|
+
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.warn('Failed to load cookies:', error.message);
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Hole Cookies für eine Domain
|
|
63
|
+
getCookiesForDomain(domain) {
|
|
64
|
+
const cookies = {};
|
|
65
|
+
|
|
66
|
+
// Suche nach exakter Domain und Wildcard-Domains
|
|
67
|
+
for (const [cookieDomain, domainCookies] of Object.entries(this.cookies)) {
|
|
68
|
+
if (domain.endsWith(cookieDomain.replace(/^\./, ''))) {
|
|
69
|
+
Object.assign(cookies, domainCookies);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return cookies;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Erstelle Cookie-String für Header
|
|
77
|
+
getCookieString(domain) {
|
|
78
|
+
const cookies = this.getCookiesForDomain(domain);
|
|
79
|
+
|
|
80
|
+
// Filtere und validiere Cookies
|
|
81
|
+
const validCookies = Object.entries(cookies)
|
|
82
|
+
.filter(([name, value]) => {
|
|
83
|
+
// Entferne ungültige Zeichen
|
|
84
|
+
const cleanValue = value.replace(/[\r\n\t]/g, '');
|
|
85
|
+
return name && cleanValue && cleanValue.length > 0;
|
|
86
|
+
})
|
|
87
|
+
.map(([name, value]) => {
|
|
88
|
+
// Säubere den Wert
|
|
89
|
+
const cleanValue = value.replace(/[\r\n\t]/g, '').trim();
|
|
90
|
+
return `${name}=${cleanValue}`;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return validCookies.join('; ');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Füge Cookie manuell hinzu
|
|
97
|
+
addCookie(domain, name, value) {
|
|
98
|
+
if (!this.cookies[domain]) {
|
|
99
|
+
this.cookies[domain] = {};
|
|
100
|
+
}
|
|
101
|
+
this.cookies[domain][name] = value;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Prüfe ob Cookies vorhanden sind
|
|
105
|
+
hasCookies() {
|
|
106
|
+
return Object.keys(this.cookies).length > 0;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = CookieManager;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
|
|
3
|
+
class VideoEncryption {
|
|
4
|
+
constructor(secretKey = 'your-secret-key-change-this') {
|
|
5
|
+
this.secretKey = secretKey;
|
|
6
|
+
this.algorithm = 'aes-256-cbc';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// URL verschlüsseln
|
|
10
|
+
encryptUrl(url, expiresIn = 3600) {
|
|
11
|
+
const expiryTime = Date.now() + (expiresIn * 1000);
|
|
12
|
+
|
|
13
|
+
const data = JSON.stringify({
|
|
14
|
+
url: url,
|
|
15
|
+
expires: expiryTime
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const iv = crypto.randomBytes(16);
|
|
19
|
+
const key = crypto.scryptSync(this.secretKey, 'salt', 32);
|
|
20
|
+
const cipher = crypto.createCipheriv(this.algorithm, key, iv);
|
|
21
|
+
|
|
22
|
+
let encrypted = cipher.update(data, 'utf8', 'hex');
|
|
23
|
+
encrypted += cipher.final('hex');
|
|
24
|
+
|
|
25
|
+
// IV + verschlüsselte Daten kombinieren
|
|
26
|
+
const token = iv.toString('hex') + ':' + encrypted;
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
token: token,
|
|
30
|
+
encryptedUrl: `encrypted://${Buffer.from(token).toString('base64')}`
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// URL entschlüsseln
|
|
35
|
+
decryptUrl(encryptedUrl) {
|
|
36
|
+
try {
|
|
37
|
+
// Base64 dekodieren
|
|
38
|
+
const token = encryptedUrl.replace('encrypted://', '');
|
|
39
|
+
const decoded = Buffer.from(token, 'base64').toString('utf8');
|
|
40
|
+
|
|
41
|
+
const [ivHex, encrypted] = decoded.split(':');
|
|
42
|
+
|
|
43
|
+
const iv = Buffer.from(ivHex, 'hex');
|
|
44
|
+
const key = crypto.scryptSync(this.secretKey, 'salt', 32);
|
|
45
|
+
const decipher = crypto.createDecipheriv(this.algorithm, key, iv);
|
|
46
|
+
|
|
47
|
+
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
48
|
+
decrypted += decipher.final('utf8');
|
|
49
|
+
|
|
50
|
+
const data = JSON.parse(decrypted);
|
|
51
|
+
|
|
52
|
+
// Prüfe Ablaufzeit
|
|
53
|
+
if (Date.now() > data.expires) {
|
|
54
|
+
throw new Error('URL expired');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return data.url;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
throw new Error(`Decryption failed: ${error.message}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Signatur für URL erstellen (HMAC)
|
|
64
|
+
signUrl(url, expiresIn = 3600) {
|
|
65
|
+
const expiryTime = Math.floor(Date.now() / 1000) + expiresIn;
|
|
66
|
+
const message = `${url}:${expiryTime}`;
|
|
67
|
+
|
|
68
|
+
const hmac = crypto.createHmac('sha256', this.secretKey);
|
|
69
|
+
hmac.update(message);
|
|
70
|
+
const signature = hmac.digest('hex');
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
url: url,
|
|
74
|
+
expires: expiryTime,
|
|
75
|
+
signature: signature,
|
|
76
|
+
signedUrl: `${url}?expires=${expiryTime}&signature=${signature}`
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Signatur verifizieren
|
|
81
|
+
verifySignature(url, expires, signature) {
|
|
82
|
+
const message = `${url}:${expires}`;
|
|
83
|
+
|
|
84
|
+
const hmac = crypto.createHmac('sha256', this.secretKey);
|
|
85
|
+
hmac.update(message);
|
|
86
|
+
const expectedSignature = hmac.digest('hex');
|
|
87
|
+
|
|
88
|
+
// Zeitbasierte Prüfung
|
|
89
|
+
if (Math.floor(Date.now() / 1000) > expires) {
|
|
90
|
+
throw new Error('URL expired');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Signatur-Prüfung
|
|
94
|
+
if (signature !== expectedSignature) {
|
|
95
|
+
throw new Error('Invalid signature');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = VideoEncryption;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
|
|
3
|
+
class RequestSpoofing {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.userAgents = [
|
|
6
|
+
// Chrome on Windows
|
|
7
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
8
|
+
// Firefox on Windows
|
|
9
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
|
|
10
|
+
// Edge on Windows
|
|
11
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0',
|
|
12
|
+
// Safari on Mac
|
|
13
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15',
|
|
14
|
+
// Chrome on Android
|
|
15
|
+
'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.144 Mobile Safari/537.36'
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
this.currentIndex = 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getRandomUserAgent() {
|
|
22
|
+
const ua = this.userAgents[this.currentIndex];
|
|
23
|
+
this.currentIndex = (this.currentIndex + 1) % this.userAgents.length;
|
|
24
|
+
return ua;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getBrowserHeaders(referer = null) {
|
|
28
|
+
const headers = {
|
|
29
|
+
'User-Agent': this.getRandomUserAgent(),
|
|
30
|
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
|
|
31
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
32
|
+
'Accept-Encoding': 'gzip, deflate, br',
|
|
33
|
+
'Connection': 'keep-alive',
|
|
34
|
+
'Upgrade-Insecure-Requests': '1',
|
|
35
|
+
'Sec-Fetch-Dest': 'document',
|
|
36
|
+
'Sec-Fetch-Mode': 'navigate',
|
|
37
|
+
'Sec-Fetch-Site': 'none',
|
|
38
|
+
'Sec-Fetch-User': '?1',
|
|
39
|
+
'Cache-Control': 'max-age=0'
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (referer) {
|
|
43
|
+
headers['Referer'] = referer;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return headers;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getVideoDownloadHeaders(videoUrl, referer) {
|
|
50
|
+
// KRITISCH: Range-Header für YouTube Downloads
|
|
51
|
+
return {
|
|
52
|
+
'User-Agent': this.getRandomUserAgent(),
|
|
53
|
+
'Accept': '*/*',
|
|
54
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
55
|
+
'Connection': 'keep-alive',
|
|
56
|
+
'Range': 'bytes=0-',
|
|
57
|
+
'Referer': referer || 'https://www.youtube.com/',
|
|
58
|
+
'Sec-Fetch-Dest': 'video',
|
|
59
|
+
'Sec-Fetch-Mode': 'no-cors',
|
|
60
|
+
'Sec-Fetch-Site': 'cross-site'
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async makeRequest(url, options = {}) {
|
|
65
|
+
const config = {
|
|
66
|
+
url: url,
|
|
67
|
+
method: options.method || 'GET',
|
|
68
|
+
headers: options.headers || this.getBrowserHeaders(options.referer),
|
|
69
|
+
timeout: options.timeout || 30000,
|
|
70
|
+
httpsAgent: new (require('https').Agent)({
|
|
71
|
+
rejectUnauthorized: false
|
|
72
|
+
}),
|
|
73
|
+
maxRedirects: 5,
|
|
74
|
+
validateStatus: (status) => status < 500
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
if (options.responseType) {
|
|
78
|
+
config.responseType = options.responseType;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const response = await axios(config);
|
|
83
|
+
return response;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
throw new Error(`Request failed: ${error.message}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async downloadStream(url, referer, onProgress) {
|
|
90
|
+
// Prüfe ob URL gültig ist
|
|
91
|
+
if (!url || !url.startsWith('http')) {
|
|
92
|
+
throw new Error('Invalid download URL');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const headers = this.getVideoDownloadHeaders(url, referer);
|
|
96
|
+
|
|
97
|
+
const config = {
|
|
98
|
+
url: url,
|
|
99
|
+
method: 'GET',
|
|
100
|
+
headers: headers,
|
|
101
|
+
responseType: 'stream',
|
|
102
|
+
httpsAgent: new (require('https').Agent)({
|
|
103
|
+
rejectUnauthorized: false
|
|
104
|
+
}),
|
|
105
|
+
maxRedirects: 10,
|
|
106
|
+
timeout: 0, // Kein Timeout für große Downloads
|
|
107
|
+
decompress: false, // WICHTIG: Keine automatische Dekompression
|
|
108
|
+
validateStatus: (status) => status >= 200 && status < 400
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const response = await axios(config);
|
|
113
|
+
|
|
114
|
+
if (response.status === 403) {
|
|
115
|
+
console.warn('Warning: Received 403 status, but continuing...');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (onProgress) {
|
|
119
|
+
const totalLength = response.headers['content-length'];
|
|
120
|
+
let downloadedLength = 0;
|
|
121
|
+
|
|
122
|
+
response.data.on('data', (chunk) => {
|
|
123
|
+
downloadedLength += chunk.length;
|
|
124
|
+
if (totalLength) {
|
|
125
|
+
const percent = Math.round((downloadedLength / totalLength) * 100);
|
|
126
|
+
onProgress(percent, downloadedLength, totalLength);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return response.data;
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (error.response && error.response.status === 403) {
|
|
134
|
+
throw new Error('403 Forbidden - Video URL requires valid signature. The N-parameter transformation may have failed.');
|
|
135
|
+
}
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = RequestSpoofing;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const vm = require('vm');
|
|
3
|
+
|
|
4
|
+
class SignatureDecoder {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.cache = new Map();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async decodeSignature(signatureCipher, videoId, userAgent) {
|
|
10
|
+
// Parse signatureCipher
|
|
11
|
+
const params = new URLSearchParams(signatureCipher);
|
|
12
|
+
const url = params.get('url');
|
|
13
|
+
const s = params.get('s');
|
|
14
|
+
const sp = params.get('sp') || 'signature';
|
|
15
|
+
|
|
16
|
+
if (!s) {
|
|
17
|
+
return url; // Keine Signatur nötig
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Player-Code laden und Signatur entschlüsseln
|
|
21
|
+
const signature = await this.decipher(s, videoId, userAgent);
|
|
22
|
+
|
|
23
|
+
// URL mit entschlüsselter Signatur zurückgeben
|
|
24
|
+
return `${url}&${sp}=${encodeURIComponent(signature)}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async decipher(signature, videoId, userAgent) {
|
|
28
|
+
// Player-URL finden
|
|
29
|
+
const playerUrl = await this.getPlayerUrl(videoId, userAgent);
|
|
30
|
+
|
|
31
|
+
if (this.cache.has(playerUrl)) {
|
|
32
|
+
const decipherFunc = this.cache.get(playerUrl);
|
|
33
|
+
return decipherFunc(signature);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Player-Code laden
|
|
37
|
+
const playerCode = await this.fetchPlayerCode(playerUrl, userAgent);
|
|
38
|
+
|
|
39
|
+
// Decipher-Funktion extrahieren
|
|
40
|
+
const decipherFunc = this.extractDecipherFunction(playerCode);
|
|
41
|
+
|
|
42
|
+
// Cache speichern
|
|
43
|
+
this.cache.set(playerUrl, decipherFunc);
|
|
44
|
+
|
|
45
|
+
return decipherFunc(signature);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async getPlayerUrl(videoId, userAgent) {
|
|
49
|
+
const response = await axios.get(`https://www.youtube.com/watch?v=${videoId}`, {
|
|
50
|
+
headers: {
|
|
51
|
+
'User-Agent': userAgent
|
|
52
|
+
},
|
|
53
|
+
httpsAgent: new (require('https').Agent)({
|
|
54
|
+
rejectUnauthorized: false
|
|
55
|
+
})
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const html = response.data;
|
|
59
|
+
|
|
60
|
+
// Player-URL extrahieren
|
|
61
|
+
const match = html.match(/"jsUrl":"([^"]+)"/);
|
|
62
|
+
if (!match) {
|
|
63
|
+
throw new Error('Could not find player URL');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let playerUrl = match[1].replace(/\\\//g, '/');
|
|
67
|
+
|
|
68
|
+
if (playerUrl.startsWith('//')) {
|
|
69
|
+
playerUrl = 'https:' + playerUrl;
|
|
70
|
+
} else if (playerUrl.startsWith('/')) {
|
|
71
|
+
playerUrl = 'https://www.youtube.com' + playerUrl;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return playerUrl;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async fetchPlayerCode(playerUrl, userAgent) {
|
|
78
|
+
const response = await axios.get(playerUrl, {
|
|
79
|
+
headers: {
|
|
80
|
+
'User-Agent': userAgent
|
|
81
|
+
},
|
|
82
|
+
httpsAgent: new (require('https').Agent)({
|
|
83
|
+
rejectUnauthorized: false
|
|
84
|
+
})
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return response.data;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
extractDecipherFunction(playerCode) {
|
|
91
|
+
// Mehrere Patterns versuchen (YouTube ändert oft die Struktur)
|
|
92
|
+
const patterns = [
|
|
93
|
+
/([a-zA-Z0-9$]+)=function\([a-zA-Z]\)\{[a-zA-Z]=\1\.split\(""\)/,
|
|
94
|
+
/\b([a-zA-Z0-9$]{2,})\s*=\s*function\(\s*a\s*\)\s*\{\s*a\s*=\s*a\.split\(\s*""\s*\)/,
|
|
95
|
+
/([a-zA-Z0-9$]+)\s*=\s*function\([a-zA-Z]+\)\{[a-zA-Z]+=[a-zA-Z]+\.split\(""\)/
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
let funcName = null;
|
|
99
|
+
|
|
100
|
+
for (const pattern of patterns) {
|
|
101
|
+
const match = playerCode.match(pattern);
|
|
102
|
+
if (match) {
|
|
103
|
+
funcName = match[1];
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!funcName) {
|
|
109
|
+
// Fallback: Suche nach signature decipher patterns
|
|
110
|
+
const altMatch = playerCode.match(/\.sig\|\|([a-zA-Z0-9$]+)\(/);
|
|
111
|
+
if (altMatch) {
|
|
112
|
+
funcName = altMatch[1];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!funcName) {
|
|
117
|
+
throw new Error('Could not find decipher function name');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Extrahiere die komplette Funktion
|
|
121
|
+
const funcPattern = new RegExp(`${funcName.replace(/\$/g, '\\$')}=function\\([a-zA-Z]\\)\\{[^}]+\\}`, 'g');
|
|
122
|
+
const funcMatch = playerCode.match(funcPattern);
|
|
123
|
+
|
|
124
|
+
if (!funcMatch) {
|
|
125
|
+
throw new Error('Could not extract decipher function');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const funcBody = funcMatch[0];
|
|
129
|
+
|
|
130
|
+
// Finde Helper-Objekt
|
|
131
|
+
const helperMatch = funcBody.match(/;([a-zA-Z0-9$]+)\./);
|
|
132
|
+
if (!helperMatch) {
|
|
133
|
+
throw new Error('Could not find helper object');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const helperName = helperMatch[1];
|
|
137
|
+
|
|
138
|
+
// Extrahiere Helper-Objekt
|
|
139
|
+
const helperPattern = new RegExp(`var ${helperName.replace(/\$/g, '\\$')}=\\{[\\s\\S]+?\\}\\};`, 'g');
|
|
140
|
+
const helperMatch2 = playerCode.match(helperPattern);
|
|
141
|
+
|
|
142
|
+
if (!helperMatch2) {
|
|
143
|
+
throw new Error('Could not extract helper object');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const helperBody = helperMatch2[0];
|
|
147
|
+
|
|
148
|
+
// Kombiniere und erstelle ausführbare Funktion
|
|
149
|
+
const code = `
|
|
150
|
+
${helperBody}
|
|
151
|
+
${funcBody}
|
|
152
|
+
${funcName};
|
|
153
|
+
`;
|
|
154
|
+
|
|
155
|
+
// Führe Code in isolierter VM aus
|
|
156
|
+
const context = {};
|
|
157
|
+
vm.createContext(context);
|
|
158
|
+
const decipherFunc = vm.runInContext(code, context);
|
|
159
|
+
|
|
160
|
+
return decipherFunc;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = SignatureDecoder;
|