184 lines
5 KiB
JavaScript
184 lines
5 KiB
JavaScript
|
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;
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Match begin and end braces of input JSON, return only json
|
||
|
*
|
||
|
* @param {string} mixedJson
|
||
|
* @returns {string}
|
||
|
*/
|
||
|
exports.cutAfterJSON = mixedJson => {
|
||
|
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 in a string
|
||
|
let isString = false;
|
||
|
|
||
|
// 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;
|
||
|
for (i = 0; i < mixedJson.length; i++) {
|
||
|
// Toggle the isString boolean when leaving/entering string
|
||
|
if (mixedJson[i] === '"' && !isEscaped) {
|
||
|
isString = !isString;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Toggle the isEscaped boolean for every backslash
|
||
|
// Reset for every regular character
|
||
|
isEscaped = mixedJson[i] === '\\' && !isEscaped;
|
||
|
|
||
|
if (isString) 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.substr(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;
|
||
|
};
|