Jable一键下载收藏

Jable一键下载视频,并自动点击收藏

As of 2024-02-14. See the latest version.

// ==UserScript==
// @name         Jable一键下载收藏
// @namespace    https://greasyfork.org/zh-CN/scripts/474848-jable%E4%B8%80%E9%94%AE%E4%B8%8B%E8%BD%BD%E6%94%B6%E8%97%8F
// @version      1.3.2
// @description  Jable一键下载视频,并自动点击收藏
// @author       Pandex
// @match        *://jable.tv/*
// @match        *://fs1.app/*
// @connect      jable.tv
// @connect      fs1.app
// @icon         https://assets-cdn.jable.tv/assets/icon/favicon-32x32.png
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_removeValueChangeListener
// @grant        GM_addValueChangeListener
// @license      MPL
// ==/UserScript==

(function () {
    const saveFileDirectory = "D:\\videos\\jav";
    const downloadParams =
        ' --maxThreads "48" --minThreads "16" --retryCount "100" --timeOut "100" --enableDelAfterDone';
    const autoDetectLiked = true;


    var _a, _b, _c, _d;
    ("use strict");

    var linkPrefix = `https://${location.host}/videos/`;

    var r = (_a = Reflect.get(document, "__monkeyWindow")) != null ? _a : window;
    r.GM;
    r.unsafeWindow = (_b = r.unsafeWindow) != null ? _b : window;
    r.unsafeWindow;
    r.GM_info;
    r.GM_cookie;

    var l = (...e) => r.GM_addStyle(...e),
        b = (...e) => r.GM_xmlhttpRequest(...e);
    
    const jableStyle = `
    #site-content > div.container {
        max-width: 2000px !important;
    }
    .video-img-box .title {
        white-space: normal;
    }
    .video-img-box.liked .title a::before {
        content: '❤️ ';
    }

    .video-img-box.hot-1 .title a::after {
        content: ' 🔥';
    }
    .video-img-box.hot-2 .title a::after {
        content: ' 🔥🔥';
    }
    .video-img-box.hot-3 .title a::after {
        content: ' 🔥🔥🔥';
    }
    
    .video-img-box.hot-1 .title {
        color: #f9c8f1;
    }
    .video-img-box.hot-2 .title {
        color: hotpink;
    }
    .video-img-box.hot-3 .title {
        color: #ff367f;
    }
    .video-img-box.liked .hover-state {
        opacity: 1;
    }

    .btn-action.fav svg {
        color: gray !important;
    }
    .btn-action.fav.active svg {
        color: white !important;
    }
    `;

    const paths = {
        video_like_btn: "#site-content > div > div > div:nth-child(1) > section.video-info.pb-3 > div.text-center > div > button.btn.btn-action.fav.mr-2"
    };

    function isVideoURL(url) {
        return !!url.match(/https:\/\/(jable\.tv|fs1\.app)\/videos\/*\/*/);
    }
    function isModelURL(url) {
        return !!url.match(/https:\/\/(jable\.tv|fs1\.app)\/models\/*\/*/) || !!url.match(/https:\/\/(jable\.tv|fs1\.app)\/s1\/models\/*\/*/);
    }
    function isHotURL(url) {
        return !!url.match(/https:\/\/(jable\.tv|fs1\.app)\/hot\/*\/*/);
    }
    function getCodeFromUrl(url) {
        let code = url
        .replace(linkPrefix, "")
        .replace(/\/[\s\S]*$/, "");
        return code;
    }

    var memoryData = {};

    var isVideoPage = isVideoURL(location.href);
    var isModelPage = isModelURL(location.href);
    var isHotPage = isHotURL(location.href);

    var logined = false;
    var userNameEl = document.querySelector(".d-lg-block");
    if (userNameEl && userNameEl.innerText != "登入") {
        logined = true;
    }

    function detectDownload() {
        let Base64 = {
            encode(str) {
                return btoa(
                    encodeURIComponent(str).replace(
                        /%([0-9A-F]{2})/g,
                        function toSolidBytes(match, p1) {
                            return String.fromCharCode("0x" + p1);
                        }
                    )
                );
            },
            decode(str) {
                // Going backwards: from bytestream, to percent-encoding, to original string.
                return decodeURIComponent(
                    atob(str)
                    .split("")
                    .map(function (c) {
                        return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
                    })
                    .join("")
                );
            },
        };

        var title_path = "#site-content > div > div > div:nth-child(1) > section.video-info.pb-3 > div.info-header > div.header-left > h4";
        var title_el = document.querySelector(title_path);
        var title = title_el.innerText;
        var url = hlsUrl;

        var download_btn = document.createElement("a");
        download_btn.className = "addtion";
        download_btn.id = "download_m3u8";
        var params =
            '"' +
            url +
            '"' +
            '  --saveName "' +
            title +
            '" --workDir "' +
            saveFileDirectory +
            '"' +
            downloadParams;
        params = Base64.encode(params);
        var downloadLink = "m3u8dl://" + params;
        download_btn.href = "javascript:void(0);";
        if (logined) {
            download_btn.innerText = "下载并收藏";
        } else {
            download_btn.innerText = "下载(无法收藏,未登录)";
        }
        download_btn.style.display = "inline-block";
        download_btn.style.padding = "10px 20px";
        download_btn.style.background = "cornflowerblue";
        download_btn.style.color = "white";
        download_btn.style.fontSize = "18px";
        download_btn.style.margin = "0 10px";
        download_btn.style.borderRadius = "5px";
        title_el.appendChild(download_btn);
        
        const likeBtn = document.querySelector(paths.video_like_btn);
        saveVideoPageStatus();
        likeBtn.addEventListener("click", () => {
            saveVideoPageStatus(true);
        });
        function checkClickLike() {
            const download = () => {
                // console.log('开始下载', downloadLink);
                window.open(downloadLink, "_blank");
            };
            if (likeBtn) {
                if (likeBtn.classList.contains("active")) {
                    var r = confirm("你已收藏此影片,可能下载过,是否继续下载?");
                    if (r == true) {
                        download();
                    } else {
                        // console.log('取消下载');
                    }
                } else {
                    likeBtn.click();
                    download();
                }
            } else {
                download();
            }
        }
        document
            .getElementById("download_m3u8")
            .addEventListener("click", function () {
                checkClickLike();
            });
    }
    function isInViewPort (el) {
        const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight 
        const top = el.getBoundingClientRect() && el.getBoundingClientRect().top
        return top  <= viewPortHeight + 100
    }
    
    function saveVideoPageStatus(isClick = false) {
        if (!isVideoPage) {
            return;
        }
        const likeBtn = document.querySelector(paths.video_like_btn);
        if (!likeBtn) {
            return;
        }
        let code = getCodeFromUrl(location.href);
        let currentLike = likeBtn.classList.contains('active');
        if (isClick) {
            currentLike = !currentLike
        }
        let data = {
            status: "success",
            targetLink: `${linkPrefix}${code}/`,
            code: code,
            liked: currentLike,
        }
        setData(code, data)

    }
    var mouse_code = null;
    var mouse_timer = null; // 定时器
    var manual_loaded_codes = [];

    // update website CSS
    function updateBoxCardCSS() {
        var imgBoxes = document.querySelectorAll(".video-img-box");
        for (let index = 0; index < imgBoxes.length; index++) {
            const box = imgBoxes[index];
            
            let title = box.querySelector(".title");
            if (!title) {
                return;
            }
            let subTitle = box.querySelector(".sub-title");
            if (
                subTitle &&
                subTitle.innerText &&
                subTitle.innerText.split("\n").length >= 2
            ) {
                // 根据观看数和点赞数设置标签
                let playText = subTitle.innerText.split("\n")[0];
                let likeText = subTitle.innerText.split("\n")[1];
                if (playText && likeText) {
                    let playCount = parseInt(playText.replaceAll(" ", ""));
                    let likeCount = parseInt(likeText);
                    if (playCount > 1300000 || likeCount > 13000) {
                        box.classList.add("hot-3");
                    } else if (playCount > 1000000 || likeCount > 10000) {
                        box.classList.add("hot-2");
                    } else if (playCount > 500000 || likeCount > 5000) {
                        box.classList.add("hot-1");
                    }
                }
            }

            let titleLink = title.querySelector("a");
            if (titleLink && titleLink.href && isVideoURL(titleLink.href)) {
                let code = getCodeFromUrl(titleLink.href);
                if (code) {
                    if (!box.classList.contains(code)) {
                        box.classList.add(code);
                        box.classList.add("waiting");
                        let heartEl = box.querySelector(".action");
                        if (heartEl) {
                            heartEl.addEventListener("click", () => {
                                let data = {
                                    status: "success",
                                    targetLink: `${linkPrefix}${code}/`,
                                    code: code,
                                    liked: !heartEl.classList.contains("active"),
                                }
                                setData(code, data)
                                loadBoxStatus(box, data)
                            });
                        }
                        function stopMouseTimer() {
                            clearTimeout(mouse_timer);
                            mouse_timer = null;
                        }
                        box.addEventListener("mouseenter", (event) => {
                            mouse_code = code;
                            stopMouseTimer();
                            if (manual_loaded_codes.indexOf(code) < 0) {
                                mouse_timer = setTimeout(() => {
                                    stopMouseTimer()
                                    getFilmResult(code);
                                    manual_loaded_codes.push(code);
                                }, 500);;
                            }
                        }, false);
                        box.addEventListener("mouseleave", (event) => {
                            mouse_code = null;
                            if (mouse_timer) {
                                stopMouseTimer()
                            }
                        }, false);
                    }
                }
            }
        }
    }

    async function loadFilmInfo() {
        if (!autoDetectLiked) {
            return
        }
        
        if (!isModelPage && !isHotPage) {
            return
        }
        var imgBoxes = document.querySelectorAll(".video-img-box.waiting");
        // console.log('loadFilmInfo', imgBoxes.length)
        for (let index = 0; index < imgBoxes.length; index++) {
            const box = imgBoxes[index];
            let titleLink = box.querySelector(".title a");
            if (titleLink && titleLink.href && isVideoURL(titleLink.href)) {
                let code = getCodeFromUrl(titleLink.href);
                if (code) {
                    let result = getData(code);
                    if (result) {
                        if (result.status == "success") {
                            loadBoxStatus(box, result);
                        } else if (result.status == "fail") {
                            if (isInViewPort(box)) {
                                await getFilmResult(code);
                            }
                        } else {
                            // loading
                        }
                    } else {
                        if (isInViewPort(box)) {
                            await getFilmResult(code);
                        }
                    }
                }
            }
        
        }
    
    }

    function getData(code) {
        if (memoryData.hasOwnProperty(code)) {
            return memoryData[code]
        }
        let res = GM_getValue(code)
        if (res) {
            memoryData[code] = res
        }
        return res
    }

    function setData(code, data) {
        memoryData[code] = data
        if (data.status == 'success') {
            GM_setValue(code, data)
        }
    }
    function loadBoxStatus(boxEl, result) {
        if (boxEl) {
            boxEl.classList.remove("waiting");
            let heartEl = boxEl.querySelector(".action");
            if (result.status == "success" && result.liked) {
                    boxEl.classList.add("liked");
                    if (heartEl) {
                        heartEl.classList.add("active");
                    }
            } else {
                if (boxEl.classList.contains("liked")) {
                    boxEl.classList.remove("liked");
                    if (heartEl && heartEl.classList.contains("active")) {
                        heartEl.classList.remove("active");
                    }
                }
            }
        }
    }

    async function getFilmResult(code) {
        if (!logined) {
            return;
        }

        console.log("getFilmResult", code);
        let item = {
            status: "loading",
            targetLink: `${linkPrefix}${code}/`,
            code: code,
            liked: false,
        };
        setData(code, item);
        const resItem = await xhr(item);
        setData(code, resItem);
        console.log("getFilmResult-finish", resItem);

        let boxEl = document.querySelector(`.video-img-box.${code}`);
        loadBoxStatus(boxEl, resItem);
    }

    function videoPageParser(responseText) {
        let res = {
            isSuccess: false,
            liked: false,
        };
        const doc = new DOMParser().parseFromString(responseText, "text/html");
        const likeBtn = doc.querySelector(paths.video_like_btn);
        if (likeBtn) {
            res.isSuccess = true;
            if (likeBtn.classList.contains("active")) {
                res.liked = true;
            }
        }
        return res;
    }

    async function xhr(siteItem) {
        const siteUrl = siteItem.targetLink;
        const xhrPromise = new Promise((resolve) => {
            b({
                method: "GET",
                url: siteUrl,
                onload: (response) => {
                    if (response.status === 404) {
                        siteItem.status = "fail";
                        resolve(siteItem);
                    } else {
                        const {
                            isSuccess,
                            liked
                        } = videoPageParser(response.responseText);
                        siteItem.status = isSuccess ? "success" : "fail";
                        siteItem.liked = liked;
                        setTimeout(() => {
                            resolve(siteItem);
                        }, 200);
                    }
                },
                onerror: (error) => {
                    console.log("xhr-error", error);
                    siteItem.status = "fail";
                    resolve(siteItem);
                },
            });
        });
        return xhrPromise;
    }

    function observePageMutations() {
        var targetNode = document.body;
        var observerOptions = {
            childList: true, // Observe direct children being added or removed
            subtree: true, // Observe all descendants of the target node
        };
        var observer = new MutationObserver(function (mutationsList, observer) {
            updateBoxCardCSS();
        });
        observer.observe(targetNode, observerOptions);
    }

    var scroll_t1 = 0;
    var scroll_t2 = 0;
    var scroll_timer = null; // 定时器

    function isScrollEnd() {
        scroll_t2 = document.documentElement.scrollTop || document.body.scrollTop;
        if (scroll_t2 == scroll_t1){
          loadFilmInfo();
        }
      }
      
    (function main() {
        l(jableStyle);
        if (isVideoPage) {
            detectDownload();
        }
        window.addEventListener("load", () => {
            updateBoxCardCSS();
            loadFilmInfo();
            observePageMutations();
        });
        window.addEventListener("scroll", () => {
            clearTimeout(scroll_timer);
            scroll_timer = setTimeout(isScrollEnd, 1000);
            scroll_t1 = document.documentElement.scrollTop || document.body.scrollTop;
        })
    })();
})();