Jav Local Helper

JavBus/JavDB 本地检测 + Everything搜索

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Advertisement:

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

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();


}



})();