Ben před 11 měsíci
rodič
revize
d1e2c27db6
3 změnil soubory, kde provedl 374 přidání a 1 odebrání
  1. 372 0
      js/20240510202714
  2. 0 0
      js/20240510202815.js
  3. 2 1
      js/webpack.config.js

+ 372 - 0
js/20240510202714

@@ -0,0 +1,372 @@
+/******/ (() => { // webpackBootstrap
+var __webpack_exports__ = {};
+parseCodecs = (format) => {
+    const mimeType = format["mimeType"]
+    if (!mimeType) {
+        return {};
+    }
+    const regex = /(?<mimetype>[^/]+\/[^;]+)(?:;\s*codecs="?(?<codecs>[^"]+))?/;
+    const match = mimeType.match(regex);
+    if (!match) {
+        return {};
+    }
+    const codecs = match.groups.codecs;
+    if (!codecs) {
+        return {};
+    }
+
+    const splitCodecs = codecs.trim().replace(/,$/, '').split(',').map(str => str.trim()).filter(Boolean);
+    let vcodec = null;
+    let acodec = null;
+
+    for (const fullCodec of splitCodecs) {
+        const codec = fullCodec.split('.')[0];
+
+        if (['avc1', 'avc2', 'avc3', 'avc4', 'vp9', 'vp8', 'hev1', 'hev2', 'h263', 'h264', 'mp4v', 'hvc1', 'av01', 'theora'].includes(codec)) {
+            if (!vcodec) {
+                vcodec = fullCodec;
+            }
+        } else if (['mp4a', 'opus', 'vorbis', 'mp3', 'aac', 'ac-3', 'ec-3', 'eac3', 'dtsc', 'dtse', 'dtsh', 'dtsl'].includes(codec)) {
+            if (!acodec) {
+                acodec = fullCodec;
+            }
+        } else {
+            console.warn(`WARNING: Unknown codec ${fullCodec}`);
+        }
+    }
+
+    if (!vcodec && !acodec) {
+        if (splitCodecs.length === 2) {
+            return {
+                vcodec: splitCodecs[0],
+                acodec: splitCodecs[1]
+            };
+        }
+    } else {
+        return {
+            vcodec: vcodec,
+            acodec: acodec
+        };
+    }
+
+    return {};
+}
+
+request = async (method, url, data = null, headers = {}, local) => {
+    if (local) {
+        url = url.replace("https://www.youtube.com", "http://127.0.0.1");
+    }
+    console.log(`请求url:${url}`)
+    console.log(`请求data:${data}`)
+    console.log(`请求method:${method}`)
+    console.log(`请求headers:${JSON.stringify((headers))}`)
+    if (local) {
+        return fetch(url, {
+            "method": method,
+            "headers": headers,
+            "body": data,
+        }).then(res => res.text())
+    }
+    return new Promise((resolve, reject) => {
+        AF.request(url, method, data, headers, (data, err) => {
+            if (err) {
+                console.log(`请求失败: ${err}`)
+                reject(err);
+            } else {
+                resolve(data);
+            }
+        });
+    })
+}
+
+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);
+}
+
+getDecipherFunction = (string) => {
+    const js = string.replace("var _yt_player={}", "");
+    const top = getStringBetween(js, `a=a.split("")`, "};", 1, -28);
+    const beginningOfFunction =
+        "var " + getStringBetween(top, `a=a.split("")`, "(", 10, 1).split(".")[0] + "=";
+    const side = getStringBetween(js, beginningOfFunction, "};", 2, -beginningOfFunction.length);
+    console.log(`side: ${side}`);
+    console.log(`top: ${top}`);
+    return eval(side + top);
+};
+
+const cache = {};
+extractJSSignatureFunction = async (baseJsUrl, local) => {
+    console.log(`解析baseUrl: ${baseJsUrl}`);
+    const cacheKey = `js:${baseJsUrl}`;
+    if (cache[cacheKey]) {
+        console.log(`从缓存中获取JSSignatureFunction: ${baseJsUrl}`);
+        return cache[cacheKey];
+    }
+    const headers = {
+        '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',
+    }
+    const baseContent = await request('GET', baseJsUrl, null, headers, local);
+    const decipher = getDecipherFunction(baseContent);
+    if (decipher) {
+        cache[cacheKey] = decipher;
+    }
+    return decipher;
+}
+
+getUrlFromSignature = async (signatureCipher, baseJsUrl, local) => {
+    const decipher = await extractJSSignatureFunction(baseJsUrl, local);
+    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"]];
+    console.log(signatureCipher, url, signature, sp);
+    return `${url}&${sp}=${decipher(signature)}`;
+}
+
+detail = async (url, local) => {
+    try {
+        console.log(`接受到解析请求: ${url}`);
+        const headers = {
+            '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',
+        }
+        const html = await request('GET', url, null, headers, local);
+
+        let regex = /var ytInitialPlayerResponse\s*=\s*({.*?});/;
+        let match = html.match(regex);
+        if (!match || !match.length) {
+            console.log("解释失败: 无法找到json");
+            throw new Error('JSON not found.');
+        }
+        const ytInitialPlayerResponse = JSON.parse(match[1]);
+        const originVideoDetails = ytInitialPlayerResponse["videoDetails"];
+        console.log(`videoDetails: ${JSON.stringify(originVideoDetails)}`);
+        const thumbnails = []
+        for (const item of originVideoDetails["thumbnail"]["thumbnails"]) {
+            thumbnails.push({
+                "url": item["url"],
+                "width": item["width"] + "",
+                "height": item["height"] + ""
+            })
+        }
+
+        const formats = []
+        const baseJsUrl = `https://www.youtube.com${JSON.parse(html.match(/set\(({.+?})\);/)[1])["PLAYER_JS_URL"]}`
+        for (let format of [].concat(ytInitialPlayerResponse["streamingData"]["formats"]).concat(ytInitialPlayerResponse["streamingData"]["adaptiveFormats"])) {
+            console.log(`current format: ${JSON.stringify(format)}`);
+            if (!format["url"]) {
+                format["url"] = await getUrlFromSignature(format["signatureCipher"], baseJsUrl, local);
+            }
+            if (format["url"]) {
+                const {vcodec, acodec} = parseCodecs(format)
+                if (vcodec && acodec) {
+                    formats.push({
+                        "width": format["width"] + "",
+                        "height": format["height"] + "",
+                        "type": format["mimeType"],
+                        "quality": format["quality"],
+                        "itag": format["itag"],
+                        "fps": format["fps"] + "",
+                        "bitrate": format["bitrate"] + "",
+                        "url": format["url"],
+                        "ext": "mp4",
+                        "vcodec": vcodec,
+                        "acodec": acodec,
+                        "vbr": "0",
+                        "abr": "0",
+                        "container": "mp4_dash"
+                    })
+                }
+            }
+        }
+
+        regex = /var ytInitialData\s*=\s*({.*?});/;
+        match = html.match(regex);
+        if (!match || !match.length) {
+            console.log(`解析失败,无法找到 ytInitialData`);
+            throw new Error('JSON not found.');
+        }
+        const ytInitialData = JSON.parse(match[1]);
+        const recommendInfo = [];
+        for (const item of ytInitialData["contents"]["twoColumnWatchNextResults"]["secondaryResults"]["secondaryResults"]["results"]) {
+            if (item["compactVideoRenderer"]) {
+                const recommendVideo = item["compactVideoRenderer"];
+                console.log(`推荐视频: ${JSON.stringify(recommendVideo)}`);
+                if (recommendVideo["videoId"]) {
+                    recommendInfo.push({
+                        "type": "gridVideoRenderer",
+                        "videoId": recommendVideo["videoId"],
+                        "title": recommendVideo["title"]?.["simpleText"],
+                        "thumbnails": recommendVideo["thumbnail"]?.["thumbnails"],
+                        "channelName": recommendVideo["longBylineText"]?.["runs"]?.[0]?.["text"],
+                        "publishedTimeText": recommendVideo["publishedTimeText"]?.["simpleText"],
+                        "viewCountText": recommendVideo["viewCountText"]?.["simpleText"],
+                        "shortViewCountText": recommendVideo["shortViewCountText"]?.["simpleText"],
+                        "lengthText": recommendVideo["lengthText"]?.["simpleText"]
+                    })
+                }
+            }
+        }
+
+        const videoDetails = {
+            "isLiveContent": originVideoDetails["isLiveContent"],
+            "title": originVideoDetails["title"],
+            "thumbnails": thumbnails,
+            "description": originVideoDetails["shortDescription"],
+            "lengthSeconds": originVideoDetails["lengthSeconds"],
+            "viewCount": originVideoDetails["viewCount"],
+            "keywords": originVideoDetails["keywords"],
+            "author": originVideoDetails["author"],
+            "channelID": originVideoDetails["channelId"],
+            "recommendInfo": recommendInfo,
+            "channelURL": `https://www.youtube.com/channel/${originVideoDetails["channelId"]}`,
+            "videoId": originVideoDetails["videoId"]
+        }
+        const ret = {
+            "code": 200,
+            "msg": "",
+            "data": {
+                "videoDetails": videoDetails,
+                "streamingData": {
+                    "formats": formats
+                }
+            },
+            "id": "MusicDetailViewModel_detail_url"
+        }
+        console.log(`解析结果: ${JSON.stringify(ret)}`);
+        return ret;
+    } catch (e) {
+        const ret = {
+            "code": -1,
+            "msg": e.toString()
+        }
+        console.log(`解析失败: ${JSON.stringify(ret)}`);
+        return ret;
+    }
+}
+
+search = async (keyword, next, local) => {
+    try {
+        console.log(`接受到搜索请求 keyword: ${keyword}`);
+        console.log(`接收到搜索请求 next: ${next}`);
+        if (next) {
+            const nextObject = JSON.parse(next);
+            const key = nextObject["key"];
+            const body = {
+                context: {
+                    client: {
+                        clientName: "WEB",
+                        clientVersion: "2.20240506.01.00",
+                    },
+                },
+                continuation: nextObject["continuation"]
+            };
+            let res = await request('POST', `https://www.youtube.com/youtubei/v1/search?key=${key}`, JSON.stringify(body), {}, local);
+            res = JSON.parse(res);
+            const videos = [];
+            for (const item of res["onResponseReceivedCommands"][0]["appendContinuationItemsAction"]["continuationItems"][0]["itemSectionRenderer"]["contents"]) {
+                const video = item["videoRenderer"];
+                console.log(`搜索结果: ${JSON.stringify(video)}`);
+                if (video && video["videoId"]) {
+                    videos.push({
+                        "type": "videoWithContextRenderer",
+                        "data": {
+                            "videoId": video["videoId"],
+                            "title": video["title"]?.["runs"]?.[0]?.["text"],
+                            "thumbnails": video["thumbnail"]?.["thumbnails"],
+                            "channelName": video["longBylineText"]?.["runs"]?.[0]?.["text"],
+                            "publishedTimeText": video["publishedTimeText"]?.["simpleText"],
+                            "viewCountText": video["viewCountText"]?.["simpleText"],
+                            "shortViewCountText": video["shortViewCountText"]?.["simpleText"],
+                            "lengthText": video["lengthText"]?.["simpleText"]
+                        }
+                    });
+                }
+            }
+            const ret = {
+                "code": 200,
+                "msg": "",
+                "data": {
+                    "data": videos,
+                    "next": JSON.stringify({
+                        "key": nextObject["key"],
+                        "continuation": res["onResponseReceivedCommands"][0]["appendContinuationItemsAction"]["continuationItems"][1]["continuationItemRenderer"]["continuationEndpoint"]["continuationCommand"]["token"],
+                    }),
+                },
+                "id": "MusicSearchResultViewModel_search_result"
+            }
+            console.log(`携带next搜索结果成功: ${JSON.stringify(ret)}`);
+            return ret;
+        } else {
+            let url = `https://www.youtube.com/results?q=${encodeURIComponent(keyword)}&sp=EgIQAQ%253D%253D`;
+
+            const html = await request('GET', url, null, {}, local);
+
+            let regex = /var ytInitialData\s*=\s*({.*?});/;
+            let match = html.match(regex);
+            if (!match || !match.length) {
+                console.log("搜索失败:无法找到ytInitialData");
+                throw new Error('JSON not found.');
+            }
+
+            const ytInitialDataResp = JSON.parse(match[1]);
+            const videos = [];
+            for (const item of ytInitialDataResp["contents"]["twoColumnSearchResultsRenderer"]["primaryContents"]["sectionListRenderer"]["contents"][0]["itemSectionRenderer"]["contents"]) {
+                if (item["videoRenderer"]) {
+                    const video = item["videoRenderer"];
+                    console.log(`搜索结果: ${JSON.stringify(video)}`);
+                    if (video && video["videoId"]) {
+                        videos.push({
+                            "type": "videoWithContextRenderer",
+                            "data": {
+                                "videoId": video["videoId"],
+                                "title": video["title"]?.["runs"]?.[0]?.["text"],
+                                "thumbnails": video["thumbnail"]?.["thumbnails"],
+                                "channelName": video["longBylineText"]?.["runs"]?.[0]?.["text"],
+                                "publishedTimeText": video["publishedTimeText"]?.["simpleText"],
+                                "viewCountText": video["viewCountText"]?.["simpleText"],
+                                "shortViewCountText": video["shortViewCountText"]?.["simpleText"],
+                                "lengthText": video["lengthText"]?.["simpleText"]
+                            }
+                        });
+                    }
+                }
+            }
+
+            let next = {};
+            if (html.split("innertubeApiKey").length > 0) {
+                next["key"] = html
+                    .split("innertubeApiKey")[1]
+                    .trim()
+                    .split(",")[0]
+                    .split('"')[2];
+            }
+            next["continuation"] = ytInitialDataResp["contents"]["twoColumnSearchResultsRenderer"]["primaryContents"]["sectionListRenderer"]["contents"][1]["continuationItemRenderer"]["continuationEndpoint"]["continuationCommand"]["token"]
+
+            const ret = {
+                "code": 200,
+                "msg": "",
+                "data": {
+                    "data": videos,
+                    "next": JSON.stringify(next),
+                },
+                "id": "MusicSearchResultViewModel_search_result"
+            }
+            console.log(`未携带next搜索结果成功: ${JSON.stringify(ret)}`);
+            return ret;
+        }
+    } catch (e) {
+        const ret = {
+            "code": -1,
+            "msg": e.toString()
+        }
+        console.log(`搜索失败: ${JSON.stringify(ret)}`);
+        return ret;
+    }
+}
+
+/******/ })()
+;

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
js/20240510202815.js


+ 2 - 1
js/webpack.config.js

@@ -20,7 +20,7 @@ module.exports = {
     entry: './info.js',
     output: {
         path: path.resolve(__dirname),
-        filename: getCurrentDateTime(),
+        filename: `${getCurrentDateTime()}.js`,
     },
     optimization: {
         minimizer: [
@@ -28,6 +28,7 @@ module.exports = {
                 uglifyOptions: {
                     compress: {},
                     keep_fnames: true,
+                    mangle: true,
                 },
             }),
         ],

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů