Jav跳转到Emby播放,支持 JavBus/Javdb/library/javbooks/avmoo/avsox/sehuatang/msin图书馆

在JavBus/Javdb/library/javbooks/avmoo/avsox/sehuatang/msin图书馆高亮emby存在的视频,并在详情页提供一键跳转功能

// ==UserScript==
// @name         Jav跳转到Emby播放,支持 JavBus/Javdb/library/javbooks/avmoo/avsox/sehuatang/msin图书馆
// @namespace    http://tampermonkey.net/
// @version      2025.2.7
// @description  在JavBus/Javdb/library/javbooks/avmoo/avsox/sehuatang/msin图书馆高亮emby存在的视频,并在详情页提供一键跳转功能
// @include      /^.*(jav|bus|dmm|see|cdn|fan){2}\..*$/
// @match        *://www.javbus.com/*
// @include      *://javdb*.com/v/*
// @include      *://javdb*.com/search?q=*
// @match        *://www.javdb.com/*
// @match        *://javdb.com/*
// @include      *://*.javlib.com/*
// @include      *://*.javlibrary.com/*
// @include      *://*/cn/*v=jav*
// @include      *://*/en/*v=jav*
// @include      *://*/tw/*v=jav*
// @include      *://*/ja/*v=jav*
// @include      /^.*(avmoo|avsox)\..*$/
// @include      *://avmoo.*/*/movie/*
// @include      *://avsox.*/*/movie/*
// @match        https://www.sehuatang.net/thread-*
// @match        https://www.sehuatang.net/forum.php?mod=viewthread&tid=*
// @match        https://.*/thread-*
// @match        https://.*/forum.php?mod=viewthread&tid=*
// @match        https://www.tanhuazu.com/threads/*
// @match       *://javbooks.com/content*censored/*.htm
// @match       *://jmvbt.com/content*censored/*.htm
// @match       *://*.com/content*censored/*.htm
// @include     *://*.cc/content_censored/*.htm
// @include     /^https:\/\/jbk008\.com\/serchinfo\_(censored|uncensored)\/topicsbt/
// @match       *://db.msin.jp/jp.page/movie?id=*
// @match       *://db.msin.jp/page/movie?id=*
// @include      *://*/works/detail/*
// @match        *://xslist.org/search?query=*
// @grant        GM_xmlhttpRequest
// @license MIT

// ==/UserScript==

const embyAPI = "279b8961e1fd44b1bf8ea4c025f67075";
const embyBaseUrl = "http://192.168.5.8:35405/";
const defaultColor = "#52b54b";  // HotPink

(function () {
    'use strict';

    class Base {
        fetchEmbyData(code, callback) {
            console.log('Fetching data for code:', code);

            GM_xmlhttpRequest({
                method: "GET",
                url: `${embyBaseUrl}emby/Users/${embyAPI}/Items?api_key=${embyAPI}&Recursive=true&IncludeItemTypes=Movie&SearchTerm=${code}`,
                headers: { accept: "application/json" },
                onload: (res) => {
                    try {
                        const data = JSON.parse(res.responseText);
                        callback(data);
                    } catch (error) {
                        console.error("Failed to parse response:", error);
                    }
                },
                onerror: (e) => {
                    console.error("Error fetching Emby data:", e);
                },
                ontimeout: () => {
                    console.error("Request to Emby timed out");
                }
            });
        }

        insertEmbyLink(targetElement, data) {
            data.Items.forEach(item => {
                const [embyUrlSpanStyle, embyUrlAStyle] = [`background: ${defaultColor}; border-radius: 3px; padding: 3px 6px;`, `color: white; text-decoration: none;`]
                const embyUrl = `${embyBaseUrl}web/index.html#!/item?id=${item.Id}&serverId=${item.ServerId}`;
                const embyLink = `<div style="${embyUrlSpanStyle}">
                                <a href="${embyUrl}" style="${embyUrlAStyle}" target="_blank">
                                  <b>跳转到emby👉</b>
                                </a>
                              </div>`;
                $(targetElement).after(embyLink);
            });
        }

        highlightAndInsertEmbyLink(videos, extractFanhaoFunction, insertAfterSelector) {
            // console.log('Highlighting videos...', videos);
            const videoArray = Array.from(videos);

            videoArray.forEach(videoElement => {
                const fanhaos = extractFanhaoFunction(videoElement);
                // console.log('Fanhaos:', fanhaos);

                if (!fanhaos || fanhaos.length === 0) return;

                const searchNextFanhao = (fanhaoIndex) => {
                    if (fanhaoIndex >= fanhaos.length) return;

                    let fanhao = fanhaos[fanhaoIndex];
                    this.fetchEmbyData(fanhao, (data) => {
                        if (data.Items.length > 0) {
                            const targetElement = insertAfterSelector
                                ? videoElement.querySelector(insertAfterSelector)
                                : videoElement;
                            this.insertEmbyLink(targetElement, data);

                            // 高亮
                            videoElement.style.borderWidth = "3px";
                            videoElement.style.borderStyle = "solid";
                            videoElement.style.borderColor = defaultColor;
                            videoElement.style.backgroundColor = defaultColor;
                        } else {
                            searchNextFanhao(fanhaoIndex + 1);
                        }
                    });
                };

                searchNextFanhao(0);
            });
        }
    }

    // 定义各站点处理类(保持空类结构)
    class JavBus extends Base { }
    class JavLibrary extends Base { }
    class Javdb extends Base { }
    class Javbooks extends Base { }
    class Avmoo extends Base { }
    class Sehuatang extends Base { }
    class Msin extends Base { }

    class Main {
        constructor() {
            console.log('Jav跳转Emby启动...');

            this.sites = {
                'javBus': {
                    selector: "footer:contains('JavBus')",  // 根据页脚判断JavBus
                    class: JavBus,
                    listPageSelector: "#waterfall .item.masonry-brick",  // 根据id和class判断瀑布/列表页
                    listPageInsertAfter: ".item date", // 新增插入位置控制
                    detailPageSelector: '.col-md-3.info p span:nth-child(2)',  // 根据选择器是否获取道识别码/番号判断详情页
                    detailPageContainer: ".col-md-3.info p span:nth-child(2):first",  // 插入Emby链接的容器
                    listPageExtract: (el) => {  // 列表页提取番号
                        const fanhaoElement = el.querySelector('.item date');
                        return fanhaoElement ? [fanhaoElement.textContent.trim()] : [];
                    },
                    detailPageExtract: () => {  // 详情页提取番号
                        const code = $('.col-md-3.info p').eq(0).find('span').eq(1).html();
                        return code ? [code] : [];
                    }
                },
                'javLibrary': {
                    selector: "#bottomcopyright:contains('JAVLibrary')",
                    class: JavLibrary,
                    listPageSelector: ".video",
                    listPageInsertAfter: "a",
                    detailPageSelector: '#content #video_title #video_jacket_info #video_info .item .text',
                    detailPageContainer: "#video_info",
                    commentPageSelector: "#video_comments .comment",
                    commentPageInsertAfter: "strong",
                    listPageExtract: (el) => {
                        const fanhao = el.children[0]?.title.split(" ")[0] || el.children[1]?.title.split(" ")[0];
                        return fanhao ? [fanhao] : [];
                    },
                    detailPageExtract: () => {
                        const code = $('#video_info .item').eq(0).find('.text').html();
                        return code ? [code] : [];
                    },
                    commentPageExtract: (el) => {
                        const anchorElement = el.querySelector('a[href^="videoreviews.php?v="]');
                        return anchorElement ? [anchorElement.textContent.split(" ")[0]] : [];
                    }
                },
                'javdb': {
                    selector: "#footer:contains('javdb')",
                    class: Javdb,
                    listPageSelector: ".movie-list .item",
                    listPageInsertAfter: ".video-title strong", // 新增插入位置控制
                    detailPageSelector: 'body > section > div > div.video-detail > h2 > strong',
                    detailPageContainer: ".panel.movie-panel-info .value:first",
                    listPageExtract: (el) => {
                        const result = [];
                        const videoTitleElement = el.querySelector('.video-title strong');
                        if (videoTitleElement) {
                            const text = videoTitleElement.textContent.trim();
                            if (/\d/.test(text)) result.push(text);
                            else {
                                const title = el.querySelector('.video-title').textContent.trim();
                                result.push(title);
                            }
                        }
                        return result;
                    },
                    detailPageExtract: () => {
                        const code = $('body > section > div > div.video-detail > h2 > strong').text().trim().split(' ')[0];
                        return code ? [code] : [];
                    }
                },
                'javbooks': {
                    selector: "#Declare_box:contains('javbooks')",
                    class: Javbooks,
                    detailPageSelector: '#info > div:nth-child(2) > font',
                    detailPageContainer: "#info",
                    detailPageExtract: () => {
                        const code = $('#info > div:nth-child(2) > font').text().trim().split(' ')[0];
                        return code ? [code] : [];
                    }
                },
                'avmoo': {
                    selector: "footer:contains('AVMOO')",
                    class: Avmoo,
                    listPageSelector: "#waterfall .item",  // 根据id和class判断瀑布/列表页
                    listPageInsertAfter: ".item date", // 新增插入位置控制
                    listPageExtract: (el) => {  // 列表页提取番号
                        const fanhaoElement = el.querySelector('.item date');
                        return fanhaoElement ? [fanhaoElement.textContent.trim()] : [];
                    },
                    detailPageSelector: '.col-md-3.info p span:nth-child(2)',
                    detailPageContainer: ".col-md-3.info",
                    detailPageExtract: () => {
                        const code = $('.col-md-3.info p').eq(0).find('span').eq(1).html();
                        return code ? [code] : [];
                    }
                },
                'sehuatang': {
                    selector: "#flk:contains('色花堂')",
                    class: Sehuatang,
                    detailPageCodeRegex: /([a-zA-Z]{2,15}[-\s]?\d{2,15}|FC2PPV-[^\d]{0,5}\d{6,7})/i,
                    detailPageContainer: "#pgt",
                    detailPageExtract: () => {
                        const str = document.title.split(" ")[0];
                        return str.match(this.detailPageCodeRegex) || [];
                    }
                },
                'msin': {
                    selector: "#footer:contains('db.msin.jp')",
                    class: Msin,
                    detailPageSelector: 'div.mv_pn',
                    detailPageContainer: "#top_content",
                    detailPageExtract: () => {
                        const code = $('div.mv_pn').text().trim().split(' ')[0];
                        return code ? [code] : [];
                    }
                }
            };

            this.site = Object.keys(this.sites).find(key => $(this.sites[key].selector).length) || null;
            console.log('Matched site:', this.site);

            this.siteClass = this.site ? this.sites[this.site].class : null;
            // console.log('Site class:', this.siteClass);
        }

        make() {
            if (!this.siteClass) return;

            const siteConfig = this.sites[this.site];
            // console.log('Site Config:', this.siteConfig);
            const instance = new siteConfig.class();
            // console.log('$(siteConfig.listPageSelector).length:', $(siteConfig.listPageSelector).length);
            // console.log('$(siteConfig.commentPageSelector).length:', $(siteConfig.commentPageSelector).length);

            // 处理列表页
            if ($(siteConfig.listPageSelector).length > 0) {
                console.log('处理列表页', $(siteConfig.listPageSelector).length);
                instance.highlightAndInsertEmbyLink(
                    $(siteConfig.listPageSelector),
                    (el) => siteConfig.listPageExtract ? siteConfig.listPageExtract(el) : [],
                    siteConfig.listPageInsertAfter // 传递插入位置选择器
                );
            }
            // 处理详情页
            else if ($(siteConfig.detailPageSelector).length > 0) {
                console.log('处理详情页', $(siteConfig.detailPageSelector).length);
                const codes = siteConfig.detailPageExtract ? siteConfig.detailPageExtract() : [];
                codes.forEach(code => {
                    instance.fetchEmbyData(code, (data) => {
                        if (data.Items.length > 0) {
                            instance.insertEmbyLink($(siteConfig.detailPageContainer), data);
                        }
                    });
                });
            }
            // 处理评论页
            else if ($(siteConfig.commentPageSelector).length > 0) {
                console.log('处理评论页');
                // 添加对应的评论页选择器和处理逻辑
                instance.highlightAndInsertEmbyLink(
                    $(siteConfig.commentPageSelector),
                    (el) => siteConfig.commentPageExtract ? siteConfig.commentPageExtract(el) : [],
                    siteConfig.commentPageInsertAfter
                );
            }
        }
    }

    setTimeout(() => new Main().make(), 1000);
})();