JavBus/JavDB 本地检测 + Everything搜索
// ==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();
}
})();