flux-dl 1.1.9 → 1.1.10
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/package.json +6 -3
- package/src/VideoDownloader.js +20 -5
- package/src/platforms/youtube.js +107 -22
- package/src/utils/cookieManager.js +0 -1
- package/src/utils/youtubeInnerTube.js +31 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flux-dl",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.10",
|
|
4
4
|
"description": "Leistungsstarke Video-Downloader Library für YouTube, Vimeo und Dailymotion - komplett in JavaScript, keine externen Binaries",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"test-encryption": "node examples/encryption-test.js",
|
|
9
9
|
"test-platforms": "node examples/test-platforms.js"
|
|
10
10
|
},
|
|
11
|
+
|
|
11
12
|
"keywords": [
|
|
12
13
|
"video",
|
|
13
14
|
"download",
|
|
@@ -38,8 +39,8 @@
|
|
|
38
39
|
"node": ">=14.0.0"
|
|
39
40
|
},
|
|
40
41
|
"dependencies": {
|
|
41
|
-
"axios": "^1.
|
|
42
|
-
"cheerio": "^1.
|
|
42
|
+
"axios": "^1.13.5",
|
|
43
|
+
"cheerio": "^1.2.0",
|
|
43
44
|
"m3u8-parser": "^7.1.0"
|
|
44
45
|
},
|
|
45
46
|
"files": [
|
|
@@ -48,3 +49,5 @@
|
|
|
48
49
|
"LICENSE"
|
|
49
50
|
]
|
|
50
51
|
}
|
|
52
|
+
|
|
53
|
+
|
package/src/VideoDownloader.js
CHANGED
|
@@ -149,11 +149,14 @@ class VideoDownloader {
|
|
|
149
149
|
* Get audio stream without saving to file
|
|
150
150
|
* Returns a readable stream that can be piped or consumed directly
|
|
151
151
|
* ULTRA-STABLE: Will never crash, handles all connection errors gracefully
|
|
152
|
+
* Uses InnerTube API (Android client) to avoid 403 errors
|
|
152
153
|
* @param {string} url - Video URL
|
|
153
154
|
* @param {function} onProgress - Optional progress callback (percent, downloaded, total)
|
|
154
155
|
* @returns {Promise<{stream: ReadableStream, info: Object}>}
|
|
155
156
|
*/
|
|
156
157
|
async getAudioStream(url, onProgress = null) {
|
|
158
|
+
// WICHTIG: Nutze getVideoInfo um InnerTube API zu verwenden (Android Client)
|
|
159
|
+
// Dies vermeidet 403 Fehler komplett!
|
|
157
160
|
const info = await this.getVideoInfo(url);
|
|
158
161
|
|
|
159
162
|
if (!info.videoUrl) {
|
|
@@ -162,9 +165,10 @@ class VideoDownloader {
|
|
|
162
165
|
|
|
163
166
|
console.log(`🎵 Streaming audio: ${info.title}`);
|
|
164
167
|
console.log(`📊 Quality: ${info.quality}`);
|
|
168
|
+
console.log(`🤖 Client: ${info.clientUsed || 'unknown'}`);
|
|
165
169
|
|
|
166
|
-
// Nutze Browser Emulation mit Cookies
|
|
167
|
-
const referer = `https://www.youtube.com/watch?v=${info.videoId}
|
|
170
|
+
// Nutze Browser Emulation mit Cookies für bessere Kompatibilität
|
|
171
|
+
const referer = info.videoId ? `https://www.youtube.com/watch?v=${info.videoId}` : null;
|
|
168
172
|
|
|
169
173
|
// Wrap onProgress to catch errors
|
|
170
174
|
const safeOnProgress = onProgress ? (percent, downloaded, total) => {
|
|
@@ -202,6 +206,7 @@ class VideoDownloader {
|
|
|
202
206
|
// Timeout protection with auto-recovery
|
|
203
207
|
let lastDataTime = Date.now();
|
|
204
208
|
let dataReceived = false;
|
|
209
|
+
let bytesReceived = 0;
|
|
205
210
|
|
|
206
211
|
const timeoutCheck = setInterval(() => {
|
|
207
212
|
const timeSinceLastData = Date.now() - lastDataTime;
|
|
@@ -221,10 +226,14 @@ class VideoDownloader {
|
|
|
221
226
|
resilientStream.on('data', (chunk) => {
|
|
222
227
|
lastDataTime = Date.now();
|
|
223
228
|
dataReceived = true;
|
|
229
|
+
bytesReceived += chunk.length;
|
|
224
230
|
});
|
|
225
231
|
|
|
226
232
|
resilientStream.on('end', () => {
|
|
227
233
|
clearInterval(timeoutCheck);
|
|
234
|
+
if (bytesReceived === 0) {
|
|
235
|
+
console.warn(`⚠️ Stream ended with 0 bytes for "${info.title}"`);
|
|
236
|
+
}
|
|
228
237
|
});
|
|
229
238
|
|
|
230
239
|
resilientStream.on('close', () => {
|
|
@@ -252,7 +261,8 @@ class VideoDownloader {
|
|
|
252
261
|
stream: resilientStream,
|
|
253
262
|
info,
|
|
254
263
|
contentType: 'audio/mpeg',
|
|
255
|
-
filename: this.sanitizeFilename(info.title) + '.mp3'
|
|
264
|
+
filename: this.sanitizeFilename(info.title) + '.mp3',
|
|
265
|
+
bytesReceived: () => bytesReceived // Function to check bytes received
|
|
256
266
|
};
|
|
257
267
|
|
|
258
268
|
} catch (error) {
|
|
@@ -277,7 +287,8 @@ class VideoDownloader {
|
|
|
277
287
|
info,
|
|
278
288
|
contentType: 'audio/mpeg',
|
|
279
289
|
filename: this.sanitizeFilename(info.title) + '.mp3',
|
|
280
|
-
error: error.message
|
|
290
|
+
error: error.message,
|
|
291
|
+
bytesReceived: () => 0
|
|
281
292
|
};
|
|
282
293
|
}
|
|
283
294
|
}
|
|
@@ -467,6 +478,7 @@ class VideoDownloader {
|
|
|
467
478
|
|
|
468
479
|
/**
|
|
469
480
|
* Stream mit User-spezifischer Qualität
|
|
481
|
+
* Uses InnerTube API (Android client) to avoid 403 errors
|
|
470
482
|
* @param {string} url - Video URL
|
|
471
483
|
* @param {string} userId - User ID
|
|
472
484
|
* @param {boolean} isPremium - Ist Premium User?
|
|
@@ -478,6 +490,7 @@ class VideoDownloader {
|
|
|
478
490
|
|
|
479
491
|
console.log(`🎵 Streaming for user ${userId}`);
|
|
480
492
|
console.log(`📊 Quality: ${this.qualityManager.getQualityLabel(userQuality)}`);
|
|
493
|
+
console.log(`👤 Premium: ${isPremium ? 'Yes' : 'No'}`);
|
|
481
494
|
|
|
482
495
|
// Temporär Qualität setzen
|
|
483
496
|
const originalQuality = this.options.quality;
|
|
@@ -488,7 +501,9 @@ class VideoDownloader {
|
|
|
488
501
|
return {
|
|
489
502
|
...result,
|
|
490
503
|
quality: userQuality,
|
|
491
|
-
qualityLabel: this.qualityManager.getQualityLabel(userQuality)
|
|
504
|
+
qualityLabel: this.qualityManager.getQualityLabel(userQuality),
|
|
505
|
+
userId: userId,
|
|
506
|
+
isPremium: isPremium
|
|
492
507
|
};
|
|
493
508
|
} finally {
|
|
494
509
|
// Qualität zurücksetzen
|
package/src/platforms/youtube.js
CHANGED
|
@@ -24,7 +24,7 @@ module.exports = {
|
|
|
24
24
|
await this.browser.startSession();
|
|
25
25
|
|
|
26
26
|
// Methode 1: InnerTube API mit verschiedenen Clients
|
|
27
|
-
const clientPriority = ['android', 'ios', 'tvEmbedded', 'web'];
|
|
27
|
+
const clientPriority = ['android', 'androidMusic', 'ios', 'webEmbedded', 'tvEmbedded', 'androidTv', 'web'];
|
|
28
28
|
|
|
29
29
|
// Get target quality from options
|
|
30
30
|
const targetQuality = options?.quality || null;
|
|
@@ -187,8 +187,10 @@ module.exports = {
|
|
|
187
187
|
|
|
188
188
|
if (decipherFunc) console.log('✓ Decipher function found');
|
|
189
189
|
if (nFunc) console.log('✓ N-transform function found');
|
|
190
|
+
else console.log('⚠ No N-transform function found - URLs may have throttling issues');
|
|
190
191
|
|
|
191
192
|
let successCount = 0;
|
|
193
|
+
let nTransformAttempts = 0;
|
|
192
194
|
|
|
193
195
|
for (const format of formats) {
|
|
194
196
|
// Schritt 1: URL aus signatureCipher extrahieren
|
|
@@ -214,12 +216,14 @@ module.exports = {
|
|
|
214
216
|
if (format.url) {
|
|
215
217
|
const nMatch = format.url.match(/[&?]n=([^&]+)/);
|
|
216
218
|
if (nMatch) {
|
|
219
|
+
nTransformAttempts++;
|
|
220
|
+
|
|
217
221
|
if (nFunc) {
|
|
218
222
|
try {
|
|
219
223
|
const n = decodeURIComponent(nMatch[1]);
|
|
220
224
|
const newN = nFunc(n);
|
|
221
225
|
|
|
222
|
-
if (newN && newN !== n) {
|
|
226
|
+
if (newN && newN !== n && typeof newN === 'string') {
|
|
223
227
|
format.url = format.url.replace(
|
|
224
228
|
/([&?])n=[^&]+/,
|
|
225
229
|
`$1n=${encodeURIComponent(newN)}`
|
|
@@ -231,20 +235,21 @@ module.exports = {
|
|
|
231
235
|
format.nTransformed = false;
|
|
232
236
|
}
|
|
233
237
|
} catch (e) {
|
|
234
|
-
|
|
235
|
-
//
|
|
238
|
+
// N-Transform fehlgeschlagen - behalte Original-URL
|
|
239
|
+
// Dies ist besser als gar keine URL zu haben
|
|
236
240
|
format.nTransformed = false;
|
|
237
241
|
}
|
|
238
242
|
} else {
|
|
239
243
|
// Kein N-Transform gefunden - behalte Original-URL
|
|
240
|
-
console.warn('No N-transform function, keeping original N parameter');
|
|
241
244
|
format.nTransformed = false;
|
|
242
245
|
}
|
|
243
246
|
}
|
|
244
247
|
}
|
|
245
248
|
}
|
|
246
249
|
|
|
247
|
-
|
|
250
|
+
if (nTransformAttempts > 0) {
|
|
251
|
+
console.log(`N-parameter transformed for ${successCount}/${nTransformAttempts} formats`);
|
|
252
|
+
}
|
|
248
253
|
|
|
249
254
|
return formats;
|
|
250
255
|
},
|
|
@@ -298,9 +303,9 @@ module.exports = {
|
|
|
298
303
|
try {
|
|
299
304
|
const vm = require('vm');
|
|
300
305
|
|
|
301
|
-
// Erweiterte Patterns für N-Funktion (
|
|
306
|
+
// Erweiterte Patterns für N-Funktion (2026 Update - mehr Patterns)
|
|
302
307
|
const patterns = [
|
|
303
|
-
// Pattern 1: Enhanced throttling function
|
|
308
|
+
// Pattern 1: Enhanced throttling function (klassisch)
|
|
304
309
|
/\.get\("n"\)\)&&\(b=([a-zA-Z0-9$]+)(?:\[(\d+)\])?\(b\)/,
|
|
305
310
|
|
|
306
311
|
// Pattern 2: Direct assignment
|
|
@@ -309,11 +314,19 @@ module.exports = {
|
|
|
309
314
|
// Pattern 3: Array access
|
|
310
315
|
/([a-zA-Z0-9$]+)\[(\d+)\]\(b\)/,
|
|
311
316
|
|
|
312
|
-
// Pattern 4:
|
|
313
|
-
|
|
317
|
+
// Pattern 4: Neue YouTube Patterns (2025/2026)
|
|
318
|
+
/\.get\("n"\)\)&&\(b=([a-zA-Z0-9$]+)(?:\[(\d+)\]|\(b\))/,
|
|
319
|
+
/b=([a-zA-Z0-9$]+)\(b\).*?\.get\("n"\)/,
|
|
320
|
+
/\.set\("n",([a-zA-Z0-9$]+)(?:\[(\d+)\])?\(/,
|
|
321
|
+
|
|
322
|
+
// Pattern 5: Function in object
|
|
323
|
+
/\{[^}]*n:function\([a-zA-Z]\)\{[^}]*\}/,
|
|
314
324
|
|
|
315
|
-
// Pattern
|
|
316
|
-
|
|
325
|
+
// Pattern 6: Throttling parameter
|
|
326
|
+
/&&\(b=([a-zA-Z0-9$]+)\[(\d+)\]\(b\)\)/,
|
|
327
|
+
|
|
328
|
+
// Pattern 7: Direct n parameter handling
|
|
329
|
+
/[,;]([a-zA-Z0-9$]+)=function\([a-zA-Z]\)\{[^}]{50,500}?\.split\(""\)[^}]{50,500}?\.join\(""\)\}/
|
|
317
330
|
];
|
|
318
331
|
|
|
319
332
|
let funcName = null;
|
|
@@ -330,8 +343,31 @@ module.exports = {
|
|
|
330
343
|
}
|
|
331
344
|
|
|
332
345
|
if (!funcName) {
|
|
333
|
-
|
|
334
|
-
|
|
346
|
+
// Fallback: Suche nach alternativen Patterns im gesamten Code
|
|
347
|
+
console.warn('Primary N-function patterns failed, trying fallback search...');
|
|
348
|
+
|
|
349
|
+
// Suche nach typischen N-Transform Charakteristiken
|
|
350
|
+
const fallbackPatterns = [
|
|
351
|
+
// Suche nach Funktionen die mit split/join arbeiten (typisch für N-Transform)
|
|
352
|
+
/([a-zA-Z0-9$_]+)\s*=\s*function\([a-zA-Z]\)\{[^}]*\.split\(""\)[^}]*\.reverse\(\)[^}]*\.join\(""\)/,
|
|
353
|
+
/function\s+([a-zA-Z0-9$_]+)\([a-zA-Z]\)\{[^}]*\.split\(""\)[^}]*\.splice\([^}]*\.join\(""\)/,
|
|
354
|
+
// Suche nach throttle/n-parameter bezogenen Funktionen
|
|
355
|
+
/([a-zA-Z0-9$_]+)\s*=\s*function\([a-zA-Z]\)\{[^}]{100,800}?return[^}]*\.join\(""\)\}/
|
|
356
|
+
];
|
|
357
|
+
|
|
358
|
+
for (let i = 0; i < fallbackPatterns.length; i++) {
|
|
359
|
+
const m = code.match(fallbackPatterns[i]);
|
|
360
|
+
if (m) {
|
|
361
|
+
funcName = m[1];
|
|
362
|
+
console.log(`✓ Found N-function using fallback pattern ${i + 1}: ${funcName}`);
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (!funcName) {
|
|
368
|
+
console.warn('Could not find N-transform function with any pattern');
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
335
371
|
}
|
|
336
372
|
|
|
337
373
|
// Versuche die Funktion zu extrahieren
|
|
@@ -350,6 +386,7 @@ module.exports = {
|
|
|
350
386
|
vm.runInContext(arrayContent, ctx);
|
|
351
387
|
|
|
352
388
|
if (ctx[funcName] && ctx[funcName][arrayIndex]) {
|
|
389
|
+
console.log('✓ N-function extracted from array');
|
|
353
390
|
return ctx[funcName][arrayIndex];
|
|
354
391
|
}
|
|
355
392
|
} catch (e) {
|
|
@@ -358,17 +395,25 @@ module.exports = {
|
|
|
358
395
|
}
|
|
359
396
|
}
|
|
360
397
|
|
|
361
|
-
// Methode 2: Direkte Funktionsextraktion
|
|
398
|
+
// Methode 2: Direkte Funktionsextraktion (erweitert)
|
|
362
399
|
const funcPatterns = [
|
|
363
|
-
|
|
364
|
-
new RegExp(
|
|
365
|
-
new RegExp(`
|
|
400
|
+
// Standard function assignment
|
|
401
|
+
new RegExp(`${funcName.replace(/\$/g, '\\$')}\\s*=\\s*function\\([^)]+\\)\\{[\\s\\S]{1,3000}?\\}`, 'g'),
|
|
402
|
+
new RegExp(`var ${funcName.replace(/\$/g, '\\$')}\\s*=\\s*function\\([^)]+\\)\\{[\\s\\S]{1,3000}?\\}`, 'g'),
|
|
403
|
+
new RegExp(`function ${funcName.replace(/\$/g, '\\$')}\\([^)]+\\)\\{[\\s\\S]{1,3000}?\\}`, 'g'),
|
|
404
|
+
|
|
405
|
+
// Arrow function
|
|
406
|
+
new RegExp(`${funcName.replace(/\$/g, '\\$')}\\s*=\\s*\\([^)]+\\)\\s*=>\\s*\\{[\\s\\S]{1,3000}?\\}`, 'g'),
|
|
407
|
+
|
|
408
|
+
// Const/let declaration
|
|
409
|
+
new RegExp(`(?:const|let)\\s+${funcName.replace(/\$/g, '\\$')}\\s*=\\s*function\\([^)]+\\)\\{[\\s\\S]{1,3000}?\\}`, 'g')
|
|
366
410
|
];
|
|
367
411
|
|
|
368
412
|
for (const pattern of funcPatterns) {
|
|
369
413
|
const matches = code.match(pattern);
|
|
370
414
|
if (matches && matches.length > 0) {
|
|
371
415
|
funcCode = matches[0];
|
|
416
|
+
console.log('✓ N-function code extracted');
|
|
372
417
|
break;
|
|
373
418
|
}
|
|
374
419
|
}
|
|
@@ -380,7 +425,29 @@ module.exports = {
|
|
|
380
425
|
|
|
381
426
|
// Versuche die Funktion auszuführen
|
|
382
427
|
try {
|
|
383
|
-
|
|
428
|
+
// Extrahiere auch mögliche Abhängigkeiten
|
|
429
|
+
let fullCode = funcCode;
|
|
430
|
+
|
|
431
|
+
// Suche nach referenzierten Variablen/Funktionen im Code
|
|
432
|
+
const references = funcCode.match(/\b([a-zA-Z0-9$_]{1,3})\b(?=\(|\[|\.)/g);
|
|
433
|
+
if (references) {
|
|
434
|
+
const uniqueRefs = [...new Set(references)].filter(ref =>
|
|
435
|
+
ref.length <= 3 && // Typische obfuscated Namen
|
|
436
|
+
!['if', 'for', 'var', 'let', 'new', 'try'].includes(ref)
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
// Versuche diese Referenzen zu finden
|
|
440
|
+
for (const ref of uniqueRefs) {
|
|
441
|
+
const refPattern = new RegExp(`(?:var|const|let|,)\\s*${ref.replace(/\$/g, '\\$')}\\s*=\\s*(?:function|\\{)[\\s\\S]{1,500}?[};]`, 'g');
|
|
442
|
+
const refMatch = code.match(refPattern);
|
|
443
|
+
if (refMatch && refMatch[0]) {
|
|
444
|
+
fullCode = refMatch[0] + '\n' + fullCode;
|
|
445
|
+
console.log(`✓ Found dependency: ${ref}`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
fullCode = `${fullCode}\n${funcName};`;
|
|
384
451
|
|
|
385
452
|
const ctx = {};
|
|
386
453
|
vm.createContext(ctx);
|
|
@@ -395,6 +462,7 @@ module.exports = {
|
|
|
395
462
|
}
|
|
396
463
|
} catch (testError) {
|
|
397
464
|
console.warn('N-function test failed:', testError.message);
|
|
465
|
+
// Versuche trotzdem zurückzugeben - könnte bei echten Werten funktionieren
|
|
398
466
|
}
|
|
399
467
|
|
|
400
468
|
return func;
|
|
@@ -500,12 +568,29 @@ module.exports = {
|
|
|
500
568
|
},
|
|
501
569
|
|
|
502
570
|
extractVideoId(url) {
|
|
503
|
-
|
|
571
|
+
// Clean URL first (remove whitespace, etc.)
|
|
572
|
+
url = url.trim();
|
|
573
|
+
|
|
574
|
+
// Standard watch URL: youtube.com/watch?v=VIDEO_ID
|
|
575
|
+
let m = url.match(/[?&]v=([a-zA-Z0-9_-]{11})/);
|
|
576
|
+
if (m) return m[1];
|
|
577
|
+
|
|
578
|
+
// Short URL: youtu.be/VIDEO_ID
|
|
579
|
+
m = url.match(/youtu\.be\/([a-zA-Z0-9_-]{11})/);
|
|
580
|
+
if (m) return m[1];
|
|
581
|
+
|
|
582
|
+
// Embed URL: youtube.com/embed/VIDEO_ID
|
|
583
|
+
m = url.match(/\/embed\/([a-zA-Z0-9_-]{11})/);
|
|
504
584
|
if (m) return m[1];
|
|
505
|
-
|
|
585
|
+
|
|
586
|
+
// Shorts URL: youtube.com/shorts/VIDEO_ID
|
|
587
|
+
m = url.match(/\/shorts\/([a-zA-Z0-9_-]{11})/);
|
|
506
588
|
if (m) return m[1];
|
|
507
|
-
|
|
589
|
+
|
|
590
|
+
// Fallback: Try to extract any 11-character video ID pattern
|
|
591
|
+
m = url.match(/([a-zA-Z0-9_-]{11})/);
|
|
508
592
|
if (m) return m[1];
|
|
593
|
+
|
|
509
594
|
return null;
|
|
510
595
|
}
|
|
511
596
|
};
|
|
@@ -2,26 +2,33 @@ const axios = require('axios');
|
|
|
2
2
|
|
|
3
3
|
class YouTubeInnerTube {
|
|
4
4
|
constructor() {
|
|
5
|
-
// Verschiedene Client-Konfigurationen zum Testen
|
|
5
|
+
// Verschiedene Client-Konfigurationen zum Testen (2026 Update)
|
|
6
6
|
this.clients = {
|
|
7
7
|
// Android Client - BESTE Option, keine N-Parameter!
|
|
8
8
|
android: {
|
|
9
9
|
clientName: 'ANDROID',
|
|
10
|
-
clientVersion: '19.
|
|
10
|
+
clientVersion: '19.29.37',
|
|
11
11
|
androidSdkVersion: 34,
|
|
12
12
|
apiKey: 'AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w'
|
|
13
13
|
},
|
|
14
|
+
// Android Music - Alternative
|
|
15
|
+
androidMusic: {
|
|
16
|
+
clientName: 'ANDROID_MUSIC',
|
|
17
|
+
clientVersion: '7.11.50',
|
|
18
|
+
androidSdkVersion: 34,
|
|
19
|
+
apiKey: 'AIzaSyAOghZGza2MQSZkY_zfZ370N-PUdXEo8AI'
|
|
20
|
+
},
|
|
14
21
|
// iOS Client
|
|
15
22
|
ios: {
|
|
16
23
|
clientName: 'IOS',
|
|
17
|
-
clientVersion: '19.
|
|
24
|
+
clientVersion: '19.29.1',
|
|
18
25
|
deviceModel: 'iPhone16,2',
|
|
19
26
|
apiKey: 'AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc'
|
|
20
27
|
},
|
|
21
28
|
// Web Client
|
|
22
29
|
web: {
|
|
23
30
|
clientName: 'WEB',
|
|
24
|
-
clientVersion: '2.
|
|
31
|
+
clientVersion: '2.20260227.01.00',
|
|
25
32
|
apiKey: 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8'
|
|
26
33
|
},
|
|
27
34
|
// TV Embedded - oft weniger geschützt
|
|
@@ -29,6 +36,19 @@ class YouTubeInnerTube {
|
|
|
29
36
|
clientName: 'TVHTML5_SIMPLY_EMBEDDED_PLAYER',
|
|
30
37
|
clientVersion: '2.0',
|
|
31
38
|
apiKey: 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8'
|
|
39
|
+
},
|
|
40
|
+
// Web Embedded - Alternative
|
|
41
|
+
webEmbedded: {
|
|
42
|
+
clientName: 'WEB_EMBEDDED_PLAYER',
|
|
43
|
+
clientVersion: '1.20260227.01.00',
|
|
44
|
+
apiKey: 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8'
|
|
45
|
+
},
|
|
46
|
+
// Android TV
|
|
47
|
+
androidTv: {
|
|
48
|
+
clientName: 'ANDROID_TESTSUITE',
|
|
49
|
+
clientVersion: '1.9',
|
|
50
|
+
androidSdkVersion: 34,
|
|
51
|
+
apiKey: 'AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w'
|
|
32
52
|
}
|
|
33
53
|
};
|
|
34
54
|
|
|
@@ -132,10 +152,13 @@ class YouTubeInnerTube {
|
|
|
132
152
|
|
|
133
153
|
getUserAgent(clientType) {
|
|
134
154
|
const agents = {
|
|
135
|
-
web: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
136
|
-
android: 'com.google.android.youtube/19.
|
|
137
|
-
|
|
138
|
-
|
|
155
|
+
web: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
|
156
|
+
android: 'com.google.android.youtube/19.29.37 (Linux; U; Android 14; en_US) gzip',
|
|
157
|
+
androidMusic: 'com.google.android.apps.youtube.music/7.11.50 (Linux; U; Android 14; en_US) gzip',
|
|
158
|
+
ios: 'com.google.ios.youtube/19.29.1 (iPhone16,2; U; CPU iOS 18_0 like Mac OS X; en_US)',
|
|
159
|
+
tvEmbedded: 'Mozilla/5.0 (ChromiumStylePlatform) Cobalt/Version',
|
|
160
|
+
webEmbedded: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
|
161
|
+
androidTv: 'com.google.android.youtube/1.9 (Linux; U; Android 14; en_US) gzip'
|
|
139
162
|
};
|
|
140
163
|
|
|
141
164
|
return agents[clientType] || agents.android;
|