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.
@@ -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;