油猴脚本,效果很简单,网页上的jav 番号,鼠标划过去弹出 2 个按钮,一个是快捷复制番号,一个是跳转到 javbus.com 的作品页面(在用 javbus 镜像站的可以自行添加到脚本里)

// ==UserScript==
// @name JAV hover tools
// @namespace https://www.javbus.com/
// @version 1.0.0
// @description 网页上出现的番号,鼠标划过浮出复制按钮+跳转 JavBus作品页
// @author Cod
// @match *://*/*
// @grant GM_setClipboard
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
const JAVBUS_ORIGIN = 'https://www.javbus.com';
const MARK_CLASS = 'javbus-number-hover-mark';
const SKIP_SELECTOR = [
'script',
'style',
'textarea',
'input',
'select',
'option',
'code',
'pre',
'[contenteditable="true"]',
`.${MARK_CLASS}`,
].join(',');
const numberPattern = /\b(?:FC2(?:[-_\s]*PPV)?[-_\s]*\d{5,8}|[A-Z]{2,8}(?:[-_]\d{2,6}|\d{3,5}))\b/gi;
const noSeparatorPrefixes = new Set([
'ABP', 'ABS', 'ADN', 'ADZ', 'AUKG', 'AVOP', 'BDA', 'BF', 'CAWD', 'CJOD',
'DANDY', 'DASD', 'DVAJ', 'EBOD', 'EKDV', 'FSDSS', 'FSDSSS', 'GENM', 'GVG',
'HND', 'IPX', 'IPZ', 'JUL', 'JUQ', 'JUX', 'KAWD', 'MIAA', 'MIDE', 'MIGD',
'MIMK', 'MIRD', 'MKMP', 'MMND', 'MOGI', 'NACR', 'NHDTA', 'NSPS', 'PRED',
'RBD', 'SDDE', 'SDJS', 'SDMU', 'SDNM', 'SDAB', 'SSIS', 'SSNI', 'START',
'STARS', 'SW', 'TEK', 'VEC', 'WANZ', 'WAAA', 'XVSR',
]);
const processedNodes = new WeakSet();
let activeMark = null;
let hideTimer = 0;
const style = document.createElement('style');
style.textContent = `
.${MARK_CLASS} {
position: relative;
border-bottom: 1px dotted rgba(32, 92, 255, 0.85);
cursor: pointer;
}
#javbus-number-hover-toolbar {
position: fixed;
z-index: 2147483647;
display: none;
align-items: center;
gap: 6px;
padding: 5px;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 7px;
background: rgba(255, 255, 255, 0.98);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
color: #1d2433;
font: 12px/1.2 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
backdrop-filter: blur(8px);
}
#javbus-number-hover-toolbar button {
min-width: 34px;
height: 26px;
padding: 0 9px;
border: 0;
border-radius: 5px;
background: #1f6feb;
color: #fff;
font: inherit;
font-weight: 600;
cursor: pointer;
}
#javbus-number-hover-toolbar button:hover {
background: #1759c7;
}
#javbus-number-hover-toolbar button[data-action="open"] {
background: #1f883d;
}
#javbus-number-hover-toolbar button[data-action="open"]:hover {
background: #187033;
}
`;
document.documentElement.appendChild(style);
const toolbar = document.createElement('div');
toolbar.id = 'javbus-number-hover-toolbar';
toolbar.innerHTML = `
<button type="button" data-action="copy" title="复制番号">copy</button>
<button type="button" data-action="open" title="打开 JavBus 页面">JavBus</button>
`;
document.documentElement.appendChild(toolbar);
function normalizeNumber(raw) {
let text = raw.toUpperCase().replace(/[_\s]+/g, '-').replace(/-+/g, '-');
if (/^FC2-?PPV-?\d+$/.test(text)) {
return text.replace(/^FC2-?PPV-?/, 'FC2-PPV-');
}
if (/^FC2-?\d+$/.test(text)) {
return text.replace(/^FC2-?/, 'FC2-');
}
if (/^[A-Z]{2,8}\d{2,6}$/.test(text)) {
text = text.replace(/^([A-Z]{2,8})(\d{2,6})$/, '$1-$2');
}
return text;
}
function isLikelyJavNumber(raw) {
const text = raw.toUpperCase().trim();
if (/^FC2(?:[-_\s]*PPV)?[-_\s]*\d{5,8}$/.test(text)) {
return true;
}
if (/^[A-Z]{2,8}[-_]\d{2,6}$/.test(text)) {
return true;
}
const noSeparatorMatch = text.match(/^([A-Z]{2,8})(\d{3,5})$/);
if (!noSeparatorMatch) {
return false;
}
return noSeparatorPrefixes.has(noSeparatorMatch[1]);
}
function isSkippable(node) {
const parent = node.parentElement;
if (!parent || parent.closest(SKIP_SELECTOR)) return true;
if (!node.nodeValue || !numberPattern.test(node.nodeValue)) return true;
numberPattern.lastIndex = 0;
return false;
}
function markTextNode(textNode) {
if (processedNodes.has(textNode) || isSkippable(textNode)) return;
processedNodes.add(textNode);
const text = textNode.nodeValue;
const fragment = document.createDocumentFragment();
let lastIndex = 0;
let match;
numberPattern.lastIndex = 0;
while ((match = numberPattern.exec(text)) !== null) {
const rawNumber = match[0];
if (!isLikelyJavNumber(rawNumber)) continue;
const start = match.index;
const end = start + rawNumber.length;
if (start > lastIndex) {
fragment.appendChild(document.createTextNode(text.slice(lastIndex, start)));
}
const mark = document.createElement('span');
mark.className = MARK_CLASS;
mark.dataset.javNumber = normalizeNumber(rawNumber);
mark.textContent = rawNumber;
fragment.appendChild(mark);
lastIndex = end;
}
if (lastIndex < text.length) {
fragment.appendChild(document.createTextNode(text.slice(lastIndex)));
}
if (fragment.childNodes.length > 1 || lastIndex > 0) {
textNode.parentNode.replaceChild(fragment, textNode);
}
}
function scan(root = document.body) {
if (!root) return;
if (root.nodeType === Node.TEXT_NODE) {
markTextNode(root);
return;
}
if (root.nodeType !== Node.ELEMENT_NODE || root.closest?.(SKIP_SELECTOR)) return;
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
acceptNode(node) {
return isSkippable(node) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT;
},
});
const textNodes = [];
let node;
while ((node = walker.nextNode())) {
textNodes.push(node);
}
textNodes.forEach(markTextNode);
}
function showToolbar(mark) {
activeMark = mark;
clearTimeout(hideTimer);
const rect = mark.getBoundingClientRect();
toolbar.style.display = 'flex';
const toolbarRect = toolbar.getBoundingClientRect();
const top = Math.max(8, rect.top - toolbarRect.height - 7);
const left = Math.min(
window.innerWidth - toolbarRect.width - 8,
Math.max(8, rect.left + rect.width / 2 - toolbarRect.width / 2),
);
toolbar.style.top = `${top}px`;
toolbar.style.left = `${left}px`;
}
function scheduleHide() {
clearTimeout(hideTimer);
hideTimer = window.setTimeout(() => {
toolbar.style.display = 'none';
activeMark = null;
}, 220);
}
async function copyText(text) {
if (typeof GM_setClipboard === 'function') {
GM_setClipboard(text, 'text');
return;
}
await navigator.clipboard.writeText(text);
}
document.addEventListener('mouseover', (event) => {
const mark = event.target.closest?.(`.${MARK_CLASS}`);
if (mark) showToolbar(mark);
});
document.addEventListener('mouseout', (event) => {
const mark = event.target.closest?.(`.${MARK_CLASS}`);
if (mark && !toolbar.matches(':hover')) scheduleHide();
});
toolbar.addEventListener('mouseenter', () => clearTimeout(hideTimer));
toolbar.addEventListener('mouseleave', scheduleHide);
toolbar.addEventListener('click', async (event) => {
const button = event.target.closest('button');
if (!button || !activeMark) return;
const number = activeMark.dataset.javNumber;
if (button.dataset.action === 'copy') {
await copyText(number);
button.textContent = '✅';
window.setTimeout(() => {
button.textContent = 'copy';
}, 900);
return;
}
window.open(`${JAVBUS_ORIGIN}/${encodeURIComponent(number)}`, '_blank', 'noopener');
});
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
scan(node);
}
}
});
scan();
observer.observe(document.documentElement, { childList: true, subtree: true });
})();複製代碼
还没有评论,来抢沙发吧。