Jav Local Helper

JavBus/JavDB 本地检测 + Everything搜索

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

Advertisement:

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

Advertisement:

// ==UserScript==
// @name         Jav Local Helper
// @namespace    https://sleazyfork.org/zh-CN/scripts/586014-jav-local-helper
// @version      2026.07.08
// @description  JavBus/JavDB 本地检测 + Everything搜索
// @author       Hawwk
// @match        https://javbus.com/*
// @match        https://www.javbus.com/*
// @match        https://javdb.com/*
// @match        https://*.javdb.com/*
// @match        http://127.0.0.1:80/*
// @match        http://localhost:80/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_listValues
// @grant        GM_deleteValue
// @grant        GM_registerMenuCommand
// @connect      localhost
// @connect      127.0.0.1
// @license      MIT
// ==/UserScript==


(function(){

'use strict';



// ==================================================
// 基础配置
// ==================================================

const EVERYTHING_URL =
    'http://localhost:80/';


const CACHE_PREFIX =
    'jav_local_cache_';


const DEBUG =
    GM_getValue(
        'debug_mode',
        false
    );



const VIDEO_EXT =
    'mp4;mkv;avi;mov;wmv;flv;webm;m4v;html';





// ==================================================
// 工具函数
// ==================================================

function log(...args){

    if(DEBUG)
        console.log(
            '[JavLocal]',
            ...args
        );

}




function sleep(ms){

    return new Promise(
        r=>setTimeout(r,ms)
    );

}





// ==================================================
// 缓存系统
// ==================================================

function getCache(code){

    if(!code)
        return null;


    try{

        return JSON.parse(
            GM_getValue(
                CACHE_PREFIX + code,
                'null'
            )
        );


    }catch(e){

        return null;

    }

}




function setCache(
    code,
    path
){

    if(!code || !path)
        return;



    GM_setValue(

        CACHE_PREFIX + code,

        JSON.stringify({

            path:path,

            time:Date.now()

        })

    );


    log(
        '缓存:',
        code,
        path
    );

}





function clearCache(){

    GM_listValues()
    .forEach(k=>{


        if(
            k.startsWith(
                CACHE_PREFIX
            )
        ){

            GM_deleteValue(k);

        }


    });


}





// ==================================================
// Everything 查询
// ==================================================

function buildSearch(code){

    return (
        code +
        ' ext:' +
        VIDEO_EXT
    );

}







function queryEverything(code){


    return new Promise(resolve=>{


        const search =
            buildSearch(code);



        const url =

            EVERYTHING_URL +

            '?json=1' +

            '&path=1' +

            '&search=' +

            encodeURIComponent(
                search
            );



        GM_xmlhttpRequest({

            method:'GET',

            url:url,

            timeout:5000,


            onload(resp){


                try{


                    const data =
                        JSON.parse(
                            resp.responseText
                        );



                    if(
                        !data.results ||
                        data.results.length===0
                    ){

                        resolve(null);

                        return;

                    }




                    const files =
                        data.results.map(
                            r=>({

                                name:
                                    r.name || '',


                                path:
                                    r.path || ''

                            })
                        );




                    const best =
                        selectBestFile(
                            files
                        );




                    if(best){


                        const full =
                            normalizePath(
                                best.path,
                                best.name
                            );



                        setCache(
                            code,
                            full
                        );



                        resolve(full);



                    }else{


                        resolve(null);

                    }



                }catch(e){


                    log(
                        'JSON错误',
                        e
                    );


                    resolve(null);

                }


            },


            onerror(){

                resolve(null);

            },


            ontimeout(){

                resolve(null);

            }


        });


    });


}






// ==================================================
// 文件选择策略
// ==================================================

function selectBestFile(files){


    if(
        !files ||
        files.length===0
    )
        return null;



    let list =
        files;



    const ch =
        files.filter(
            f=>/ch/i.test(
                f.name
            )
        );



    if(
        ch.length
    ){

        list =
            ch;

    }



    return list[0];


}






function normalizePath(
    dir,
    name
){

    if(!dir)
        return name || '';



    if(
        dir.endsWith(name)
    )
        return dir;



    return (

        dir.replace(
            /\\+$/,
            ''
        )

        +

        '\\'

        +

        name

    );

}





// ==================================================
// 查询本地文件
// ==================================================

async function findLocalFile(code){


    if(!code)
        return null;



    const cache =
        getCache(code);



    if(
        cache &&
        cache.path
    ){

        return cache.path;

    }



    return await queryEverything(code);


}






// ==================================================
// 网站识别
// ==================================================

const HOST =
    location.hostname;



const isJavBus =
    HOST.includes(
        'javbus'
    );



const isJavDB =
    HOST.includes(
        'javdb'
    );



const isJavDBDetail =
    isJavDB &&
    /^\/v\//.test(
        location.pathname
    );







// ==================================================
// 番号提取
// ==================================================

function getJavBusCode(){


    const el =
        document.querySelector(
            'span[style*="color:#CC0000"]'
        );



    if(el){

        const code =
            el.innerText.trim();



        if(
            /^[A-Z0-9]+-\d+$/i.test(code)
        ){

            return code.toUpperCase();

        }

    }



    return null;

}





function getJavDBCode(){


    const values =
        document.querySelectorAll(
            '.value'
        );



    for(
        const v of values
    ){

        const text =
            v.innerText.trim();



        if(
            /^[A-Z0-9]+-\d+$/i.test(text)
        ){

            return text.toUpperCase();

        }

    }




    const body =
        document.body.innerText;



    const match =
        body.match(
            /\b[A-Z]{2,8}-\d{2,5}\b/i
        );



    if(match){

        return match[0]
        .toUpperCase();

    }



    return null;

}





function getCurrentCode(){


    if(isJavBus)
        return getJavBusCode();



    if(isJavDB)
        return getJavDBCode();



    return null;

}


// ==================================================
// 调用本机Everything软件搜索
// ==================================================

function openEverythingSearch(code){

    if(!code)
        return;


    const url =
        'es:' +
        code;


    window.location.href =
        url;

}


// ==================================================
// 详情页检测按钮
// ==================================================

let playButton = null;



function createPlayButton(){


    if(
        document.getElementById(
            'jav-local-play'
        )
    ){

        playButton =
            document.getElementById(
                'jav-local-play'
            );

        return;

    }



    let target = null;



    if(isJavBus){


        target =
            document.querySelector(
                'ul.nav'
            );


    }



    if(isJavDBDetail){


        target =
            document.querySelector(
                '.video-panel-info'
            )
            ||
            document.querySelector(
                '.panel-block'
            );


    }



    if(!target){

        setTimeout(
            createPlayButton,
            1000
        );

        return;

    }




    const btn =
        document.createElement(
            'a'
        );



    btn.id =
        'jav-local-play';



    btn.href =
        '#';



    btn.innerHTML =
        '⏳检测本地';



    btn.style.cssText =

    `
    margin-left:12px;
    cursor:pointer;
    color:#888;
    font-size:14px;
    text-decoration:none;
    `;



    target.appendChild(
        btn
    );



    playButton =
        btn;





    btn.onclick =
    async function(e){


        e.preventDefault();



        const code =
            getCurrentCode();



        if(!code){

            alert(
                '未找到番号'
            );

            return;

        }



        const path =
            await findLocalFile(
                code
            );



        if(path){

            openEverythingSearch(
                code
            );


        }else{


            alert(
                '未找到本地文件:\n' +
                code
            );

        }


    };



    refreshButton();

}







// ==================================================
// 更新详情按钮状态
// ==================================================

async function refreshButton(){


    if(!playButton)
        return;



    const code =
        getCurrentCode();



    if(!code){

        playButton.innerHTML =
            '❌无番号';

        return;

    }




    playButton.innerHTML =
        '🔍检测中...';



    const path =
        await findLocalFile(
            code
        );



    if(path){


        playButton.innerHTML =
            '🔍 Everything';



        playButton.style.color =
            '#27ae60';



        playButton.title =
            path;



    }else{


        playButton.innerHTML =
            '📁无本地文件';



        playButton.style.color =
            '#e67e22';


    }


}







// ==================================================
// JavDB封面角标
// ==================================================

function addDownloadedBadge(
    cover,
    code
){


    if(!cover)
        return;



    if(
        cover.querySelector(
            '.jav-local-badge'
        )
    )
        return;




    const badge =
        document.createElement(
            'div'
        );



    badge.className =
        'jav-local-badge';



    badge.innerHTML =
        '✓ 已下载';



    badge.style.cssText =

    `
    position:absolute;

    top:5px;

    left:5px;

    z-index:99;

    background:#27ae60;

    color:white;

    padding:3px 8px;

    border-radius:4px;

    font-size:12px;

    cursor:pointer;

    `;



    badge.onclick =
    function(e){


        e.stopPropagation();


        openEverythingSearch(
            code
        );


    };



    const style =
        getComputedStyle(
            cover
        );



    if(
        style.position ===
        'static'
    ){

        cover.style.position =
            'relative';

    }



    cover.appendChild(
        badge
    );


}







function getCoverElement(el){


    return (

        el.closest(
            '.item'
        )

        ||

        el.closest(
            '.movie-list'
        )

        ||

        el.closest(
            '.video-list-item'
        )

        ||

        el.parentElement

    );


}







// ==================================================
// 详情页初始化
// ==================================================

function initDetailPage(){


    createPlayButton();



    let lastCode = '';



    setInterval(()=>{


        const code =
            getCurrentCode();



        if(
            code &&
            code !== lastCode
        ){

            lastCode =
                code;



            createPlayButton();


            refreshButton();

        }


    },1000);


}








// ==================================================
// 列表检测
// ==================================================

const listProcessed =
    new Set();





async function checkListCode(
    code,
    element
){


    if(
        !code ||
        listProcessed.has(code)
    )
        return;



    listProcessed.add(
        code
    );



    const path =
        await findLocalFile(
            code
        );



    if(path){


        markLocal(
            element,
            code
        );


    }


}








// ==================================================
// 标记已下载
// ==================================================

function markLocal(
    el,
    code
){


    el.style.color =
        '#27ae60';



    el.style.cursor =
        'pointer';



    el.title =
        '点击搜索本地文件';



    el.dataset.javLocal =
        '1';




    if(
        isJavDB &&
        !isJavDBDetail
    ){


        addDownloadedBadge(

            getCoverElement(el),

            code

        );


    }


}







// ==================================================
// 扫描列表
// ==================================================

function scanListPage(){


    let nodes = [];



    if(isJavBus){


        nodes =
        [

            ...document.querySelectorAll(
                '.photo-info date'
            )

        ];


    }




    if(
        isJavDB &&
        !isJavDBDetail
    ){


        nodes =
        [

            ...document.querySelectorAll(
                '.uid'
            ),

            ...document.querySelectorAll(
                '.video-title'
            )

        ];


    }




    nodes.forEach(el=>{


        if(
            el.dataset.checked
        )
            return;



        const match =
            el.innerText.match(
                /\b[A-Z0-9]{2,8}-\d{2,5}\b/i
            );



        if(!match)
            return;



        el.dataset.checked =
            '1';



        checkListCode(
            match[0].toUpperCase(),
            el
        );


    });


}







// ==================================================
// 点击已下载标识
// ==================================================

document.addEventListener(
'click',
function(e){


    const badge =
        e.target.closest(
            '.jav-local-badge'
        );



    if(badge)
        return;



    const el =
        e.target.closest(
            '[data-jav-local]'
        );



    if(!el)
        return;



    const code =
        el.innerText.match(
            /\b[A-Z0-9]{2,8}-\d{2,5}\b/i
        );



    if(code){

        openEverythingSearch(
            code[0].toUpperCase()
        );

    }


},
true
);








// ==================================================
// Everything批量导入
// ==================================================

async function importEverything(){


    const url =

        EVERYTHING_URL +

        '?json=1' +

        '&path=1' +

        '&search=' +

        encodeURIComponent(
            'ext:' + VIDEO_EXT
        );



    GM_xmlhttpRequest({

        method:'GET',

        url:url,


        onload(resp){


            try{


                const data =
                    JSON.parse(
                        resp.responseText
                    );


                let count=0;



                data.results.forEach(r=>{


                    const m =
                        r.name.match(
                            /\b[A-Z0-9]{2,8}-\d{2,5}\b/i
                        );



                    if(m){


                        setCache(

                            m[0].toUpperCase(),

                            normalizePath(
                                r.path,
                                r.name
                            )

                        );


                        count++;

                    }


                });



                alert(
                    '导入完成\n数量:'+
                    count
                );


            }catch(e){


                alert(
                    '导入失败'
                );

            }


        }


    });


}

// ==================================================
// 调试缓存
// ==================================================

function showCache(){


    const keys =
        GM_listValues()
        .filter(
            k =>
            k.startsWith(
                CACHE_PREFIX
            )
        );



    console.clear();



    console.log(
        '缓存数量:',
        keys.length
    );



    keys.forEach(k=>{


        console.log(

            k.replace(
                CACHE_PREFIX,
                ''
            ),

            GM_getValue(k)

        );


    });



    alert(
        '缓存信息已输出到控制台'
    );


}








// ==================================================
// 油猴菜单
// ==================================================

GM_registerMenuCommand(
'🗑 清空本地缓存',
()=>{


    clearCache();



    alert(
        '缓存已清空'
    );


});





GM_registerMenuCommand(
'📥 从Everything导入索引',
()=>{


    importEverything();


});





GM_registerMenuCommand(
'🐛 查看缓存',
()=>{


    showCache();


});





GM_registerMenuCommand(
'🔧 调试模式',
()=>{


    GM_setValue(

        'debug_mode',

        !DEBUG

    );


    alert(
        '刷新页面后生效'
    );


});









// ==================================================
// 初始化
// ==================================================

function init(){


    /*
    
    JavBus:
        详情按钮 + 列表检测


    JavDB:
        /v/xxx
            详情按钮


        其他页面
            封面已下载标识

    */



    if(isJavBus){


        initDetailPage();



        setTimeout(
            scanListPage,
            1000
        );



        setInterval(
            scanListPage,
            3000
        );



        return;

    }





    if(isJavDBDetail){


        initDetailPage();



        return;

    }





    if(isJavDB){


        setTimeout(
            scanListPage,
            1000
        );



        setInterval(
            scanListPage,
            3000
        );


    }


}







// ==================================================
// 启动
// ==================================================

if(
    document.readyState ===
    'loading'
){


    document.addEventListener(
        'DOMContentLoaded',
        init
    );


}else{


    init();


}



})();