|
@@ -109,6 +109,7 @@ request = async (method, url, data = null, headers = {}, platform) => {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
AF.request(url, method, data, headers, (data, headers, err) => {
|
|
|
if (err) {
|
|
|
+ console.log(`error: ${err}`)
|
|
|
reject(err);
|
|
|
} else {
|
|
|
console.log(`response headers: ${headers}`);
|
|
@@ -121,127 +122,6 @@ request = async (method, url, data = null, headers = {}, platform) => {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-getStringBetween = (string, needleStart, needleEnd, offsetStart = 0, offsetEnd = 0) => {
|
|
|
- const x = string.indexOf(needleStart);
|
|
|
- const y = needleEnd ? string.indexOf(needleEnd, x) : string.length;
|
|
|
- return string.substring(x + needleStart.length + offsetEnd, y + offsetStart);
|
|
|
-}
|
|
|
-
|
|
|
-findFunction = (jsCode, regexp, platform) => {
|
|
|
- const match = jsCode.match(regexp)
|
|
|
- if (!match && match.length <= 1) {
|
|
|
- return null;
|
|
|
- }
|
|
|
- let result = "";
|
|
|
- const dependencyMatches = match[0].match(/([$a-zA-Z0-9]+\.[$a-zA-Z0-9]+)/g)
|
|
|
- const existDependencies = [];
|
|
|
- if (dependencyMatches && dependencyMatches.length >= 1) {
|
|
|
- for (let currentMatch of dependencyMatches) {
|
|
|
- const varName = currentMatch.split('.')[0];
|
|
|
- if (existDependencies.includes(varName)) {
|
|
|
- continue
|
|
|
- }
|
|
|
- if (!/^[$A-Z|a-z]{2,}$/.test(varName)) {
|
|
|
- continue
|
|
|
- }
|
|
|
- let reg = "var (\$)?" + varName + "={(.|\\n)*?};"
|
|
|
- const varNameMatch = jsCode.match(new RegExp(reg), 'ig');
|
|
|
- if (varNameMatch && varNameMatch.length >= 1) {
|
|
|
- result += varNameMatch[0] + "\n";
|
|
|
- }
|
|
|
- existDependencies.push(varName);
|
|
|
- }
|
|
|
- }
|
|
|
- result += `\n${match[0]}`;
|
|
|
- if (printable(platform)) {
|
|
|
- console.log(`findFunction result: ` + result);
|
|
|
- }
|
|
|
- return eval(result);
|
|
|
-};
|
|
|
-
|
|
|
-const cache = {};
|
|
|
-
|
|
|
-fetchBaseJSContent = async (baseJsUrl, platform) => {
|
|
|
- const cacheKey = `jsContent:${baseJsUrl}`;
|
|
|
- if (cache[cacheKey]) {
|
|
|
- console.log(`baseContent from cache: ${baseJsUrl}`);
|
|
|
- return cache[cacheKey];
|
|
|
- }
|
|
|
- console.log(`extract baseUrl: ${baseJsUrl}`);
|
|
|
- const baseContentResp = await request('GET', baseJsUrl, null, {
|
|
|
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.101 Safari/537.36',
|
|
|
- }, platform);
|
|
|
- const {data, _} = baseContentResp;
|
|
|
- cache[cacheKey] = data;
|
|
|
- return data;
|
|
|
-}
|
|
|
-
|
|
|
-extractJSSignatureFunction = async (baseJsUrl, platform) => {
|
|
|
- const cacheKey = `jsSign:${baseJsUrl}`
|
|
|
- if (cache[cacheKey]) {
|
|
|
- console.log(`jsSign from cache: ${baseJsUrl}`);
|
|
|
- return cache[cacheKey];
|
|
|
- }
|
|
|
- const baseJsContent = await fetchBaseJSContent(baseJsUrl, platform);
|
|
|
- const result = findFunction(baseJsContent, /([a-zA-Z0-9]+)=function\([a-zA-Z0-9]+\)\{a=a\.split\(""\).*};/, platform);
|
|
|
- cache[cacheKey] = result
|
|
|
- return result
|
|
|
-}
|
|
|
-
|
|
|
-extractNJSFunction = async (baseJsUrl, platform) => {
|
|
|
- const cacheKey = `jsN:${baseJsUrl}`
|
|
|
- if (cache[cacheKey]) {
|
|
|
- console.log(`jsN from cache: ${baseJsUrl}`);
|
|
|
- return cache[cacheKey];
|
|
|
- }
|
|
|
- const baseJsContent = await fetchBaseJSContent(baseJsUrl, platform);
|
|
|
- const result = findFunction(baseJsContent, /([a-zA-Z0-9]+)=function\([a-zA-Z0-9]+\)\{try{var b=a\.split\(","\)[\s\S]*?};/, platform);
|
|
|
- cache[cacheKey] = result
|
|
|
- return result
|
|
|
-}
|
|
|
-
|
|
|
-signUrl = async (signatureCipher, baseJsUrl, platform) => {
|
|
|
- const searchParams = {}
|
|
|
- for (const item of signatureCipher.split('&')) {
|
|
|
- const [key, value] = item.split('=');
|
|
|
- searchParams[decodeURIComponent(key)] = decodeURIComponent(value);
|
|
|
- }
|
|
|
- const [url, signature, sp] = [searchParams['url'], searchParams['s'], searchParams['sp']];
|
|
|
- const decipher = await extractJSSignatureFunction(baseJsUrl, platform);
|
|
|
- if (!decipher) {
|
|
|
- return null;
|
|
|
- }
|
|
|
- if (printable(platform)) {
|
|
|
- console.log(`signatureCipher=${signatureCipher}, url=${url}, signature=${signature}, sp=${sp}`)
|
|
|
- }
|
|
|
- let newUrl = `${url}&${sp}=${decipher(signature)}`;
|
|
|
-
|
|
|
- function replaceUrlParam(url, paramName, paramValue) {
|
|
|
- let pattern = new RegExp(`([?&])${paramName}=.*?(&|$)`, 'i');
|
|
|
- let newUrl = url.replace(pattern, `$1${paramName}=${paramValue}$2`);
|
|
|
-
|
|
|
- if (newUrl === url && url.indexOf('?') === -1) {
|
|
|
- newUrl += `?${paramName}=${paramValue}`;
|
|
|
- } else if (newUrl === url) {
|
|
|
- newUrl += `&${paramName}=${paramValue}`;
|
|
|
- }
|
|
|
- return newUrl;
|
|
|
- }
|
|
|
-
|
|
|
- for (const item of url.split('&')) {
|
|
|
- const [key, value] = item.split('=');
|
|
|
- searchParams[decodeURIComponent(key)] = decodeURIComponent(value);
|
|
|
- }
|
|
|
-
|
|
|
- const nFunction = await extractNJSFunction(baseJsUrl, platform);
|
|
|
- const n = searchParams['n']
|
|
|
- if (n && nFunction) {
|
|
|
- const newN = nFunction(n);
|
|
|
- return replaceUrlParam(newUrl, 'n', newN);
|
|
|
- }
|
|
|
- return newUrl;
|
|
|
-}
|
|
|
-
|
|
|
detail = async (url, platform) => {
|
|
|
try {
|
|
|
const htmlResp = await request('GET', `${url}&bpctr=9999999999&has_verified=1`, null, {
|
|
@@ -311,6 +191,12 @@ detail = async (url, platform) => {
|
|
|
originFormats = originFormats.concat(currentFormats);
|
|
|
} catch (e) {
|
|
|
console.log(`can not found format android api error: ${e}`);
|
|
|
+ console.log(`detail2 result error: ${JSON.stringify(ret)}`);
|
|
|
+ const ret = {
|
|
|
+ "code": -1,
|
|
|
+ "msg": e.toString()
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
}
|
|
|
console.log(`after android api, format size:${originFormats.length}`);
|
|
|
|
|
@@ -325,31 +211,13 @@ detail = async (url, platform) => {
|
|
|
originFormats = originFormats.concat(currentFormats);
|
|
|
console.log(`after html, format size:${originFormats.length}`);
|
|
|
|
|
|
- const baseJsUrl = `https://www.youtube.com${JSON.parse(html.match(/set\(({.+?})\);/)[1])["PLAYER_JS_URL"]}`
|
|
|
- let audioUrl = ""
|
|
|
- for (let format of originFormats) {
|
|
|
- if (!format["url"]) {
|
|
|
- format["url"] = await signUrl(format["signatureCipher"], baseJsUrl, platform);
|
|
|
- }
|
|
|
- if (format["url"]) {
|
|
|
- const {vcodec, acodec} = parseCodecs(format)
|
|
|
- if (!vcodec && acodec) {
|
|
|
- audioUrl = format["url"]
|
|
|
- break
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- let formatIds = [];
|
|
|
+ let qualities = [];
|
|
|
const formats = [];
|
|
|
for (let format of originFormats) {
|
|
|
if (printable(platform)) {
|
|
|
console.log(format);
|
|
|
}
|
|
|
- if (format && formatIds.indexOf(format['itag']) === -1) {
|
|
|
- if (!format["url"]) {
|
|
|
- format["url"] = await signUrl(format["signatureCipher"], baseJsUrl, platform);
|
|
|
- }
|
|
|
+ if (format && qualities.indexOf(format['itag']) === -1) {
|
|
|
if (format["url"]) {
|
|
|
const {vcodec, acodec} = parseCodecs(format)
|
|
|
if (vcodec && acodec) {
|
|
@@ -369,13 +237,10 @@ detail = async (url, platform) => {
|
|
|
"abr": "0",
|
|
|
"container": "mp4_dash",
|
|
|
"from": format["from"],
|
|
|
- "audioUrl": audioUrl
|
|
|
- }
|
|
|
- if (platform === "WEB") {
|
|
|
- current["source"] = format
|
|
|
+ "source": format
|
|
|
}
|
|
|
formats.push(current)
|
|
|
- formatIds.push(format["itag"]);
|
|
|
+ qualities.push(format["qualityLabel"]);
|
|
|
}
|
|
|
}
|
|
|
}
|