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,201 @@
1
+ const axios = require('axios');
2
+
3
+ class YouTubeInnerTube {
4
+ constructor() {
5
+ // Verschiedene Client-Konfigurationen zum Testen
6
+ this.clients = {
7
+ // Android Client - BESTE Option, keine N-Parameter!
8
+ android: {
9
+ clientName: 'ANDROID',
10
+ clientVersion: '19.09.37',
11
+ androidSdkVersion: 34,
12
+ apiKey: 'AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w'
13
+ },
14
+ // iOS Client
15
+ ios: {
16
+ clientName: 'IOS',
17
+ clientVersion: '19.09.3',
18
+ deviceModel: 'iPhone16,2',
19
+ apiKey: 'AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc'
20
+ },
21
+ // Web Client
22
+ web: {
23
+ clientName: 'WEB',
24
+ clientVersion: '2.20240304.00.00',
25
+ apiKey: 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8'
26
+ },
27
+ // TV Embedded - oft weniger geschützt
28
+ tvEmbedded: {
29
+ clientName: 'TVHTML5_SIMPLY_EMBEDDED_PLAYER',
30
+ clientVersion: '2.0',
31
+ apiKey: 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8'
32
+ }
33
+ };
34
+
35
+ this.currentClient = 'android'; // Android als Standard
36
+ }
37
+
38
+ getContext(clientType = this.currentClient) {
39
+ const client = this.clients[clientType];
40
+
41
+ const context = {
42
+ client: {
43
+ clientName: client.clientName,
44
+ clientVersion: client.clientVersion,
45
+ hl: 'en',
46
+ gl: 'US'
47
+ }
48
+ };
49
+
50
+ if (client.androidSdkVersion) {
51
+ context.client.androidSdkVersion = client.androidSdkVersion;
52
+ context.client.osName = 'Android';
53
+ context.client.osVersion = '14';
54
+ context.client.platform = 'MOBILE';
55
+ }
56
+
57
+ if (client.deviceModel) {
58
+ context.client.deviceModel = client.deviceModel;
59
+ context.client.platform = 'MOBILE';
60
+ }
61
+
62
+ // Für TV/Embedded Clients
63
+ if (clientType === 'tvEmbedded') {
64
+ context.thirdParty = {
65
+ embedUrl: 'https://www.youtube.com/'
66
+ };
67
+ }
68
+
69
+ return context;
70
+ }
71
+
72
+ async getVideoInfo(videoId, clientType = this.currentClient) {
73
+ const client = this.clients[clientType];
74
+ const url = `https://www.youtube.com/youtubei/v1/player?key=${client.apiKey}&prettyPrint=false`;
75
+
76
+ const payload = {
77
+ videoId: videoId,
78
+ context: this.getContext(clientType),
79
+ playbackContext: {
80
+ contentPlaybackContext: {
81
+ html5Preference: 'HTML5_PREF_WANTS',
82
+ signatureTimestamp: Math.floor(Date.now() / 1000)
83
+ }
84
+ },
85
+ contentCheckOk: true,
86
+ racyCheckOk: true
87
+ };
88
+
89
+ const headers = {
90
+ 'Content-Type': 'application/json',
91
+ 'User-Agent': this.getUserAgent(clientType),
92
+ 'Accept': '*/*',
93
+ 'Accept-Language': 'en-US,en;q=0.9',
94
+ 'Origin': 'https://www.youtube.com',
95
+ 'Referer': 'https://www.youtube.com/'
96
+ };
97
+
98
+ if (clientType === 'android' || clientType === 'androidTv') {
99
+ headers['X-YouTube-Client-Name'] = '3';
100
+ headers['X-YouTube-Client-Version'] = client.clientVersion;
101
+ }
102
+
103
+ try {
104
+ const response = await axios.post(url, payload, {
105
+ headers: headers,
106
+ httpsAgent: new (require('https').Agent)({
107
+ rejectUnauthorized: false
108
+ })
109
+ });
110
+
111
+ return response.data;
112
+ } catch (error) {
113
+ // Versuche andere Clients in dieser Reihenfolge
114
+ if (clientType === this.currentClient) {
115
+ const alternatives = ['tv', 'webEmbedded', 'androidTv', 'ios', 'android', 'web'];
116
+
117
+ for (const alt of alternatives) {
118
+ if (alt === clientType) continue;
119
+
120
+ try {
121
+ console.log(`Trying ${alt} client...`);
122
+ return await this.getVideoInfo(videoId, alt);
123
+ } catch (e) {
124
+ continue;
125
+ }
126
+ }
127
+ }
128
+
129
+ throw new Error(`All InnerTube clients failed: ${error.message}`);
130
+ }
131
+ }
132
+
133
+ getUserAgent(clientType) {
134
+ const agents = {
135
+ web: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
136
+ android: 'com.google.android.youtube/19.09.37 (Linux; U; Android 14; en_US) gzip',
137
+ ios: 'com.google.ios.youtube/19.09.3 (iPhone16,2; U; CPU iOS 17_4 like Mac OS X; en_US)',
138
+ tvEmbedded: 'Mozilla/5.0 (ChromiumStylePlatform) Cobalt/Version'
139
+ };
140
+
141
+ return agents[clientType] || agents.android;
142
+ }
143
+
144
+ selectBestFormat(formats) {
145
+ if (!formats || formats.length === 0) {
146
+ return null;
147
+ }
148
+
149
+ // Bevorzuge Formate mit Audio + Video
150
+ const withBoth = formats.filter(f =>
151
+ f.mimeType &&
152
+ f.mimeType.includes('video') &&
153
+ f.audioQuality &&
154
+ f.url
155
+ );
156
+
157
+ if (withBoth.length > 0) {
158
+ withBoth.sort((a, b) => {
159
+ const heightA = a.height || 0;
160
+ const heightB = b.height || 0;
161
+ return heightB - heightA;
162
+ });
163
+ return withBoth[0];
164
+ }
165
+
166
+ // Fallback: Nur Video
167
+ const videoOnly = formats.filter(f =>
168
+ f.mimeType &&
169
+ f.mimeType.includes('video') &&
170
+ f.url
171
+ );
172
+
173
+ if (videoOnly.length > 0) {
174
+ videoOnly.sort((a, b) => (b.height || 0) - (a.height || 0));
175
+ return videoOnly[0];
176
+ }
177
+
178
+ return formats.find(f => f.url);
179
+ }
180
+
181
+ selectBestAudio(formats) {
182
+ if (!formats || formats.length === 0) {
183
+ return null;
184
+ }
185
+
186
+ const audioFormats = formats.filter(f =>
187
+ f.mimeType &&
188
+ f.mimeType.includes('audio') &&
189
+ f.url
190
+ );
191
+
192
+ if (audioFormats.length === 0) {
193
+ return null;
194
+ }
195
+
196
+ audioFormats.sort((a, b) => (b.bitrate || 0) - (a.bitrate || 0));
197
+ return audioFormats[0];
198
+ }
199
+ }
200
+
201
+ module.exports = YouTubeInnerTube;
@@ -0,0 +1,283 @@
1
+ const axios = require('axios');
2
+ const vm = require('vm');
3
+
4
+ class YouTubeParser {
5
+ constructor() {
6
+ this.cache = {
7
+ playerCode: null,
8
+ playerUrl: null,
9
+ timestamp: null
10
+ };
11
+ }
12
+
13
+ async getVideoInfo(videoId, userAgent) {
14
+ // Methode 1: Embedded Player API (oft ohne Signatur)
15
+ try {
16
+ const embedUrl = `https://www.youtube.com/embed/${videoId}`;
17
+ const embedResponse = await axios.get(embedUrl, {
18
+ headers: {
19
+ 'User-Agent': userAgent,
20
+ 'Accept-Language': 'en-US,en;q=0.9'
21
+ },
22
+ httpsAgent: new (require('https').Agent)({
23
+ rejectUnauthorized: false
24
+ })
25
+ });
26
+
27
+ const embedHtml = embedResponse.data;
28
+
29
+ // Extrahiere Player Response aus Embed
30
+ let playerResponse = this.extractPlayerResponse(embedHtml);
31
+
32
+ if (playerResponse) {
33
+ return playerResponse;
34
+ }
35
+ } catch (error) {
36
+ console.log('Embed method failed, trying watch page...');
37
+ }
38
+
39
+ // Methode 2: Watch Page
40
+ const watchUrl = `https://www.youtube.com/watch?v=${videoId}`;
41
+ const response = await axios.get(watchUrl, {
42
+ headers: {
43
+ 'User-Agent': userAgent,
44
+ 'Accept-Language': 'en-US,en;q=0.9',
45
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
46
+ },
47
+ httpsAgent: new (require('https').Agent)({
48
+ rejectUnauthorized: false
49
+ })
50
+ });
51
+
52
+ const html = response.data;
53
+ return this.extractPlayerResponse(html);
54
+ }
55
+
56
+ extractPlayerResponse(html) {
57
+ // Mehrere Patterns versuchen
58
+ const patterns = [
59
+ /var ytInitialPlayerResponse = ({.+?});/,
60
+ /ytInitialPlayerResponse\s*=\s*({.+?});/,
61
+ /"playerResponse":"({.+?})"/,
62
+ /ytInitialPlayerResponse\s*=\s*({.+?});<\/script>/
63
+ ];
64
+
65
+ for (const pattern of patterns) {
66
+ const match = html.match(pattern);
67
+ if (match) {
68
+ try {
69
+ let jsonStr = match[1];
70
+
71
+ // Wenn escaped JSON
72
+ if (jsonStr.startsWith('"')) {
73
+ jsonStr = JSON.parse(jsonStr);
74
+ }
75
+
76
+ return JSON.parse(jsonStr);
77
+ } catch (e) {
78
+ continue;
79
+ }
80
+ }
81
+ }
82
+
83
+ throw new Error('Could not extract player response');
84
+ }
85
+
86
+ async getPlayerCode(videoId, userAgent) {
87
+ // Cache prüfen (5 Minuten gültig)
88
+ if (this.cache.playerCode && this.cache.timestamp) {
89
+ const age = Date.now() - this.cache.timestamp;
90
+ if (age < 5 * 60 * 1000) {
91
+ return { code: this.cache.playerCode, url: this.cache.playerUrl };
92
+ }
93
+ }
94
+
95
+ const watchUrl = `https://www.youtube.com/watch?v=${videoId}`;
96
+ const response = await axios.get(watchUrl, {
97
+ headers: {
98
+ 'User-Agent': userAgent
99
+ },
100
+ httpsAgent: new (require('https').Agent)({
101
+ rejectUnauthorized: false
102
+ })
103
+ });
104
+
105
+ const html = response.data;
106
+
107
+ // Player URL finden
108
+ const patterns = [
109
+ /"jsUrl":"([^"]+)"/,
110
+ /"PLAYER_JS_URL":"([^"]+)"/,
111
+ /jsUrl":"([^"]+)"/
112
+ ];
113
+
114
+ let playerUrl = null;
115
+ for (const pattern of patterns) {
116
+ const match = html.match(pattern);
117
+ if (match) {
118
+ playerUrl = match[1].replace(/\\\//g, '/');
119
+ break;
120
+ }
121
+ }
122
+
123
+ if (!playerUrl) {
124
+ throw new Error('Could not find player URL');
125
+ }
126
+
127
+ if (playerUrl.startsWith('//')) {
128
+ playerUrl = 'https:' + playerUrl;
129
+ } else if (playerUrl.startsWith('/')) {
130
+ playerUrl = 'https://www.youtube.com' + playerUrl;
131
+ }
132
+
133
+ // Player Code laden
134
+ const playerResponse = await axios.get(playerUrl, {
135
+ headers: {
136
+ 'User-Agent': userAgent
137
+ },
138
+ httpsAgent: new (require('https').Agent)({
139
+ rejectUnauthorized: false
140
+ })
141
+ });
142
+
143
+ const playerCode = playerResponse.data;
144
+
145
+ // Cache speichern
146
+ this.cache = {
147
+ playerCode: playerCode,
148
+ playerUrl: playerUrl,
149
+ timestamp: Date.now()
150
+ };
151
+
152
+ return { code: playerCode, url: playerUrl };
153
+ }
154
+
155
+ decipherSignature(signature, playerCode) {
156
+ try {
157
+ // N-Parameter Transform finden (wichtig für 403 Vermeidung)
158
+ const nTransform = this.extractNTransform(playerCode);
159
+
160
+ // Signature Decipher finden
161
+ const decipherFunc = this.extractDecipherFunc(playerCode);
162
+
163
+ return {
164
+ signature: decipherFunc ? decipherFunc(signature) : signature,
165
+ nTransform: nTransform
166
+ };
167
+ } catch (error) {
168
+ console.warn('Decipher failed:', error.message);
169
+ return { signature: signature, nTransform: null };
170
+ }
171
+ }
172
+
173
+ extractNTransform(playerCode) {
174
+ // N-Parameter ist kritisch für 403-Vermeidung
175
+ try {
176
+ const patterns = [
177
+ /&&\(b=([a-zA-Z0-9$]+)\(decodeURIComponent\(b\)\)/,
178
+ /\(b=([a-zA-Z0-9$]+)\(decodeURIComponent\(b\)\)\)/,
179
+ /\.get\("n"\)\)&&\(b=([a-zA-Z0-9$]+)(?:\[(\d+)\])?\([a-zA-Z]\)/
180
+ ];
181
+
182
+ for (const pattern of patterns) {
183
+ const match = playerCode.match(pattern);
184
+ if (match) {
185
+ const funcName = match[1];
186
+
187
+ // Funktion extrahieren
188
+ const funcPattern = new RegExp(`${funcName.replace(/\$/g, '\\$')}=function\\([^)]+\\)\\{[^}]+\\}`, 'g');
189
+ const funcMatch = playerCode.match(funcPattern);
190
+
191
+ if (funcMatch) {
192
+ const code = funcMatch[0];
193
+ const context = {};
194
+ vm.createContext(context);
195
+ vm.runInContext(code, context);
196
+ return context[funcName];
197
+ }
198
+ }
199
+ }
200
+ } catch (error) {
201
+ console.warn('N-transform extraction failed:', error.message);
202
+ }
203
+
204
+ return null;
205
+ }
206
+
207
+ extractDecipherFunc(playerCode) {
208
+ // Vereinfachter Ansatz: Suche nach bekannten Patterns
209
+ try {
210
+ // Pattern 1: Standard decipher
211
+ const patterns = [
212
+ /([a-zA-Z0-9$]+)=function\([a-zA-Z]\)\{[a-zA-Z]=\1\.split\(""\)/,
213
+ /\b([a-zA-Z0-9$]{2,})\s*=\s*function\(\s*a\s*\)\s*\{\s*a\s*=\s*a\.split\(\s*""\s*\)/
214
+ ];
215
+
216
+ for (const pattern of patterns) {
217
+ const match = playerCode.match(pattern);
218
+ if (match) {
219
+ const funcName = match[1];
220
+
221
+ // Extrahiere Funktion und Helper
222
+ const funcPattern = new RegExp(`${funcName.replace(/\$/g, '\\$')}=function\\([a-zA-Z]\\)\\{[^}]+\\}`, 'g');
223
+ const funcMatch = playerCode.match(funcPattern);
224
+
225
+ if (!funcMatch) continue;
226
+
227
+ const funcBody = funcMatch[0];
228
+ const helperMatch = funcBody.match(/;([a-zA-Z0-9$]+)\./);
229
+
230
+ if (!helperMatch) continue;
231
+
232
+ const helperName = helperMatch[1];
233
+ const helperPattern = new RegExp(`var ${helperName.replace(/\$/g, '\\$')}=\\{[\\s\\S]+?\\}\\};`);
234
+ const helperMatch2 = playerCode.match(helperPattern);
235
+
236
+ if (!helperMatch2) continue;
237
+
238
+ const code = `${helperMatch2[0]}\n${funcBody}\n${funcName};`;
239
+
240
+ const context = {};
241
+ vm.createContext(context);
242
+ return vm.runInContext(code, context);
243
+ }
244
+ }
245
+ } catch (error) {
246
+ console.warn('Decipher extraction failed:', error.message);
247
+ }
248
+
249
+ return null;
250
+ }
251
+
252
+ parseSignatureCipher(cipher) {
253
+ const params = new URLSearchParams(cipher);
254
+ return {
255
+ url: params.get('url'),
256
+ s: params.get('s'),
257
+ sp: params.get('sp') || 'signature'
258
+ };
259
+ }
260
+
261
+ buildUrl(baseUrl, signature, sp, nParam, nTransform) {
262
+ let url = baseUrl;
263
+
264
+ // Signatur hinzufügen
265
+ if (signature && sp) {
266
+ url += `&${sp}=${encodeURIComponent(signature)}`;
267
+ }
268
+
269
+ // N-Parameter transformieren (wichtig!)
270
+ if (nParam && nTransform) {
271
+ try {
272
+ const transformedN = nTransform(nParam);
273
+ url = url.replace(`&n=${nParam}`, `&n=${transformedN}`);
274
+ } catch (error) {
275
+ console.warn('N-transform failed:', error.message);
276
+ }
277
+ }
278
+
279
+ return url;
280
+ }
281
+ }
282
+
283
+ module.exports = YouTubeParser;
@@ -0,0 +1,94 @@
1
+ const axios = require('axios');
2
+
3
+ class YouTubeSearch {
4
+ constructor() {
5
+ this.baseUrl = 'https://www.youtube.com/results';
6
+ }
7
+
8
+ async search(query, maxResults = 5) {
9
+ try {
10
+ const response = await axios.get(this.baseUrl, {
11
+ params: { search_query: query },
12
+ headers: {
13
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
14
+ }
15
+ });
16
+
17
+ const html = response.data;
18
+
19
+ // Extrahiere ytInitialData
20
+ const match = html.match(/var ytInitialData = ({.+?});/);
21
+ if (!match) {
22
+ throw new Error('Could not extract search results');
23
+ }
24
+
25
+ const data = JSON.parse(match[1]);
26
+
27
+ // Navigiere durch die Datenstruktur
28
+ const contents = data?.contents?.twoColumnSearchResultsRenderer?.primaryContents?.sectionListRenderer?.contents;
29
+
30
+ if (!contents) {
31
+ return [];
32
+ }
33
+
34
+ const results = [];
35
+
36
+ for (const section of contents) {
37
+ const items = section?.itemSectionRenderer?.contents || [];
38
+
39
+ for (const item of items) {
40
+ if (item.videoRenderer && results.length < maxResults) {
41
+ const video = item.videoRenderer;
42
+
43
+ results.push({
44
+ videoId: video.videoId,
45
+ title: video.title?.runs?.[0]?.text || 'Unknown',
46
+ url: `https://www.youtube.com/watch?v=${video.videoId}`,
47
+ thumbnail: video.thumbnail?.thumbnails?.[0]?.url || '',
48
+ duration: this.parseDuration(video.lengthText?.simpleText),
49
+ author: video.ownerText?.runs?.[0]?.text || 'Unknown',
50
+ views: video.viewCountText?.simpleText || '0 views',
51
+ publishedTime: video.publishedTimeText?.simpleText || 'Unknown'
52
+ });
53
+ }
54
+ }
55
+ }
56
+
57
+ return results;
58
+ } catch (error) {
59
+ throw new Error(`YouTube search failed: ${error.message}`);
60
+ }
61
+ }
62
+
63
+ parseDuration(durationText) {
64
+ if (!durationText) return 0;
65
+
66
+ const parts = durationText.split(':').reverse();
67
+ let seconds = 0;
68
+
69
+ if (parts[0]) seconds += parseInt(parts[0]);
70
+ if (parts[1]) seconds += parseInt(parts[1]) * 60;
71
+ if (parts[2]) seconds += parseInt(parts[2]) * 3600;
72
+
73
+ return seconds;
74
+ }
75
+
76
+ async searchAndDownload(query, downloader, options = {}) {
77
+ const results = await this.search(query, 1);
78
+
79
+ if (results.length === 0) {
80
+ throw new Error('No results found');
81
+ }
82
+
83
+ const video = results[0];
84
+ console.log(`Found: ${video.title} by ${video.author}`);
85
+
86
+ if (options.audioOnly) {
87
+ return await downloader.downloadAudio(video.url, options.outputPath);
88
+ } else {
89
+ return await downloader.download(video.url, options.outputPath);
90
+ }
91
+ }
92
+ }
93
+
94
+ module.exports = YouTubeSearch;