JavDB 跳轉 MissAv

Combine waterfall layout and jump button for JavDB

ของเมื่อวันที่ 04-03-2025 ดู เวอร์ชันล่าสุด

// ==UserScript==
// @name         JavDB 跳轉 MissAv
// @namespace    https://javdb.com/
// @license      MIT
// @version      2.0
// @description  Combine waterfall layout and jump button for JavDB
// @author       YourName
// @match        https://javdb.com/*
// @match        https://missav.ws/*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_notification
// @grant        GM_setClipboard
// @grant        GM_getResourceURL
// @grant        GM_registerMenuCommand
// @grant        GM_info
// @grant        GM_openInTab
// @grant        GM_xmlhttpRequest
// @connect      *
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // JavDB Waterfall Layout Code
    const EMPTY_IMAGE_DATA = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAAB4CAMAAABsOSjPAAAAn1BMVEUAAAD/lgD/x3f/zoj/nRH/3Kr/+e7/sD//68z/1Zj/qzP/skT/uVX/9eX/pCL/oh3/wGb/8d3/4LL/5Lv/2aL/tUv/kAD/0o//9uj/v2L/mQn//vv//Pf/rjf/+vL/1pn/2pT/mAX/kwD/+/X/9OH/4rb/qzD/qSX/mAH/6sn/6cL/26f/y4D/nxr/vV7/uU//wnH/7tb/1YX/wEL/nypWdfcGAAAAAXRSTlMAQObYZgAABOdJREFUeNrs2F1TgkAYhmGeRMRWLbDlS5BAUPysqf7/b6sYt600dQ8a3nW4T3Teo2uWHRYw2tquMOiU5mhDl64ADR36jdZhsVv0X7XoFn2iFt2iT9Si/wG93AxY73Q8fLAooW2GizI7MRm0j4tLCiJoBTPyN4sE2s6h0o4EmkMprySAXmaK6CcC6A0UG1nNowdQLFo2j2aQmTxMKpyrbB7dk5rd54VfLXAmlxI6NOqKrUZocygOG43QXEycSB90KCZTUx90Mvk6bvRBV+P95F6jPY1RUQ9SrW55uPOdqR3id3kUEUYDkZkdmu8da731KrLoI3mPxkfDV486Ovthrps800aPUrs7l2ZR6uV00XPrc2GZMMtWC7Lo26Aez7gwyyxGFJ0MxWHOhVlmlW7pugE1dL03hPpl/y/wA2KfxX6gk++62cSoixkShxba9fuywjgsfgKwHZNCHyuYfVtvjjqfOHqdSWHMsG8XU0Z3I6Av98ZXi5vOR5uNQxDtVoBQzzhk+f63Sw+9juQenjKiz9Pj/oNsnQ4qYeNpZ0H1JYBBsWzVPPoZim2L5tElFOsRuOXFc6iVEkAbXSiVTCigjRAKmTaNE/G9nfNtbRsGwnjudBKW5Gk2ZtCtsJcb7N2+/5ebHv1JXROEkkFxih6IkSr57ufrnaQ2Ia93UP/4c5pt3Ar16dvfE509XpT/Hb62FX593852YHp9+dLWz3MfTfc686cQbmpAD+iGBvSAbmhAD+iGBvSAbmhAD+iGBvSAbuhjoDdKErYPmDSBttISWRvzhJI899m1tDShFRW5B6AnomBqSzXn3fWWANPchDbMMxF74segq3khf/h3X3g3b+Ek02V3aUFX+2QuD2l9e6vFH35VimjaQaPTr7mVHtW+XB6TAXRO5uVgRP8fNPdCM81WyCsp0dOeiBaDGY4o28GgqB00C7l8s5RhHx8iUFK4BW0dkdMpoyY493E4TJ5ydFUgCoFUL/RMQkViriXKGKgIS26pN2ht4a1Az7WkpZg5FuIGs8XBlrocp2uC59LNsv3QlO/2uH9DgwEINws7kmSdWWqZAE7jQRSMlGGPJULzgnv0EVqn1W9B6WP2CqcuuaJkZioOSN8BHVZHHoZtZIELXEL8cQ7JQrIiVXgPfXGYBCOeJKWGOmYEoANHpUBbuPKANrUFCy6aVWkJNdIPzYBzIArx4hBPQJucDj72HYVdduThtMXQpsjF4SVX0RFaJ9LqJLl0FTqatQgDPNdlowNaXyPtk5EKyPl2kzyuggdBytj96pGjKiTLFdqRugFt3kFzju+a-Sm0MnTOvP5IAzVlAeotxF6YLlYigaDAHfh8gpw8rhU6Q0U5pIdGKekMvd2ABqCCWcYMC9S5ACAxVS38diEaR1kqBwoGcImvrAAbUC7J2ny/eVtAgw1yZUjfgjZCZXE3lOUL9BxdrVIdNKEnKuIYX1egLZy4sphMBq0ggAB13bprOCu1zwsWCqORHmiXM8hSn9HCZ/aspdC000Nx1npZGVXFKl0AwmwvVuU5a4lcXgOqlLqy8ARUZnUcgnjXMdcjiI4ty7q4W9kWHjXx2oDul+rdiU/yR4C28SV9B5SzQJtr0nfoLNDlIOFNz9zTQKfi031TzwN9hwb0gG5oQA/oDxR9iq80ehI9PfQT6amhh4Y+k/4BYXA+w/0JM4sAAAAASUVORK5CYII=';
    const JAVDB_ITEM_SELECTOR = '.movie-list.v.cols-4.vcols-8 .item, .movie-list.v.cols-4.vcols-5 .item, .movie-list.h.cols-4.vcols-8 .item, .movie-list.h.cols-4.vcols-5 .item';

    function Queue(n) {
        n = parseInt(n, 10);
        return new Queue.prototype.init((n && n > 0) ? n : 1)
    }

    Queue.prototype = {
        init: function (n) {
            this.threads = [];
            this.taskList = [];
            while (n--) {
                this.threads.push(new this.Thread)
            }
        },
        push: function (callback) {
            if (typeof callback !== 'function') return;
            var index = this.indexOfIdle();
            if (index != -1) {
                this.threads[index].idle(callback);
            } else {
                this.taskList.push(callback);
                for (var i = 0, l = this.threads.length; i < l; i++) {
                    ((thread, self, id) => {
                        thread.idle(() => {
                            if (self.taskList.length > 0) {
                                let promise = self.taskList.shift()();
                                return promise.promise ? promise : $.Deferred().resolve().promise();
                            } else {
                                return $.Deferred().resolve().promise();
                            }
                        })
                    })(this.threads[i], this, i);
                }
            }
        },
        indexOfIdle: function () {
            var threads = this.threads,
                thread = null,
                index = -1;
            for (var i = 0, l = threads.length; i < l; i++) {
                thread = threads[i];
                if (thread.promise.state() === 'resolved') {
                    index = i;
                    break;
                }
            }
            return index;
        },
        Thread: function () {
            this.promise = $.Deferred().resolve().promise();
            this.idle = (callback) => {
                this.promise = this.promise.then(callback)
            }
        }
    };
    Queue.prototype.init.prototype = Queue.prototype;

    var thirdparty = {
        waterfallButton: null,
        waterfallScrollInit: () => {
            var w = new thirdparty.waterfall({});

            var $pages4 = $(JAVDB_ITEM_SELECTOR);
            if ($pages4.length) {
                GM_addStyle(`
                    .container {max-width: inherit !important;}
                    .tags{display: block !important;}
                    .tag.hobby{display: block;float: right;color: #fff;line-height: 2em;}
                `);
                $pages4[0].parentElement.id = "waterfall";
                w = new thirdparty.waterfall({
                    next: '.pagination .pagination-next',
                    item: JAVDB_ITEM_SELECTOR,
                    cont: '#waterfall',
                    pagi: '.pagination',
                });
            }

            w.setSecondCallback((cont, elems) => {
                cont.append(elems);
            });
        },
        waterfall: (() => {
            function waterfall(selectorcfg = {}) {
                class Lock {
                    constructor(d = false) {
                        this.locked = d;
                    }
                    lock() {
                        this.locked = true;
                    }
                    unlock() {
                        this.locked = false;
                    }
                }
                this.queue = new Queue(1);
                this.page_queue = new Queue(1);
                this.lock = new Lock();
                this.baseURI = this.getBaseURI();
                this.selector = {
                    next: '.pagination .pagination-next',
                    item: JAVDB_ITEM_SELECTOR,
                    cont: '#waterfall',
                    pagi: '.pagination',
                };
                Object.assign(this.selector, selectorcfg);
                this.pagegen = this.fetchSync(location.href);
                this.anchor = $(this.selector.pagi)[0];
                this._count = 0;
                this._1func = (cont, elems) => {
                    cont.empty().append(elems);
                };
                this._2func = (cont, elems) => {
                    cont.append(elems);
                };
                this._3func = (elems) => { };
                if ($(this.selector.item).length) {
                    document.addEventListener('scroll', this.scroll.bind(this));
                    document.addEventListener('wheel', this.wheel.bind(this));
                    this.appendElems(this._1func);
                }
            }

            waterfall.prototype.getBaseURI = () => {
                let _ = location;
                return `${_.protocol}//${_.hostname}${(_.port && `:${_.port}`)}`;
            };
            waterfall.prototype.getNextURL = function (href) {
                let a = document.createElement('a');
                a.href = href;
                return `${this.baseURI}${a.pathname}${a.search}`;
            };
            waterfall.prototype.fetchURL = function (url) {
                console.log(`fetchUrl = ${url}`);
                let status = 404;
                const fetchwithcookie = fetch(url, { credentials: 'same-origin' });
                return fetchwithcookie.then(response => {
                        status = response.status;
                        return response.text();
                    }).then(html => new DOMParser().parseFromString(html, 'text/html'))
                    .then(doc => {
                        let $doc = $(doc);
                        let elems = [];
                        let nextURL;
                        if(status < 300){
                            let href = $doc.find(this.selector.next).attr('href');
                            nextURL = href ? this.getNextURL(href) : undefined;
                            elems = $doc.find(this.selector.item);
                            for (const elem of elems) {
                                const links = elem.getElementsByTagName('a');
                                for (const link of links) {
                                    link.target = "_blank";
                                }
                            }
                            if ($(JAVDB_ITEM_SELECTOR).length && (this._count !== 0) && url === nextURL) {
                                if ($(`#waterfall>div>a[href="${$(elems[0]).find('a.box')[0].attr('href')}"]`).length > 0) {
                                    nextURL = undefined;
                                    elems = [];
                                }
                            }
                        } else {
                            nextURL = $doc.url;
                        }
                        return {
                            nextURL,
                            elems
                        };
                    });
            };
            waterfall.prototype.fetchSync = function* (urli) {
                let url = urli;
                do {
                    yield new Promise((resolve, reject) => {
                        if (this.lock.locked) {
                            reject();
                        }
                        else {
                            this.lock.lock();
                            resolve();
                        }
                    }).then(() => {
                        return this.fetchURL(url).then(info => {
                            url = info.nextURL;
                            return info.elems;
                        });
                    }).then(elems => {
                        this.lock.unlock();
                        return elems;
                    }).catch((err) => {
                        // Locked!
                    });
                } while (url);
            };
            waterfall.prototype.appendElems = function () {
                let nextpage = this.pagegen.next();
                if (!nextpage.done) {
                    nextpage.value.then(elems => {
                        const cb = (this._count === 0) ? this._1func : this._2func;
                        cb($(this.selector.cont), elems);
                        this._count += 1;
                    });
                }
                return nextpage.done;
            };
            waterfall.prototype.end = function () {
                document.removeEventListener('scroll', this.scroll.bind(this));
                document.removeEventListener('wheel', this.wheel.bind(this));
                let $end = $(`<h1>The End</h1>`);
                $(this.anchor).replaceWith($end);
            };
            waterfall.prototype.reachBottom = function (elem, limit) {
                return (elem.getBoundingClientRect().top - $(window).height()) < limit;
            };
            waterfall.prototype.scroll = function () {
                this.pageQueuePush();
            };
            waterfall.prototype.wheel = function () {
                this.pageQueuePush();
            };
            waterfall.prototype.pageQueuePush = function () {
                this.page_queue.push(() => {
                    let defer = $.Deferred();
                    new Promise(resolve => {
                        if (this.reachBottom(this.anchor, 1200) && this.appendElems(this._2func)) {
                            this.end();
                        }
                        resolve();
                    }).then(() => {
                        defer.resolve();
                    });
                    return defer.promise();
                });
            };
            waterfall.prototype.setFirstCallback = function (f) {
                this._1func = f;
            };
            waterfall.prototype.setSecondCallback = function (f) {
                this._2func = f;
            };
            waterfall.prototype.setJavlibCallback = function (f) {
                this._3func = f;
            };
            waterfall.prototype.setFourthCallback = function (f) {
                this._4func = f;
            };
            return waterfall;
        })(),
    };

    function mainRun() {
        thirdparty.waterfallScrollInit();
    }

    // Add JQuery
    (function(callback) {
        var script = document.createElement("script");
        script.setAttribute("src", "//cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js");
        script.addEventListener('load', function() {
            var script = document.createElement("script");
            script.textContent = '(' + callback.toString() + ')();';
            document.body.appendChild(script);
        }, false);
        document.body.appendChild(script);
    })(mainRun);

    // JavDB Jump Button Code
    var jumpButton = document.createElement('button');
    jumpButton.style.backgroundColor = 'green';
    jumpButton.style.color = 'white';
    jumpButton.style.position = 'fixed';
    jumpButton.style.bottom = '10px';
    jumpButton.style.left = '10px';
    jumpButton.style.zIndex = '9999';
    jumpButton.style.padding = '16px 24px';
    jumpButton.style.borderRadius = '8px';
    jumpButton.style.fontSize = '24px';
    jumpButton.style.border = 'none';
    jumpButton.style.cursor = 'pointer';
    jumpButton.style.display = 'none';

    jumpButton.addEventListener('click', function() {
        let 番號 = '';
        let url = '';
        if (window.location.href.includes('javdb.com')) {
            var link = document.querySelector('.panel-block.first-block a.button.is-white.copy-to-clipboard');
            if (link) {
                番號 = link.getAttribute('data-clipboard-text');
                if (番號) {
                    url = "https://missav.ws/" + 番號.toLowerCase();
                }
            }
        } else if (window.location.href.includes('missav.ws')) {
            var element = document.querySelector('.text-secondary span.font-medium');
            if (element) {
                番號 = element.innerText;
                番號 = 番號.replace(/-UNCENSORED-LEAK|-CHINESE-SUBTITLE/g, '');
                url = "https://javdb.com/search?f=all&q=" + 番號;
            }
        }
        if (番號 && url) {
            var a = document.createElement('a');
            a.href = url;
            a.target = '_blank';
            a.style.display = 'none';
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
        }
    });

    let 番號 = '';
    if (window.location.href.includes('javdb.com')) {
        var link = document.querySelector('.panel-block.first-block a.button.is-white.copy-to-clipboard');
        if (link) {
            番號 = link.getAttribute('data-clipboard-text');
        }
    } else if (window.location.href.includes('missav.ws')) {
        var element = document.querySelector('.text-secondary span.font-medium');
        if (element) {
            番號 = element.innerText;
            番號 = 番號.replace(/-UNCENSORED-LEAK|-CHINESE-SUBTITLE/g, '');
        }
    }

    if (番號) {
        jumpButton.innerHTML = 番號;
        jumpButton.style.display = 'block';
    } else {
        jumpButton.style.display = 'none';
    }

    document.body.appendChild(jumpButton);
    mainRun(); // 確保 mainRun 在跳轉按鈕初始化後運行
})();