@oreohq/ytdl-core 4.15.1

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/utils.js ADDED
@@ -0,0 +1,437 @@
1
+ const { request } = require("undici");
2
+ const { writeFileSync } = require("fs");
3
+ const AGENT = require("./agent");
4
+
5
+ /**
6
+ * Extract string inbetween another.
7
+ *
8
+ * @param {string} haystack
9
+ * @param {string} left
10
+ * @param {string} right
11
+ * @returns {string}
12
+ */
13
+ const between = (exports.between = (haystack, left, right) => {
14
+ let pos;
15
+ if (left instanceof RegExp) {
16
+ const match = haystack.match(left);
17
+ if (!match) {
18
+ return "";
19
+ }
20
+ pos = match.index + match[0].length;
21
+ } else {
22
+ pos = haystack.indexOf(left);
23
+ if (pos === -1) {
24
+ return "";
25
+ }
26
+ pos += left.length;
27
+ }
28
+ haystack = haystack.slice(pos);
29
+ pos = haystack.indexOf(right);
30
+ if (pos === -1) {
31
+ return "";
32
+ }
33
+ haystack = haystack.slice(0, pos);
34
+ return haystack;
35
+ });
36
+
37
+ exports.tryParseBetween = (body, left, right, prepend = "", append = "") => {
38
+ try {
39
+ let data = between(body, left, right);
40
+ if (!data) return null;
41
+ return JSON.parse(`${prepend}${data}${append}`);
42
+ } catch (e) {
43
+ return null;
44
+ }
45
+ };
46
+
47
+ /**
48
+ * Get a number from an abbreviated number string.
49
+ *
50
+ * @param {string} string
51
+ * @returns {number}
52
+ */
53
+ exports.parseAbbreviatedNumber = string => {
54
+ const match = string
55
+ .replace(",", ".")
56
+ .replace(" ", "")
57
+ .match(/([\d,.]+)([MK]?)/);
58
+ if (match) {
59
+ let [, num, multi] = match;
60
+ num = parseFloat(num);
61
+ return Math.round(multi === "M" ? num * 1000000 : multi === "K" ? num * 1000 : num);
62
+ }
63
+ return null;
64
+ };
65
+
66
+ /**
67
+ * Escape sequences for cutAfterJS
68
+ * @param {string} start the character string the escape sequence
69
+ * @param {string} end the character string to stop the escape seequence
70
+ * @param {undefined|Regex} startPrefix a regex to check against the preceding 10 characters
71
+ */
72
+ const ESCAPING_SEQUENZES = [
73
+ // Strings
74
+ { start: '"', end: '"' },
75
+ { start: "'", end: "'" },
76
+ { start: "`", end: "`" },
77
+ // RegeEx
78
+ { start: "/", end: "/", startPrefix: /(^|[[{:;,/])\s?$/ },
79
+ ];
80
+
81
+ /**
82
+ * Match begin and end braces of input JS, return only JS
83
+ *
84
+ * @param {string} mixedJson
85
+ * @returns {string}
86
+ */
87
+ exports.cutAfterJS = mixedJson => {
88
+ // Define the general open and closing tag
89
+ let open, close;
90
+ if (mixedJson[0] === "[") {
91
+ open = "[";
92
+ close = "]";
93
+ } else if (mixedJson[0] === "{") {
94
+ open = "{";
95
+ close = "}";
96
+ }
97
+
98
+ if (!open) {
99
+ throw new Error(`Can't cut unsupported JSON (need to begin with [ or { ) but got: ${mixedJson[0]}`);
100
+ }
101
+
102
+ // States if the loop is currently inside an escaped js object
103
+ let isEscapedObject = null;
104
+
105
+ // States if the current character is treated as escaped or not
106
+ let isEscaped = false;
107
+
108
+ // Current open brackets to be closed
109
+ let counter = 0;
110
+
111
+ let i;
112
+ // Go through all characters from the start
113
+ for (i = 0; i < mixedJson.length; i++) {
114
+ // End of current escaped object
115
+ if (!isEscaped && isEscapedObject !== null && mixedJson[i] === isEscapedObject.end) {
116
+ isEscapedObject = null;
117
+ continue;
118
+ // Might be the start of a new escaped object
119
+ } else if (!isEscaped && isEscapedObject === null) {
120
+ for (const escaped of ESCAPING_SEQUENZES) {
121
+ if (mixedJson[i] !== escaped.start) continue;
122
+ // Test startPrefix against last 10 characters
123
+ if (!escaped.startPrefix || mixedJson.substring(i - 10, i).match(escaped.startPrefix)) {
124
+ isEscapedObject = escaped;
125
+ break;
126
+ }
127
+ }
128
+ // Continue if we found a new escaped object
129
+ if (isEscapedObject !== null) {
130
+ continue;
131
+ }
132
+ }
133
+
134
+ // Toggle the isEscaped boolean for every backslash
135
+ // Reset for every regular character
136
+ isEscaped = mixedJson[i] === "\\" && !isEscaped;
137
+
138
+ if (isEscapedObject !== null) continue;
139
+
140
+ if (mixedJson[i] === open) {
141
+ counter++;
142
+ } else if (mixedJson[i] === close) {
143
+ counter--;
144
+ }
145
+
146
+ // All brackets have been closed, thus end of JSON is reached
147
+ if (counter === 0) {
148
+ // Return the cut JSON
149
+ return mixedJson.substring(0, i + 1);
150
+ }
151
+ }
152
+
153
+ // We ran through the whole string and ended up with an unclosed bracket
154
+ throw Error("Can't cut unsupported JSON (no matching closing bracket found)");
155
+ };
156
+
157
+ class UnrecoverableError extends Error {}
158
+ /**
159
+ * Checks if there is a playability error.
160
+ *
161
+ * @param {Object} player_response
162
+ * @returns {!Error}
163
+ */
164
+ exports.playError = player_response => {
165
+ const playability = player_response && player_response.playabilityStatus;
166
+ if (!playability) return null;
167
+ if (["ERROR", "LOGIN_REQUIRED"].includes(playability.status)) {
168
+ return new UnrecoverableError(playability.reason || (playability.messages && playability.messages[0]));
169
+ }
170
+ if (playability.status === "LIVE_STREAM_OFFLINE") {
171
+ return new UnrecoverableError(playability.reason || "The live stream is offline.");
172
+ }
173
+ if (playability.status === "UNPLAYABLE") {
174
+ return new UnrecoverableError(playability.reason || "This video is unavailable.");
175
+ }
176
+ return null;
177
+ };
178
+
179
+ // Undici request
180
+ exports.request = async (url, options = {}) => {
181
+ let { requestOptions, rewriteRequest } = options;
182
+
183
+ if (typeof rewriteRequest === "function") {
184
+ const request = rewriteRequest(url, requestOptions);
185
+ requestOptions = request.requestOptions;
186
+ url = request.url;
187
+ }
188
+
189
+ const req = await request(url, requestOptions);
190
+ const code = req.statusCode.toString();
191
+ if (code.startsWith("2")) {
192
+ if (req.headers["content-type"].includes("application/json")) return req.body.json();
193
+ return req.body.text();
194
+ }
195
+ if (code.startsWith("3")) return exports.request(req.headers.location, options);
196
+ const e = new Error(`Status code: ${code}`);
197
+ e.statusCode = req.statusCode;
198
+ throw e;
199
+ };
200
+
201
+ /**
202
+ * Temporary helper to help deprecating a few properties.
203
+ *
204
+ * @param {Object} obj
205
+ * @param {string} prop
206
+ * @param {Object} value
207
+ * @param {string} oldPath
208
+ * @param {string} newPath
209
+ */
210
+ exports.deprecate = (obj, prop, value, oldPath, newPath) => {
211
+ Object.defineProperty(obj, prop, {
212
+ get: () => {
213
+ console.warn(`\`${oldPath}\` will be removed in a near future release, ` + `use \`${newPath}\` instead.`);
214
+ return value;
215
+ },
216
+ });
217
+ };
218
+
219
+ // Check for updates.
220
+ const pkg = require("../package.json");
221
+ const UPDATE_INTERVAL = 1000 * 60 * 60 * 12;
222
+ let updateWarnTimes = 0;
223
+ exports.lastUpdateCheck = 0;
224
+ exports.checkForUpdates = () => {
225
+ if (
226
+ !process.env.YTDL_NO_UPDATE &&
227
+ !pkg.version.startsWith("0.0.0-") &&
228
+ Date.now() - exports.lastUpdateCheck >= UPDATE_INTERVAL
229
+ ) {
230
+ exports.lastUpdateCheck = Date.now();
231
+ return exports
232
+ .request("https://api.github.com/repos/distubejs/ytdl-core/contents/package.json", {
233
+ requestOptions: {
234
+ headers: {
235
+ "User-Agent":
236
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.3",
237
+ },
238
+ },
239
+ })
240
+ .then(
241
+ response => {
242
+ const buf = Buffer.from(response.content, response.encoding);
243
+ const pkgFile = JSON.parse(buf.toString("ascii"));
244
+ if (pkgFile.version !== pkg.version && updateWarnTimes++ < 5) {
245
+ // eslint-disable-next-line max-len
246
+ console.warn(
247
+ '\x1b[33mWARNING:\x1B[0m @distube/ytdl-core is out of date! Update with "npm install @distube/ytdl-core@latest".',
248
+ );
249
+ }
250
+ },
251
+ err => {
252
+ console.warn("Error checking for updates:", err.message);
253
+ console.warn("You can disable this check by setting the `YTDL_NO_UPDATE` env variable.");
254
+ },
255
+ );
256
+ }
257
+ return null;
258
+ };
259
+
260
+ /**
261
+ * Gets random IPv6 Address from a block
262
+ *
263
+ * @param {string} ip the IPv6 block in CIDR-Notation
264
+ * @returns {string}
265
+ */
266
+ const getRandomIPv6 = (exports.getRandomIPv6 = ip => {
267
+ // Start with a fast Regex-Check
268
+ if (!isIPv6(ip)) throw Error("Invalid IPv6 format");
269
+ // Start by splitting and normalizing addr and mask
270
+ const [rawAddr, rawMask] = ip.split("/");
271
+ let base10Mask = parseInt(rawMask);
272
+ if (!base10Mask || base10Mask > 128 || base10Mask < 24) throw Error("Invalid IPv6 subnet");
273
+ const base10addr = normalizeIP(rawAddr);
274
+ // Get random addr to pad with
275
+ // using Math.random since we're not requiring high level of randomness
276
+ const randomAddr = new Array(8).fill(1).map(() => Math.floor(Math.random() * 0xffff));
277
+
278
+ // Merge base10addr with randomAddr
279
+ const mergedAddr = randomAddr.map((randomItem, idx) => {
280
+ // Calculate the amount of static bits
281
+ const staticBits = Math.min(base10Mask, 16);
282
+ // Adjust the bitmask with the staticBits
283
+ base10Mask -= staticBits;
284
+ // Calculate the bitmask
285
+ // lsb makes the calculation way more complicated
286
+ const mask = 0xffff - (2 ** (16 - staticBits) - 1);
287
+ // Combine base10addr and random
288
+ return (base10addr[idx] & mask) + (randomItem & (mask ^ 0xffff));
289
+ });
290
+ // Return new addr
291
+ return mergedAddr.map(x => x.toString("16")).join(":");
292
+ });
293
+
294
+ // eslint-disable-next-line max-len
295
+ const IPV6_REGEX =
296
+ /^(([0-9a-f]{1,4}:)(:[0-9a-f]{1,4}){1,6}|([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}|([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}|([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}|([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}|([0-9a-f]{1,4}:){1,6}(:[0-9a-f]{1,4})|([0-9a-f]{1,4}:){1,7}(([0-9a-f]{1,4})|:))\/(1[0-1]\d|12[0-8]|\d{1,2})$/;
297
+ /**
298
+ * Quick check for a valid IPv6
299
+ * The Regex only accepts a subset of all IPv6 Addresses
300
+ *
301
+ * @param {string} ip the IPv6 block in CIDR-Notation to test
302
+ * @returns {boolean} true if valid
303
+ */
304
+ const isIPv6 = (exports.isIPv6 = ip => IPV6_REGEX.test(ip));
305
+
306
+ /**
307
+ * Normalise an IP Address
308
+ *
309
+ * @param {string} ip the IPv6 Addr
310
+ * @returns {number[]} the 8 parts of the IPv6 as Integers
311
+ */
312
+ const normalizeIP = (exports.normalizeIP = ip => {
313
+ // Split by fill position
314
+ const parts = ip.split("::").map(x => x.split(":"));
315
+ // Normalize start and end
316
+ const partStart = parts[0] || [];
317
+ const partEnd = parts[1] || [];
318
+ partEnd.reverse();
319
+ // Placeholder for full ip
320
+ const fullIP = new Array(8).fill(0);
321
+ // Fill in start and end parts
322
+ for (let i = 0; i < Math.min(partStart.length, 8); i++) {
323
+ fullIP[i] = parseInt(partStart[i], 16) || 0;
324
+ }
325
+ for (let i = 0; i < Math.min(partEnd.length, 8); i++) {
326
+ fullIP[7 - i] = parseInt(partEnd[i], 16) || 0;
327
+ }
328
+ return fullIP;
329
+ });
330
+
331
+ exports.saveDebugFile = (name, body) => {
332
+ const filename = `${+new Date()}-${name}`;
333
+ writeFileSync(filename, body);
334
+ return filename;
335
+ };
336
+
337
+ const findPropKeyInsensitive = (obj, prop) =>
338
+ Object.keys(obj).find(p => p.toLowerCase() === prop.toLowerCase()) || null;
339
+
340
+ exports.getPropInsensitive = (obj, prop) => {
341
+ const key = findPropKeyInsensitive(obj, prop);
342
+ return key && obj[key];
343
+ };
344
+
345
+ exports.setPropInsensitive = (obj, prop, value) => {
346
+ const key = findPropKeyInsensitive(obj, prop);
347
+ obj[key || prop] = value;
348
+ return key;
349
+ };
350
+
351
+ let oldCookieWarning = true;
352
+ let oldDispatcherWarning = true;
353
+ exports.applyDefaultAgent = options => {
354
+ if (!options.agent) {
355
+ const { jar } = AGENT.defaultAgent;
356
+ const c = exports.getPropInsensitive(options.requestOptions.headers, "cookie");
357
+ if (c) {
358
+ jar.removeAllCookiesSync();
359
+ AGENT.addCookiesFromString(jar, c);
360
+ if (oldCookieWarning) {
361
+ oldCookieWarning = false;
362
+ console.warn(
363
+ "\x1b[33mWARNING:\x1B[0m Using old cookie format, " +
364
+ "please use the new one instead. (https://github.com/distubejs/ytdl-core#cookies-support)",
365
+ );
366
+ }
367
+ }
368
+ if (options.requestOptions.dispatcher && oldDispatcherWarning) {
369
+ oldDispatcherWarning = false;
370
+ console.warn(
371
+ "\x1b[33mWARNING:\x1B[0m Your dispatcher is overridden by `ytdl.Agent`. " +
372
+ "To implement your own, check out the documentation. " +
373
+ "(https://github.com/distubejs/ytdl-core#how-to-implement-ytdlagent-with-your-own-dispatcher)",
374
+ );
375
+ }
376
+ options.agent = AGENT.defaultAgent;
377
+ }
378
+ };
379
+
380
+ let oldLocalAddressWarning = true;
381
+ exports.applyOldLocalAddress = options => {
382
+ if (
383
+ !options.requestOptions ||
384
+ !options.requestOptions.localAddress ||
385
+ options.requestOptions.localAddress === options.agent.localAddress
386
+ )
387
+ return;
388
+ options.agent = AGENT.createAgent(undefined, { localAddress: options.requestOptions.localAddress });
389
+ if (oldLocalAddressWarning) {
390
+ oldLocalAddressWarning = false;
391
+ console.warn(
392
+ "\x1b[33mWARNING:\x1B[0m Using old localAddress option, " +
393
+ "please add it to the agent options instead. (https://github.com/distubejs/ytdl-core#ip-rotation)",
394
+ );
395
+ }
396
+ };
397
+
398
+ let oldIpRotationsWarning = true;
399
+ exports.applyIPv6Rotations = options => {
400
+ if (options.IPv6Block) {
401
+ options.requestOptions = Object.assign({}, options.requestOptions, {
402
+ localAddress: getRandomIPv6(options.IPv6Block),
403
+ });
404
+ if (oldIpRotationsWarning) {
405
+ oldIpRotationsWarning = false;
406
+ oldLocalAddressWarning = false;
407
+ console.warn(
408
+ "\x1b[33mWARNING:\x1B[0m IPv6Block option is deprecated, " +
409
+ "please create your own ip rotation instead. (https://github.com/distubejs/ytdl-core#ip-rotation)",
410
+ );
411
+ }
412
+ }
413
+ };
414
+
415
+ exports.applyDefaultHeaders = options => {
416
+ options.requestOptions = Object.assign({}, options.requestOptions);
417
+ options.requestOptions.headers = Object.assign(
418
+ {},
419
+ {
420
+ // eslint-disable-next-line max-len
421
+ "User-Agent":
422
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.101 Safari/537.36",
423
+ },
424
+ options.requestOptions.headers,
425
+ );
426
+ };
427
+
428
+ exports.generateClientPlaybackNonce = length => {
429
+ const CPN_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
430
+ return Array.from({ length }, () => CPN_CHARS[Math.floor(Math.random() * CPN_CHARS.length)]).join("");
431
+ };
432
+
433
+ exports.applyPlayerClients = options => {
434
+ if (!options.playerClients || options.playerClients.length === 0) {
435
+ options.playerClients = ["WEB_CREATOR", "IOS"];
436
+ }
437
+ };
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@oreohq/ytdl-core",
3
+ "description": "Oreo HQ fork of ytdl-core. YouTube video downloader in pure javascript.",
4
+ "version": "4.15.1",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git://github.com/oreohq/ytdl-core.git"
8
+ },
9
+ "author": "Aniket (https://github.com/aniket091)",
10
+ "contributors": [
11
+ "fent <fentbox@gmail.com> (https://github.com/fent)",
12
+ "Tobias Kutscha (https://github.com/TimeForANinja)",
13
+ "Andrew Kelley (https://github.com/andrewrk)",
14
+ "Mauricio Allende (https://github.com/mallendeo)",
15
+ "Rodrigo Altamirano (https://github.com/raltamirano)",
16
+ "Jim Buck (https://github.com/JimmyBoh)",
17
+ "Pawel Rucinski (https://github.com/Roki100)",
18
+ "Alexander Paolini (https://github.com/Million900o)"
19
+ ],
20
+ "main": "./lib/index.js",
21
+ "types": "./typings/index.d.ts",
22
+ "files": [
23
+ "lib",
24
+ "typings"
25
+ ],
26
+ "dependencies": {
27
+ "http-cookie-agent": "^6.0.6",
28
+ "m3u8stream": "^0.8.6",
29
+ "miniget": "^4.2.3",
30
+ "sax": "^1.4.1",
31
+ "tough-cookie": "^4.1.4",
32
+ "undici": "five"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^22.8.1",
36
+ "prettier": "^3.3.3",
37
+ "typescript": "^5.6.3"
38
+ },
39
+ "engines": {
40
+ "node": ">=14.0"
41
+ },
42
+ "license": "MIT",
43
+ "scripts": {
44
+ "prettier": "prettier --write \"**/*.{js,json,yml,md,ts}\""
45
+ }
46
+ }