Browse Source

add: search

Ben 11 months ago
parent
commit
6e36f566e6
3 changed files with 253 additions and 131 deletions
  1. 232 129
      js/info.js
  2. 3 2
      js/nginx.conf
  3. 18 0
      js/test.js

+ 232 - 129
js/info.js

@@ -50,30 +50,18 @@ function parseCodecs(format) {
     return {};
 }
 
-request = async (method, url, data = null, headers = {}) => {
-    return new Promise(function (resolve, reject) {
-        const xhr = new XMLHttpRequest();
-        xhr.open(method, url);
-
-        // 设置请求头
-        Object.keys(headers).forEach(function (key) {
-            xhr.setRequestHeader(key, headers[key]);
-        });
-
-        xhr.onload = function () {
-            if (xhr.status >= 200 && xhr.status < 300) {
-                resolve(xhr.responseText);
-            } else {
-                reject(new Error('Request failed with status: ' + xhr.status));
-            }
-        };
-
-        xhr.onerror = function () {
-            reject(new Error('Request failed'));
-        };
-
-        xhr.send(data);
-    });
+request = async (method, url, data = null, headers = {}, local) => {
+    if (local) {
+        url = url.replace("https://www.youtube.com", "http://127.0.0.1");
+    }
+    if (local) {
+        console.log(url);
+        return fetch(url, {
+            "method": method,
+            "headers": headers,
+            "body": data,
+        }).then(res => res.text())
+    }
 }
 
 getStringBetween = (string, needleStart, needleEnd, offsetStart = 0, offsetEnd = 0) => {
@@ -101,123 +89,238 @@ getDecipherFunction = (string) => {
 };
 
 detail = async (url, local) => {
-    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',
-    }
-    if (local) {
-        url = url.replace("https://www.youtube.com", "http://127.0.0.1");
-    }
-    const html = await request('GET', url, null, headers);
+    try {
+        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 baseJsUrl = `https://www.youtube.com${JSON.parse(html.match(/set\(({.+?})\);/)[1])["PLAYER_JS_URL"]}`
-    if (local) {
-        baseJsUrl = baseJsUrl.replace("https://www.youtube.com", "http://127.0.0.1");
-    }
+        let baseJsUrl = `https://www.youtube.com${JSON.parse(html.match(/set\(({.+?})\);/)[1])["PLAYER_JS_URL"]}`
 
-    console.log(baseJsUrl);
+        console.log(baseJsUrl);
 
-    const baseContent = await request('GET', baseJsUrl, null, headers);
+        const baseContent = await request('GET', baseJsUrl, null, headers, local);
 
-    let regex = /var ytInitialPlayerResponse\s*=\s*({.*?});/;
-    let match = html.match(regex);
-    if (!match || !match.length) {
-        throw new Error('JSON not found.');
-    }
-    const ytInitialPlayerResponse = JSON.parse(match[1]);
-    console.log(ytInitialPlayerResponse);
-    const originVideoDetails = ytInitialPlayerResponse["videoDetails"];
-    const thumbnails = []
-    for (const item of originVideoDetails["thumbnail"]["thumbnails"]) {
-        thumbnails.push({
-            "url": item["url"],
-            "width": item["width"] + "",
-            "height": item["height"] + ""
-        })
-    }
-
-    const formats = []
-    for (let format of [].concat(ytInitialPlayerResponse["streamingData"]["formats"]).concat(ytInitialPlayerResponse["streamingData"]["adaptiveFormats"])) {
-        console.log(format);
-        if (!format["url"]) {
-            format["url"] = getUrlFromSignature(format["signatureCipher"], baseContent);
+        let regex = /var ytInitialPlayerResponse\s*=\s*({.*?});/;
+        let match = html.match(regex);
+        if (!match || !match.length) {
+            throw new Error('JSON not found.');
+        }
+        const ytInitialPlayerResponse = JSON.parse(match[1]);
+        console.log(ytInitialPlayerResponse);
+        const originVideoDetails = ytInitialPlayerResponse["videoDetails"];
+        const thumbnails = []
+        for (const item of originVideoDetails["thumbnail"]["thumbnails"]) {
+            thumbnails.push({
+                "url": item["url"],
+                "width": item["width"] + "",
+                "height": item["height"] + ""
+            })
         }
-        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"
-                })
+
+        const formats = []
+        for (let format of [].concat(ytInitialPlayerResponse["streamingData"]["formats"]).concat(ytInitialPlayerResponse["streamingData"]["adaptiveFormats"])) {
+            console.log(format);
+            if (!format["url"]) {
+                format["url"] = getUrlFromSignature(format["signatureCipher"], baseContent);
+            }
+            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) {
-        throw new Error('JSON not found.');
-    }
-    if (!match || !match.length) {
-        throw new Error('JSON not found.');
-    }
-    const ytInitialData = JSON.parse(match[1]);
-    console.log(ytInitialData);
-    const recommendInfo = [];
-    for (const item of ytInitialData["contents"]["twoColumnWatchNextResults"]["secondaryResults"]["secondaryResults"]["results"]) {
-        if (item["compactVideoRenderer"]) {
-            const recommendVideo = item["compactVideoRenderer"];
-            console.log(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"]
-                })
+        regex = /var ytInitialData\s*=\s*({.*?});/;
+        match = html.match(regex);
+        if (!match || !match.length) {
+            throw new Error('JSON not found.');
+        }
+        if (!match || !match.length) {
+            throw new Error('JSON not found.');
+        }
+        const ytInitialData = JSON.parse(match[1]);
+        console.log(ytInitialData);
+        const recommendInfo = [];
+        for (const item of ytInitialData["contents"]["twoColumnWatchNextResults"]["secondaryResults"]["secondaryResults"]["results"]) {
+            if (item["compactVideoRenderer"]) {
+                const recommendVideo = item["compactVideoRenderer"];
+                console.log(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 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"]
+        }
+        return {
+            "code": 200,
+            "msg": "",
+            "data": {
+                "videoDetails": videoDetails,
+                "streamingData": {
+                    "formats": formats
+                }
+            },
+            "id": "MusicDetailViewModel_detail_url"
+        }
+    } catch (e) {
+        console.log(e);
+        return {
+            "code": -1,
+            "msg": e.toString()
+        }
     }
-    return {
-        "code": 200,
-        "msg": "",
-        "data": {
-            "videoDetails": videoDetails,
-            "streamingData": {
-                "formats": formats
+}
+
+search = async (keyword, next, local) => {
+    try {
+        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);
+            console.log(res);
+            const videos = [];
+            for (const item of res["onResponseReceivedCommands"][0]["appendContinuationItemsAction"]["continuationItems"][0]["itemSectionRenderer"]["contents"]) {
+                const video = item["videoRenderer"];
+                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"]
+                        }
+                    });
+                }
+            }
+            return {
+                "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"
+            }
+        } 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) {
+                throw new Error('JSON not found.');
+            }
+
+            const ytInitialDataResp = JSON.parse(match[1]);
+            console.log(ytInitialDataResp);
+            const videos = [];
+            for (const item of ytInitialDataResp["contents"]["twoColumnSearchResultsRenderer"]["primaryContents"]["sectionListRenderer"]["contents"][0]["itemSectionRenderer"]["contents"]) {
+                if (item["videoRenderer"]) {
+                    const video = item["videoRenderer"];
+                    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) {
+                // 写入path
+                next["key"] = html
+                    .split("innertubeApiKey")[1]
+                    .trim()
+                    .split(",")[0]
+                    .split('"')[2];
             }
-        },
-        "id": "MusicDetailViewModel_detail_url"
+            next["continuation"] = ytInitialDataResp["contents"]["twoColumnSearchResultsRenderer"]["primaryContents"]["sectionListRenderer"]["contents"][1]["continuationItemRenderer"]["continuationEndpoint"]["continuationCommand"]["token"]
+
+            return {
+                "code": 200,
+                "msg": "",
+                "data": {
+                    "data": videos,
+                    "next": JSON.stringify(next),
+                },
+                "id": "MusicSearchResultViewModel_search_result"
+            }
+        }
+    } catch (e) {
+        console.log(e);
+        return {
+            "code": -1,
+            "msg": e.toString()
+        }
     }
 }

+ 3 - 2
js/nginx.conf

@@ -22,8 +22,9 @@ http {
 
     server {
         listen       80;
-        add_header 'Access-Control-Allow-Origin' '*';
-        add_header 'Access-Control-Allow-Methods' '*';
+        proxy_hide_header 'Access-Control-Allow-Origin';
+        add_header 'Access-Control-Allow-Origin' '*' always;
+        add_header 'Access-Control-Allow-Methods' '*' always;
         add_header 'Access-Control-Allow-Headers' '*';
         location / {
             proxy_pass https://www.youtube.com/;

+ 18 - 0
js/test.js

@@ -6,3 +6,21 @@ detail(url, true)
     .catch(e => {
         console.log(e);
     })
+
+search("周 杰伦", null, true)
+    .then(res => {
+        console.log(res);
+
+        const next = res["data"]["next"];
+        console.log(next);
+        search("周 杰伦", next, true)
+            .then(nextRes => {
+                console.log(nextRes);
+            })
+            .catch(e => {
+                console.log(e);
+            })
+    })
+    .catch(e => {
+        console.log(e);
+    })