@oreohq/ytdl-core 4.15.2 → 4.16.5
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/README.md +46 -137
- package/lib/agent.js +9 -1
- package/lib/format-utils.js +2 -4
- package/lib/index.js +9 -6
- package/lib/info-extras.js +19 -32
- package/lib/info.js +105 -51
- package/lib/sig.js +68 -70
- package/lib/utils.js +93 -78
- package/package.json +13 -18
- package/typings/index.d.ts +3 -2
- package/LICENSE +0 -21
package/lib/info.js
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
/* eslint-disable no-unused-vars */
|
2
2
|
const sax = require("sax");
|
3
|
+
|
3
4
|
const utils = require("./utils");
|
4
5
|
// Forces Node JS version of setTimeout for Electron based applications
|
5
6
|
const { setTimeout } = require("timers");
|
@@ -68,10 +69,8 @@ exports.getBasicInfo = async (id, options) => {
|
|
68
69
|
info.videoDetails = extras.cleanVideoDetails(
|
69
70
|
Object.assign(
|
70
71
|
{},
|
71
|
-
info.player_response
|
72
|
-
|
73
|
-
info.player_response.microformat.playerMicroformatRenderer,
|
74
|
-
info.player_response && info.player_response.videoDetails,
|
72
|
+
info.player_response?.microformat?.playerMicroformatRenderer,
|
73
|
+
info.player_response?.videoDetails,
|
75
74
|
additional,
|
76
75
|
),
|
77
76
|
info,
|
@@ -96,7 +95,7 @@ const getEmbedPageBody = (id, options) => {
|
|
96
95
|
const getHTML5player = body => {
|
97
96
|
let html5playerRes =
|
98
97
|
/<script\s+src="([^"]+)"(?:\s+type="text\/javascript")?\s+name="player_ias\/base"\s*>|"jsUrl":"([^"]+)"/.exec(body);
|
99
|
-
return html5playerRes
|
98
|
+
return html5playerRes?.[1] || html5playerRes?.[2];
|
100
99
|
};
|
101
100
|
|
102
101
|
/**
|
@@ -122,7 +121,7 @@ const retryFunc = async (func, args, options) => {
|
|
122
121
|
result = await func(...args);
|
123
122
|
break;
|
124
123
|
} catch (err) {
|
125
|
-
if (
|
124
|
+
if (err?.statusCode < 500 || currentTry >= options.maxRetries) throw err;
|
126
125
|
let wait = Math.min(++currentTry * options.backoff.inc, options.backoff.max);
|
127
126
|
await new Promise(resolve => setTimeout(resolve, wait));
|
128
127
|
}
|
@@ -153,12 +152,9 @@ const findJSON = (source, varName, body, left, right, prependJSON) => {
|
|
153
152
|
};
|
154
153
|
|
155
154
|
const findPlayerResponse = (source, info) => {
|
155
|
+
if (!info) return {};
|
156
156
|
const player_response =
|
157
|
-
info
|
158
|
-
((info.args && info.args.player_response) ||
|
159
|
-
info.player_response ||
|
160
|
-
info.playerResponse ||
|
161
|
-
info.embedded_player_response);
|
157
|
+
info.args?.player_response || info.player_response || info.playerResponse || info.embedded_player_response;
|
162
158
|
return parseJSON(source, "player_response", player_response);
|
163
159
|
};
|
164
160
|
|
@@ -201,17 +197,11 @@ const getWatchHTMLPage = async (id, options) => {
|
|
201
197
|
* @returns {Array.<Object>}
|
202
198
|
*/
|
203
199
|
const parseFormats = player_response => {
|
204
|
-
|
205
|
-
if (player_response && player_response.streamingData) {
|
206
|
-
formats = formats
|
207
|
-
.concat(player_response.streamingData.formats || [])
|
208
|
-
.concat(player_response.streamingData.adaptiveFormats || []);
|
209
|
-
}
|
210
|
-
return formats;
|
200
|
+
return (player_response?.streamingData?.formats || [])?.concat(player_response?.streamingData?.adaptiveFormats || []);
|
211
201
|
};
|
212
202
|
|
213
203
|
const parseAdditionalManifests = (player_response, options) => {
|
214
|
-
let streamingData = player_response
|
204
|
+
let streamingData = player_response?.streamingData,
|
215
205
|
manifests = [];
|
216
206
|
if (streamingData) {
|
217
207
|
if (streamingData.dashManifestUrl) {
|
@@ -241,7 +231,6 @@ exports.getInfo = async (id, options) => {
|
|
241
231
|
const info = await exports.getBasicInfo(id, options);
|
242
232
|
let funcs = [];
|
243
233
|
|
244
|
-
// Fill in HTML5 player URL
|
245
234
|
info.html5player =
|
246
235
|
info.html5player ||
|
247
236
|
getHTML5player(await getWatchHTMLPageBody(id, options)) ||
|
@@ -251,52 +240,102 @@ exports.getInfo = async (id, options) => {
|
|
251
240
|
throw Error("Unable to find html5player file");
|
252
241
|
}
|
253
242
|
|
254
|
-
|
243
|
+
info.html5player = new URL(info.html5player, BASE_URL).toString();
|
244
|
+
|
245
|
+
let bestPlayerResponse = null;
|
255
246
|
|
256
247
|
try {
|
257
|
-
if (info.videoDetails.age_restricted) throw Error("Cannot download age restricted videos with mobile clients");
|
258
248
|
const promises = [];
|
259
|
-
if (options.playerClients.includes("
|
249
|
+
if (options.playerClients.includes("WEB_EMBEDDED")) promises.push(fetchWebEmbeddedPlayer(id, info, options));
|
250
|
+
if (options.playerClients.includes("TV")) promises.push(fetchTvPlayer(id, info, options));
|
260
251
|
if (options.playerClients.includes("IOS")) promises.push(fetchIosJsonPlayer(id, options));
|
261
252
|
if (options.playerClients.includes("ANDROID")) promises.push(fetchAndroidJsonPlayer(id, options));
|
262
|
-
const responses = await Promise.allSettled(promises);
|
263
|
-
info.formats = [].concat(...responses.map(r => parseFormats(r.value)));
|
264
|
-
if (info.formats.length === 0) throw new Error("Player JSON API failed");
|
265
253
|
|
266
|
-
|
254
|
+
if (promises.length > 0) {
|
255
|
+
const responses = await Promise.allSettled(promises);
|
256
|
+
const successfulResponses = responses
|
257
|
+
.filter(r => r.status === "fulfilled")
|
258
|
+
.map(r => r.value)
|
259
|
+
.filter(r => r);
|
260
|
+
|
261
|
+
console.log(`Found ${successfulResponses.length} successful responses from clients`);
|
267
262
|
|
268
|
-
|
269
|
-
|
270
|
-
funcs.push(
|
263
|
+
if (successfulResponses.length > 0) {
|
264
|
+
bestPlayerResponse = successfulResponses[0];
|
265
|
+
funcs.push(sig.decipherFormats(parseFormats(bestPlayerResponse), info.html5player, options));
|
266
|
+
funcs.push(...parseAdditionalManifests(bestPlayerResponse, options));
|
271
267
|
}
|
272
268
|
}
|
273
269
|
|
274
|
-
if (options.playerClients.includes("WEB")) {
|
275
|
-
|
276
|
-
funcs.push(
|
270
|
+
if (!bestPlayerResponse && options.playerClients.includes("WEB")) {
|
271
|
+
bestPlayerResponse = info.player_response;
|
272
|
+
funcs.push(sig.decipherFormats(parseFormats(info.player_response), info.html5player, options));
|
273
|
+
funcs.push(...parseAdditionalManifests(info.player_response, options));
|
277
274
|
}
|
278
|
-
} catch (
|
279
|
-
|
280
|
-
|
275
|
+
} catch (error) {
|
276
|
+
console.error("Error fetching formats:", error);
|
277
|
+
|
278
|
+
if (!bestPlayerResponse && options.playerClients.includes("WEB")) {
|
279
|
+
bestPlayerResponse = info.player_response;
|
280
|
+
funcs.push(sig.decipherFormats(parseFormats(info.player_response), info.html5player, options));
|
281
|
+
funcs.push(...parseAdditionalManifests(info.player_response, options));
|
282
|
+
}
|
283
|
+
}
|
284
|
+
|
285
|
+
if (funcs.length === 0) {
|
286
|
+
throw new Error("Failed to find any playable formats");
|
281
287
|
}
|
282
288
|
|
283
289
|
let results = await Promise.all(funcs);
|
284
290
|
info.formats = Object.values(Object.assign({}, ...results));
|
285
|
-
|
291
|
+
|
292
|
+
info.formats = info.formats.filter(format => format && format.url && format.mimeType);
|
293
|
+
|
294
|
+
if (info.formats.length === 0) {
|
295
|
+
throw new Error("No playable formats found");
|
296
|
+
}
|
297
|
+
|
298
|
+
|
299
|
+
info.formats = info.formats.map(format => {
|
300
|
+
const enhancedFormat = formatUtils.addFormatMeta(format);
|
301
|
+
|
302
|
+
if (!enhancedFormat.audioBitrate && enhancedFormat.hasAudio) {
|
303
|
+
enhancedFormat.audioBitrate = estimateAudioBitrate(enhancedFormat);
|
304
|
+
}
|
305
|
+
|
306
|
+
if (!enhancedFormat.isHLS && enhancedFormat.mimeType &&
|
307
|
+
(enhancedFormat.mimeType.includes('hls') ||
|
308
|
+
enhancedFormat.mimeType.includes('x-mpegURL') ||
|
309
|
+
enhancedFormat.mimeType.includes('application/vnd.apple.mpegurl'))) {
|
310
|
+
enhancedFormat.isHLS = true;
|
311
|
+
}
|
312
|
+
|
313
|
+
return enhancedFormat;
|
314
|
+
});
|
315
|
+
|
286
316
|
info.formats.sort(formatUtils.sortFormats);
|
287
317
|
|
318
|
+
const bestFormat = info.formats.find(format => format.hasVideo && format.hasAudio) ||
|
319
|
+
info.formats.find(format => format.hasVideo) ||
|
320
|
+
info.formats.find(format => format.hasAudio) ||
|
321
|
+
info.formats[0];
|
322
|
+
|
323
|
+
info.bestFormat = bestFormat;
|
324
|
+
info.videoUrl = bestFormat.url;
|
325
|
+
info.selectedFormat = bestFormat;
|
326
|
+
|
288
327
|
info.full = true;
|
328
|
+
|
289
329
|
return info;
|
290
330
|
};
|
291
331
|
|
292
332
|
const getPlaybackContext = async (html5player, options) => {
|
293
333
|
const body = await utils.request(html5player, options);
|
294
|
-
|
295
|
-
|
334
|
+
const mo = body.match(/(signatureTimestamp|sts):(\d+)/);
|
296
335
|
return {
|
297
336
|
contentPlaybackContext: {
|
298
337
|
html5Preference: "HTML5_PREF_WANTS",
|
299
|
-
signatureTimestamp: mo
|
338
|
+
signatureTimestamp: mo?.[2],
|
300
339
|
},
|
301
340
|
};
|
302
341
|
};
|
@@ -304,26 +343,42 @@ const getPlaybackContext = async (html5player, options) => {
|
|
304
343
|
const LOCALE = { hl: "en", timeZone: "UTC", utcOffsetMinutes: 0 },
|
305
344
|
CHECK_FLAGS = { contentCheckOk: true, racyCheckOk: true };
|
306
345
|
|
307
|
-
const
|
346
|
+
const WEB_EMBEDDED_CONTEXT = {
|
308
347
|
client: {
|
309
|
-
clientName: "
|
310
|
-
clientVersion: "1.
|
348
|
+
clientName: "WEB_EMBEDDED_PLAYER",
|
349
|
+
clientVersion: "1.20240723.01.00",
|
311
350
|
...LOCALE,
|
312
351
|
},
|
313
352
|
};
|
314
353
|
|
315
|
-
const
|
354
|
+
const TVHTML5_CONTEXT = {
|
355
|
+
client: {
|
356
|
+
clientName: "TVHTML5",
|
357
|
+
clientVersion: "7.20241201.18.00",
|
358
|
+
...LOCALE,
|
359
|
+
},
|
360
|
+
};
|
361
|
+
|
362
|
+
const fetchWebEmbeddedPlayer = async (videoId, info, options) => {
|
316
363
|
const payload = {
|
317
|
-
context:
|
364
|
+
context: WEB_EMBEDDED_CONTEXT,
|
318
365
|
videoId,
|
319
|
-
playbackContext: await getPlaybackContext(html5player, options),
|
366
|
+
playbackContext: await getPlaybackContext(info.html5player, options),
|
320
367
|
...CHECK_FLAGS,
|
321
368
|
};
|
322
|
-
|
323
|
-
|
369
|
+
return await playerAPI(videoId, payload, options);
|
370
|
+
};
|
371
|
+
const fetchTvPlayer = async (videoId, info, options) => {
|
372
|
+
const payload = {
|
373
|
+
context: TVHTML5_CONTEXT,
|
374
|
+
videoId,
|
375
|
+
playbackContext: await getPlaybackContext(info.html5player, options),
|
376
|
+
...CHECK_FLAGS,
|
377
|
+
};
|
378
|
+
return await playerAPI(videoId, payload, options);
|
324
379
|
};
|
325
380
|
|
326
|
-
const playerAPI = async (videoId, payload,
|
381
|
+
const playerAPI = async (videoId, payload, options) => {
|
327
382
|
const { jar, dispatcher } = options.agent;
|
328
383
|
const opts = {
|
329
384
|
requestOptions: {
|
@@ -336,8 +391,7 @@ const playerAPI = async (videoId, payload, userAgent, options) => {
|
|
336
391
|
},
|
337
392
|
headers: {
|
338
393
|
"Content-Type": "application/json",
|
339
|
-
|
340
|
-
"User-Agent": userAgent,
|
394
|
+
Cookie: jar.getCookieStringSync("https://www.youtube.com"),
|
341
395
|
"X-Goog-Api-Format-Version": "2",
|
342
396
|
},
|
343
397
|
body: JSON.stringify(payload),
|
package/lib/sig.js
CHANGED
@@ -15,37 +15,47 @@ exports.cache = new Cache(1);
|
|
15
15
|
*/
|
16
16
|
exports.getFunctions = (html5playerfile, options) =>
|
17
17
|
exports.cache.getOrSet(html5playerfile, async () => {
|
18
|
+
// Rewrite tce player script URLs to non-tce variant
|
19
|
+
if (html5playerfile.includes("/player_ias_tce.vflset/")) {
|
20
|
+
console.debug("jsUrl URL points to tce-variant player script, rewriting to non-tce.");
|
21
|
+
html5playerfile = html5playerfile.replace("/player_ias_tce.vflset/", "/player_ias.vflset/");
|
22
|
+
}
|
23
|
+
|
18
24
|
const body = await utils.request(html5playerfile, options);
|
19
25
|
const functions = exports.extractFunctions(body);
|
20
26
|
exports.cache.set(html5playerfile, functions);
|
21
27
|
return functions;
|
22
28
|
});
|
23
29
|
|
24
|
-
//
|
25
|
-
const
|
26
|
-
"\\bm=([a-zA-Z0-9$]{2,})\\(decodeURIComponent\\(h\\.s\\)\\)",
|
27
|
-
"\\bc&&\\(c=([a-zA-Z0-9$]{2,})\\(decodeURIComponent\\(c\\)\\)",
|
28
|
-
'(?:\\b|[^a-zA-Z0-9$])([a-zA-Z0-9$]{2,})\\s*=\\s*function\\(\\s*a\\s*\\)\\s*\\{\\s*a\\s*=\\s*a\\.split\\(\\s*""\\s*\\)',
|
29
|
-
'([\\w$]+)\\s*=\\s*function\\((\\w+)\\)\\{\\s*\\2=\\s*\\2\\.split\\(""\\)\\s*;',
|
30
|
-
];
|
30
|
+
// Updated VARIABLE_PART based on the Java code
|
31
|
+
const VARIABLE_PART = "[a-zA-Z_\\$][a-zA-Z_0-9\\$]*";
|
31
32
|
|
32
|
-
|
33
|
-
|
33
|
+
const DECIPHER_NAME_REGEXPS = {
|
34
|
+
"\\b([a-zA-Z0-9_$]+)&&\\(\\1=([a-zA-Z0-9_$]{2,})\\(decodeURIComponent\\(\\1\\)\\)": 2,
|
35
|
+
'([a-zA-Z0-9_$]+)\\s*=\\s*function\\(\\s*([a-zA-Z0-9_$]+)\\s*\\)\\s*{\\s*\\2\\s*=\\s*\\2\\.split\\(\\s*""\\s*\\)\\s*;\\s*[^}]+;\\s*return\\s+\\2\\.join\\(\\s*""\\s*\\)': 1,
|
36
|
+
'/(?:\\b|[^a-zA-Z0-9_$])([a-zA-Z0-9_$]{2,})\\s*=\\s*function\\(\\s*a\\s*\\)\\s*{\\s*a\\s*=\\s*a\\.split\\(\\s*""\\s*\\)(?:;[a-zA-Z0-9_$]{2}\\.[a-zA-Z0-9_$]{2}\\(a,\\d+\\))?/': 1,
|
37
|
+
"\\bm=([a-zA-Z0-9$]{2,})\\(decodeURIComponent\\(h\\.s\\)\\)": 1,
|
38
|
+
"\\bc&&\\(c=([a-zA-Z0-9$]{2,})\\(decodeURIComponent\\(c\\)\\)": 1,
|
39
|
+
'(?:\\b|[^a-zA-Z0-9$])([a-zA-Z0-9$]{2,})\\s*=\\s*function\\(\\s*a\\s*\\)\\s*\\{\\s*a\\s*=\\s*a\\.split\\(\\s*""\\s*\\)': 1,
|
40
|
+
'([\\w$]+)\\s*=\\s*function\\((\\w+)\\)\\{\\s*\\2=\\s*\\2\\.split\\(""\\)\\s*;': 1,
|
41
|
+
};
|
42
|
+
|
43
|
+
// LavaPlayer regexps - update to use the new VARIABLE_PART
|
34
44
|
const VARIABLE_PART_DEFINE = `\\"?${VARIABLE_PART}\\"?`;
|
35
45
|
const BEFORE_ACCESS = '(?:\\[\\"|\\.)';
|
36
46
|
const AFTER_ACCESS = '(?:\\"\\]|)';
|
37
47
|
const VARIABLE_PART_ACCESS = BEFORE_ACCESS + VARIABLE_PART + AFTER_ACCESS;
|
38
|
-
const REVERSE_PART = ":function\\(
|
39
|
-
const SLICE_PART = ":function\\(
|
40
|
-
const SPLICE_PART = ":function\\(
|
48
|
+
const REVERSE_PART = ":function\\(\\w\\)\\{(?:return )?\\w\\.reverse\\(\\)\\}";
|
49
|
+
const SLICE_PART = ":function\\(\\w,\\w\\)\\{return \\w\\.slice\\(\\w\\)\\}";
|
50
|
+
const SPLICE_PART = ":function\\(\\w,\\w\\)\\{\\w\\.splice\\(0,\\w\\)\\}";
|
41
51
|
const SWAP_PART =
|
42
|
-
":function\\(
|
52
|
+
":function\\(\\w,\\w\\)\\{var \\w=\\w\\[0\\];\\w\\[0\\]=\\w\\[\\w%\\w\\.length\\];\\w\\[\\w(?:%\\w.length|)\\]=\\w(?:;return \\w)?\\}";
|
43
53
|
|
44
54
|
const DECIPHER_REGEXP =
|
45
|
-
`function(?: ${VARIABLE_PART})?\\(a\\)\\{` +
|
46
|
-
|
47
|
-
`((?:(
|
48
|
-
|
55
|
+
`function(?: ${VARIABLE_PART})?\\(([a-zA-Z])\\)\\{` +
|
56
|
+
'\\1=\\1\\.split\\(""\\);\\s*' +
|
57
|
+
`((?:(?:\\1=)?${VARIABLE_PART}${VARIABLE_PART_ACCESS}\\(\\1,\\d+\\);)+)` +
|
58
|
+
'return \\1\\.join\\(""\\)' +
|
49
59
|
`\\}`;
|
50
60
|
|
51
61
|
const HELPER_REGEXP = `var (${VARIABLE_PART})=\\{((?:(?:${VARIABLE_PART_DEFINE}${REVERSE_PART}|${
|
@@ -55,24 +65,14 @@ const HELPER_REGEXP = `var (${VARIABLE_PART})=\\{((?:(?:${VARIABLE_PART_DEFINE}$
|
|
55
65
|
const SCVR = "[a-zA-Z0-9$_]";
|
56
66
|
const MCR = `${SCVR}+`;
|
57
67
|
const AAR = "\\[(\\d+)]";
|
58
|
-
const N_TRANSFORM_NAME_REGEXPS =
|
59
|
-
|
60
|
-
`${SCVR}="nn"\\[\\+${MCR}\\.${MCR}],${MCR}\\(${MCR}\\),${MCR}=${MCR}\\.${MCR}\\[${MCR}]\\|\\|null\\)
|
61
|
-
|
62
|
-
})\\(
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
`${SCVR}="nn"\\[\\+${MCR}\\.${MCR}],${MCR}=${MCR}\\.get\\(${MCR}\\)\\).+\\|\\|(${MCR})\\(""\\)`,
|
67
|
-
`${SCVR}="nn"\\[\\+${MCR}\\.${MCR}],${MCR}=${MCR}\\.get\\(${MCR}\\)\\)&&\\(${MCR}=(${MCR})\\[(\\d+)]`,
|
68
|
-
`\\(${SCVR}=String\\.fromCharCode\\(110\\),${SCVR}=${SCVR}\\.get\\(${SCVR}\\)\\)&&\\(${SCVR}=(${MCR})(?:${AAR})?\\(${
|
69
|
-
SCVR
|
70
|
-
}\\)`,
|
71
|
-
`\\.get\\("n"\\)\\)&&\\(${SCVR}=(${MCR})(?:${AAR})?\\(${SCVR}\\)`,
|
72
|
-
// Skick regexps
|
73
|
-
'(\\w+).length\\|\\|\\w+\\(""\\)',
|
74
|
-
'\\w+.length\\|\\|(\\w+)\\(""\\)',
|
75
|
-
];
|
68
|
+
const N_TRANSFORM_NAME_REGEXPS = {
|
69
|
+
[`${SCVR}="nn"\\[\\+${MCR}\\.${MCR}],${MCR}\\(${MCR}\\),${MCR}=${MCR}\\.${MCR}\\[${MCR}]\\|\\|null\\).+\\|\\|(${MCR})\\(""\\)`]: 1,
|
70
|
+
[`${SCVR}="nn"\\[\\+${MCR}\\.${MCR}],${MCR}\\(${MCR}\\),${MCR}=${MCR}\\.${MCR}\\[${MCR}]\\|\\|null\\)&&\\(${MCR}=(${MCR})${AAR}`]: 1,
|
71
|
+
[`${SCVR}="nn"\\[\\+${MCR}\\.${MCR}],${MCR}=${MCR}\\.get\\(${MCR}\\)\\).+\\|\\|(${MCR})\\(""\\)`]: 1,
|
72
|
+
[`${SCVR}="nn"\\[\\+${MCR}\\.${MCR}],${MCR}=${MCR}\\.get\\(${MCR}\\)\\)&&\\(${MCR}=(${MCR})\\[(\\d+)]`]: 1,
|
73
|
+
[`\\(${SCVR}=String\\.fromCharCode\\(110\\),${SCVR}=${SCVR}\\.get\\(${SCVR}\\)\\)&&\\(${SCVR}=(${MCR})(?:${AAR})?\\(${SCVR}\\)`]: 1,
|
74
|
+
[`\\.get\\("n"\\)\\)&&\\(${SCVR}=(${MCR})(?:${AAR})?\\(${SCVR}\\)`]: 1,
|
75
|
+
};
|
76
76
|
|
77
77
|
// LavaPlayer regexps
|
78
78
|
const N_TRANSFORM_REGEXP =
|
@@ -80,7 +80,7 @@ const N_TRANSFORM_REGEXP =
|
|
80
80
|
"var\\s*(\\w+)=(?:\\1\\.split\\(.*?\\)|String\\.prototype\\.split\\.call\\(\\1,.*?\\))," +
|
81
81
|
"\\s*(\\w+)=(\\[.*?]);\\s*\\3\\[\\d+]" +
|
82
82
|
"(.*?try)(\\{.*?})catch\\(\\s*(\\w+)\\s*\\)\\s*\\{" +
|
83
|
-
'\\s*return"
|
83
|
+
'\\s*return"[\\w-]+([A-z0-9-]+)"\\s*\\+\\s*\\1\\s*}' +
|
84
84
|
'\\s*return\\s*(\\2\\.join\\(""\\)|Array\\.prototype\\.join\\.call\\(\\2,.*?\\))};';
|
85
85
|
|
86
86
|
const DECIPHER_ARGUMENT = "sig";
|
@@ -92,17 +92,15 @@ const matchRegex = (regex, str) => {
|
|
92
92
|
return match;
|
93
93
|
};
|
94
94
|
|
95
|
-
const
|
96
|
-
|
97
|
-
const matchGroup1 = (regex, str) => matchRegex(regex, str)[1];
|
95
|
+
const matchGroup = (regex, str, idx = 0) => matchRegex(regex, str)[idx];
|
98
96
|
|
99
97
|
const getFuncName = (body, regexps) => {
|
100
98
|
let fn;
|
101
|
-
for (const regex of regexps) {
|
99
|
+
for (const [regex, idx] of Object.entries(regexps)) {
|
102
100
|
try {
|
103
|
-
fn =
|
101
|
+
fn = matchGroup(regex, body, idx);
|
104
102
|
try {
|
105
|
-
fn =
|
103
|
+
fn = matchGroup(`${fn.replace(/\$/g, "\\$")}=\\[([a-zA-Z0-9$\\[\\]]{2,})\\]`, body, 1);
|
106
104
|
} catch (err) {
|
107
105
|
// Function name is not inside an array
|
108
106
|
}
|
@@ -111,44 +109,44 @@ const getFuncName = (body, regexps) => {
|
|
111
109
|
continue;
|
112
110
|
}
|
113
111
|
}
|
114
|
-
if (!fn || fn.includes("[")) throw Error();
|
112
|
+
if (!fn || fn.includes("[")) throw Error("Could not match");
|
115
113
|
return fn;
|
116
114
|
};
|
117
115
|
|
118
116
|
const DECIPHER_FUNC_NAME = "DisTubeDecipherFunc";
|
119
|
-
const extractDecipherFunc = body => {
|
117
|
+
const extractDecipherFunc = (exports.d1 = body => {
|
120
118
|
try {
|
121
|
-
const helperObject =
|
122
|
-
const decipherFunc =
|
119
|
+
const helperObject = matchGroup(HELPER_REGEXP, body, 0);
|
120
|
+
const decipherFunc = matchGroup(DECIPHER_REGEXP, body, 0);
|
123
121
|
const resultFunc = `var ${DECIPHER_FUNC_NAME}=${decipherFunc};`;
|
124
122
|
const callerFunc = `${DECIPHER_FUNC_NAME}(${DECIPHER_ARGUMENT});`;
|
125
123
|
return helperObject + resultFunc + callerFunc;
|
126
124
|
} catch (e) {
|
127
125
|
return null;
|
128
126
|
}
|
129
|
-
};
|
127
|
+
});
|
130
128
|
|
131
|
-
const extractDecipherWithName = body => {
|
129
|
+
const extractDecipherWithName = (exports.d2 = body => {
|
132
130
|
try {
|
133
131
|
const decipherFuncName = getFuncName(body, DECIPHER_NAME_REGEXPS);
|
134
132
|
const funcPattern = `(${decipherFuncName.replace(/\$/g, "\\$")}=function\\([a-zA-Z0-9_]+\\)\\{.+?\\})`;
|
135
|
-
const decipherFunc = `var ${
|
136
|
-
const helperObjectName =
|
133
|
+
const decipherFunc = `var ${matchGroup(funcPattern, body, 1)};`;
|
134
|
+
const helperObjectName = matchGroup(";([A-Za-z0-9_\\$]{2,})\\.\\w+\\(", decipherFunc, 1);
|
137
135
|
const helperPattern = `(var ${helperObjectName.replace(/\$/g, "\\$")}=\\{[\\s\\S]+?\\}\\};)`;
|
138
|
-
const helperObject =
|
136
|
+
const helperObject = matchGroup(helperPattern, body, 1);
|
139
137
|
const callerFunc = `${decipherFuncName}(${DECIPHER_ARGUMENT});`;
|
140
138
|
return helperObject + decipherFunc + callerFunc;
|
141
139
|
} catch (e) {
|
142
140
|
return null;
|
143
141
|
}
|
144
|
-
};
|
142
|
+
});
|
145
143
|
|
146
|
-
const getExtractFunctions = (extractFunctions, body) => {
|
144
|
+
const getExtractFunctions = (extractFunctions, body, postProcess = null) => {
|
147
145
|
for (const extractFunction of extractFunctions) {
|
148
146
|
try {
|
149
147
|
const func = extractFunction(body);
|
150
148
|
if (!func) continue;
|
151
|
-
return new vm.Script(func);
|
149
|
+
return new vm.Script(postProcess ? postProcess(func) : func);
|
152
150
|
} catch (err) {
|
153
151
|
continue;
|
154
152
|
}
|
@@ -164,10 +162,11 @@ const extractDecipher = body => {
|
|
164
162
|
if (!decipherFunc && !decipherWarning) {
|
165
163
|
console.warn(
|
166
164
|
"\x1b[33mWARNING:\x1B[0m Could not parse decipher function.\n" +
|
167
|
-
|
165
|
+
"Stream URLs will be missing.\n" +
|
166
|
+
`Please report this issue by uploading the "${utils.saveDebugFile(
|
168
167
|
"base.js",
|
169
168
|
body,
|
170
|
-
)}" file on https://github.com/
|
169
|
+
)}" file on https://github.com/distubejs/ytdl-core/issues/144.`,
|
171
170
|
);
|
172
171
|
decipherWarning = true;
|
173
172
|
}
|
@@ -175,50 +174,49 @@ const extractDecipher = body => {
|
|
175
174
|
};
|
176
175
|
|
177
176
|
const N_TRANSFORM_FUNC_NAME = "DisTubeNTransformFunc";
|
178
|
-
const extractNTransformFunc = body => {
|
177
|
+
const extractNTransformFunc = (exports.n1 = body => {
|
179
178
|
try {
|
180
|
-
const nFunc =
|
179
|
+
const nFunc = matchGroup(N_TRANSFORM_REGEXP, body, 0);
|
181
180
|
const resultFunc = `var ${N_TRANSFORM_FUNC_NAME}=${nFunc}`;
|
182
181
|
const callerFunc = `${N_TRANSFORM_FUNC_NAME}(${N_ARGUMENT});`;
|
183
182
|
return resultFunc + callerFunc;
|
184
183
|
} catch (e) {
|
185
184
|
return null;
|
186
185
|
}
|
187
|
-
};
|
186
|
+
});
|
188
187
|
|
189
|
-
const extractNTransformWithName = body => {
|
188
|
+
const extractNTransformWithName = (exports.n2 = body => {
|
190
189
|
try {
|
191
190
|
const nFuncName = getFuncName(body, N_TRANSFORM_NAME_REGEXPS);
|
192
|
-
const funcPattern = `(${
|
193
|
-
|
194
|
-
// eslint-disable-next-line max-len
|
195
|
-
}=\\s*function([\\S\\s]*?\\}\\s*return (([\\w$]+?\\.join\\(""\\))|(Array\\.prototype\\.join\\.call\\([\\w$]+?,[\\n\\s]*(("")|(\\("",""\\)))\\)))\\s*\\}))`;
|
196
|
-
const nTransformFunc = `var ${matchGroup1(funcPattern, body)};`;
|
191
|
+
const funcPattern = `(${nFuncName.replace(/\$/g, "\\$")}=function\\([a-zA-Z0-9_]+\\)\\{.+?\\})`;
|
192
|
+
const nTransformFunc = `var ${matchGroup(funcPattern, body, 1)};`;
|
197
193
|
const callerFunc = `${nFuncName}(${N_ARGUMENT});`;
|
198
194
|
return nTransformFunc + callerFunc;
|
199
195
|
} catch (e) {
|
200
196
|
return null;
|
201
197
|
}
|
202
|
-
};
|
198
|
+
});
|
199
|
+
|
203
200
|
|
204
201
|
let nTransformWarning = false;
|
205
202
|
const extractNTransform = body => {
|
206
203
|
// Faster: extractNTransformFunc
|
207
|
-
const nTransformFunc = getExtractFunctions([extractNTransformFunc, extractNTransformWithName], body
|
204
|
+
const nTransformFunc = getExtractFunctions([extractNTransformFunc, extractNTransformWithName], body, code =>
|
205
|
+
code.replace(/if\s*\(\s*typeof\s*[\w$]+\s*===?.*?\)\s*return\s+[\w$]+\s*;?/, ""),
|
206
|
+
);
|
208
207
|
if (!nTransformFunc && !nTransformWarning) {
|
209
208
|
// This is optional, so we can continue if it's not found, but it will bottleneck the download.
|
210
209
|
console.warn(
|
211
210
|
"\x1b[33mWARNING:\x1B[0m Could not parse n transform function.\n" +
|
212
|
-
`Please report this issue
|
211
|
+
`Please report this issue by uploading the "${utils.saveDebugFile(
|
213
212
|
"base.js",
|
214
213
|
body,
|
215
|
-
)}" file on https://github.com/
|
214
|
+
)}" file on https://github.com/distubejs/ytdl-core/issues/144.`,
|
216
215
|
);
|
217
216
|
nTransformWarning = true;
|
218
217
|
}
|
219
218
|
return nTransformFunc;
|
220
219
|
};
|
221
|
-
|
222
220
|
/**
|
223
221
|
* Extracts the actions that should be taken to decipher a signature
|
224
222
|
* and transform the n parameter
|