@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/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
- info.player_response.microformat &&
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 ? html5playerRes[1] || html5playerRes[2] : null;
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 ((err && err.statusCode < 500) || currentTry >= options.maxRetries) throw err;
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
- let formats = [];
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 && player_response.streamingData,
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
- const html5player = new URL(info.html5player, BASE_URL).toString();
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("WEB_CREATOR")) promises.push(fetchWebCreatorPlayer(id, html5player, options));
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
- funcs.push(sig.decipherFormats(info.formats, html5player, options));
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
- for (let resp of responses) {
269
- if (resp.value) {
270
- funcs.push(...parseAdditionalManifests(resp.value, options));
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
- funcs.push(sig.decipherFormats(parseFormats(info.player_response), html5player, options));
276
- funcs.push(...parseAdditionalManifests(info.player_response));
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
- funcs.push(sig.decipherFormats(parseFormats(info.player_response), html5player, options));
280
- funcs.push(...parseAdditionalManifests(info.player_response));
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
- info.formats = info.formats.map(formatUtils.addFormatMeta);
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
- let mo = body.match(/signatureTimestamp:(\d+)/);
295
-
334
+ const mo = body.match(/(signatureTimestamp|sts):(\d+)/);
296
335
  return {
297
336
  contentPlaybackContext: {
298
337
  html5Preference: "HTML5_PREF_WANTS",
299
- signatureTimestamp: mo ? mo[1] : undefined,
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 WEB_CREATOR_CONTEXT = {
346
+ const WEB_EMBEDDED_CONTEXT = {
308
347
  client: {
309
- clientName: "WEB_CREATOR",
310
- clientVersion: "1.20241023.00.01",
348
+ clientName: "WEB_EMBEDDED_PLAYER",
349
+ clientVersion: "1.20240723.01.00",
311
350
  ...LOCALE,
312
351
  },
313
352
  };
314
353
 
315
- const fetchWebCreatorPlayer = async (videoId, html5player, options) => {
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: WEB_CREATOR_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
- return await playerAPI(videoId, payload, undefined, options);
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, userAgent, options) => {
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
- cookie: jar.getCookieStringSync("https://www.youtube.com"),
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
- // NewPipeExtractor regexps
25
- const DECIPHER_NAME_REGEXPS = [
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
- // LavaPlayer regexps
33
- const VARIABLE_PART = "[a-zA-Z_\\$][a-zA-Z_0-9]*";
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\\(a\\)\\{(?:return )?a\\.reverse\\(\\)\\}";
39
- const SLICE_PART = ":function\\(a,b\\)\\{return a\\.slice\\(b\\)\\}";
40
- const SPLICE_PART = ":function\\(a,b\\)\\{a\\.splice\\(0,b\\)\\}";
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\\(a,b\\)\\{" + "var c=a\\[0\\];a\\[0\\]=a\\[b%a\\.length\\];a\\[b(?:%a.length|)\\]=c(?:;return a)?\\}";
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
- `a=a\\.split\\(""\\);\\s*` +
47
- `((?:(?:a=)?${VARIABLE_PART}${VARIABLE_PART_ACCESS}\\(a,\\d+\\);)+)` +
48
- `return a\\.join\\(""\\)` +
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
- // NewPipeExtractor regexps
60
- `${SCVR}="nn"\\[\\+${MCR}\\.${MCR}],${MCR}\\(${MCR}\\),${MCR}=${MCR}\\.${MCR}\\[${MCR}]\\|\\|null\\).+\\|\\|(${
61
- MCR
62
- })\\(""\\)`,
63
- `${SCVR}="nn"\\[\\+${MCR}\\.${MCR}],${MCR}\\(${MCR}\\),${MCR}=${MCR}\\.${MCR}\\[${MCR}]\\|\\|null\\)&&\\(${MCR}=(${
64
- MCR
65
- })${AAR}`,
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"enhanced_except_([A-z0-9-]+)"\\s*\\+\\s*\\1\\s*}' +
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 matchFirst = (regex, str) => matchRegex(regex, str)[0];
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 = matchGroup1(regex, body);
101
+ fn = matchGroup(regex, body, idx);
104
102
  try {
105
- fn = matchGroup1(`${fn.replace(/\$/g, "\\$")}=\\[([a-zA-Z0-9$\\[\\]]{2,})\\]`, body);
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 = matchFirst(HELPER_REGEXP, body);
122
- const decipherFunc = matchFirst(DECIPHER_REGEXP, body);
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 ${matchGroup1(funcPattern, body)};`;
136
- const helperObjectName = matchGroup1(";([A-Za-z0-9_\\$]{2,})\\.\\w+\\(", decipherFunc);
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 = matchGroup1(helperPattern, body);
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
- `Please report this issue with the "${utils.saveDebugFile(
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/oreohq/ytdl-core/issues.\nStream URL will be missing.`,
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 = matchFirst(N_TRANSFORM_REGEXP, body);
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
- nFuncName.replace(/\$/g, "\\$")
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 with the "${utils.saveDebugFile(
211
+ `Please report this issue by uploading the "${utils.saveDebugFile(
213
212
  "base.js",
214
213
  body,
215
- )}" file on https://github.com/oreohq/ytdl-core/issues.`,
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