123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- const miniget = require('miniget');
- /**
- * Extract string inbetween another.
- *
- * @param {string} haystack
- * @param {string} left
- * @param {string} right
- * @returns {string}
- */
- exports.between = (haystack, left, right) => {
- let pos;
- if (left instanceof RegExp) {
- const match = haystack.match(left);
- if (!match) { return ''; }
- pos = match.index + match[0].length;
- } else {
- pos = haystack.indexOf(left);
- if (pos === -1) { return ''; }
- pos += left.length;
- }
- haystack = haystack.slice(pos);
- pos = haystack.indexOf(right);
- if (pos === -1) { return ''; }
- haystack = haystack.slice(0, pos);
- return haystack;
- };
- /**
- * Get a number from an abbreviated number string.
- *
- * @param {string} string
- * @returns {number}
- */
- exports.parseAbbreviatedNumber = string => {
- const match = string
- .replace(',', '.')
- .replace(' ', '')
- .match(/([\d,.]+)([MK]?)/);
- if (match) {
- let [, num, multi] = match;
- num = parseFloat(num);
- return Math.round(multi === 'M' ? num * 1000000 :
- multi === 'K' ? num * 1000 : num);
- }
- return null;
- };
- /**
- * Escape sequences for cutAfterJS
- * @param {string} start the character string the escape sequence
- * @param {string} end the character string to stop the escape seequence
- * @param {undefined|Regex} startPrefix a regex to check against the preceding 10 characters
- */
- const ESCAPING_SEQUENZES = [
- // Strings
- { start: '"', end: '"' },
- { start: "'", end: "'" },
- { start: '`', end: '`' },
- // RegeEx
- { start: '/', end: '/', startPrefix: /(^|[[{:;,/])\s?$/ },
- ];
- /**
- * Match begin and end braces of input JS, return only JS
- *
- * @param {string} mixedJson
- * @returns {string}
- */
- exports.cutAfterJS = mixedJson => {
- // Define the general open and closing tag
- let open, close;
- if (mixedJson[0] === '[') {
- open = '[';
- close = ']';
- } else if (mixedJson[0] === '{') {
- open = '{';
- close = '}';
- }
- if (!open) {
- throw new Error(`Can't cut unsupported JSON (need to begin with [ or { ) but got: ${mixedJson[0]}`);
- }
- // States if the loop is currently inside an escaped js object
- let isEscapedObject = null;
- // States if the current character is treated as escaped or not
- let isEscaped = false;
- // Current open brackets to be closed
- let counter = 0;
- let i;
- // Go through all characters from the start
- for (i = 0; i < mixedJson.length; i++) {
- // End of current escaped object
- if (!isEscaped && isEscapedObject !== null && mixedJson[i] === isEscapedObject.end) {
- isEscapedObject = null;
- continue;
- // Might be the start of a new escaped object
- } else if (!isEscaped && isEscapedObject === null) {
- for (const escaped of ESCAPING_SEQUENZES) {
- if (mixedJson[i] !== escaped.start) continue;
- // Test startPrefix against last 10 characters
- if (!escaped.startPrefix || mixedJson.substring(i - 10, i).match(escaped.startPrefix)) {
- isEscapedObject = escaped;
- break;
- }
- }
- // Continue if we found a new escaped object
- if (isEscapedObject !== null) {
- continue;
- }
- }
- // Toggle the isEscaped boolean for every backslash
- // Reset for every regular character
- isEscaped = mixedJson[i] === '\\' && !isEscaped;
- if (isEscapedObject !== null) continue;
- if (mixedJson[i] === open) {
- counter++;
- } else if (mixedJson[i] === close) {
- counter--;
- }
- // All brackets have been closed, thus end of JSON is reached
- if (counter === 0) {
- // Return the cut JSON
- return mixedJson.substring(0, i + 1);
- }
- }
- // We ran through the whole string and ended up with an unclosed bracket
- throw Error("Can't cut unsupported JSON (no matching closing bracket found)");
- };
- /**
- * Checks if there is a playability error.
- *
- * @param {Object} player_response
- * @param {Array.<string>} statuses
- * @param {Error} ErrorType
- * @returns {!Error}
- */
- exports.playError = (player_response, statuses, ErrorType = Error) => {
- let playability = player_response && player_response.playabilityStatus;
- if (playability && statuses.includes(playability.status)) {
- return new ErrorType(playability.reason || (playability.messages && playability.messages[0]));
- }
- return null;
- };
- /**
- * Does a miniget request and calls options.requestCallback if present
- *
- * @param {string} url the request url
- * @param {Object} options an object with optional requestOptions and requestCallback parameters
- * @param {Object} requestOptionsOverwrite overwrite of options.requestOptions
- * @returns {miniget.Stream}
- */
- exports.exposedMiniget = (url, options = {}, requestOptionsOverwrite) => {
- const req = miniget(url, requestOptionsOverwrite || options.requestOptions);
- if (typeof options.requestCallback === 'function') options.requestCallback(req);
- return req;
- };
- /**
- * Temporary helper to help deprecating a few properties.
- *
- * @param {Object} obj
- * @param {string} prop
- * @param {Object} value
- * @param {string} oldPath
- * @param {string} newPath
- */
- exports.deprecate = (obj, prop, value, oldPath, newPath) => {
- Object.defineProperty(obj, prop, {
- get: () => {
- console.warn(`\`${oldPath}\` will be removed in a near future release, ` +
- `use \`${newPath}\` instead.`);
- return value;
- },
- });
- };
- // Check for updates.
- const pkg = require('../package.json');
- const UPDATE_INTERVAL = 1000 * 60 * 60 * 12;
- exports.lastUpdateCheck = 0;
- exports.checkForUpdates = () => {
- if (!process.env.YTDL_NO_UPDATE && !pkg.version.startsWith('0.0.0-') &&
- Date.now() - exports.lastUpdateCheck >= UPDATE_INTERVAL) {
- exports.lastUpdateCheck = Date.now();
- return miniget('https://api.github.com/repos/fent/node-ytdl-core/releases/latest', {
- headers: { 'User-Agent': 'ytdl-core' },
- }).text().then(response => {
- if (JSON.parse(response).tag_name !== `v${pkg.version}`) {
- console.warn('\x1b[33mWARNING:\x1B[0m ytdl-core is out of date! Update with "npm install ytdl-core@latest".');
- }
- }, err => {
- console.warn('Error checking for updates:', err.message);
- console.warn('You can disable this check by setting the `YTDL_NO_UPDATE` env variable.');
- });
- }
- return null;
- };
- /**
- * Gets random IPv6 Address from a block
- *
- * @param {string} ip the IPv6 block in CIDR-Notation
- * @returns {string}
- */
- exports.getRandomIPv6 = ip => {
- // Start with a fast Regex-Check
- if (!isIPv6(ip)) throw Error('Invalid IPv6 format');
- // Start by splitting and normalizing addr and mask
- const [rawAddr, rawMask] = ip.split('/');
- let base10Mask = parseInt(rawMask);
- if (!base10Mask || base10Mask > 128 || base10Mask < 24) throw Error('Invalid IPv6 subnet');
- const base10addr = normalizeIP(rawAddr);
- // Get random addr to pad with
- // using Math.random since we're not requiring high level of randomness
- const randomAddr = new Array(8).fill(1).map(() => Math.floor(Math.random() * 0xffff));
- // Merge base10addr with randomAddr
- const mergedAddr = randomAddr.map((randomItem, idx) => {
- // Calculate the amount of static bits
- const staticBits = Math.min(base10Mask, 16);
- // Adjust the bitmask with the staticBits
- base10Mask -= staticBits;
- // Calculate the bitmask
- // lsb makes the calculation way more complicated
- const mask = 0xffff - ((2 ** (16 - staticBits)) - 1);
- // Combine base10addr and random
- return (base10addr[idx] & mask) + (randomItem & (mask ^ 0xffff));
- });
- // Return new addr
- return mergedAddr.map(x => x.toString('16')).join(':');
- };
- // eslint-disable-next-line max-len
- const IPV6_REGEX = /^(([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})$/;
- /**
- * Quick check for a valid IPv6
- * The Regex only accepts a subset of all IPv6 Addresses
- *
- * @param {string} ip the IPv6 block in CIDR-Notation to test
- * @returns {boolean} true if valid
- */
- const isIPv6 = exports.isIPv6 = ip => IPV6_REGEX.test(ip);
- /**
- * Normalise an IP Address
- *
- * @param {string} ip the IPv6 Addr
- * @returns {number[]} the 8 parts of the IPv6 as Integers
- */
- const normalizeIP = exports.normalizeIP = ip => {
- // Split by fill position
- const parts = ip.split('::').map(x => x.split(':'));
- // Normalize start and end
- const partStart = parts[0] || [];
- const partEnd = parts[1] || [];
- partEnd.reverse();
- // Placeholder for full ip
- const fullIP = new Array(8).fill(0);
- // Fill in start and end parts
- for (let i = 0; i < Math.min(partStart.length, 8); i++) {
- fullIP[i] = parseInt(partStart[i], 16) || 0;
- }
- for (let i = 0; i < Math.min(partEnd.length, 8); i++) {
- fullIP[7 - i] = parseInt(partEnd[i], 16) || 0;
- }
- return fullIP;
- };
|