JavDB 跳转 JavTrailers; From JavDB jump to JavTrailers.
// ==UserScript==
// @name JavDB to JavTrailers
// @namespace http://tampermonkey.net/
// @version 1.7
// @description JavDB 跳转 JavTrailers; From JavDB jump to JavTrailers.
// @author Phoebe
// @match https://javdb.com/*
// @match https://javtrailers.com/*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// --- JavTrailers 静音和全屏逻辑 ---
if (location.hostname === 'javtrailers.com') {
const STYLE_ID = 'javtrailers-web-fullscreen-style';
// 用户交互时间戳(用于判断是否是用户手动操作)
let lastUserInteraction = 0;
const updateInteraction = () => { lastUserInteraction = Date.now(); };
document.addEventListener('pointerdown', updateInteraction, true);
document.addEventListener('keydown', updateInteraction, true);
// 拦截页面脚本对视频 pause / currentTime 的操控
function hookVideoMethods() {
if (window.__jtVideoHooked) return;
window.__jtVideoHooked = true;
const originalPause = HTMLMediaElement.prototype.pause;
HTMLMediaElement.prototype.pause = function() {
if (this.tagName === 'VIDEO' && this.closest('.javtrailers-web-fullscreen')) {
if (Date.now() - lastUserInteraction < 800) return originalPause.apply(this, arguments);
if (this.ended || (this.duration && this.currentTime >= this.duration - 0.5)) return originalPause.apply(this, arguments);
console.log('JavTrailers: Blocked auto pause');
return;
}
return originalPause.apply(this, arguments);
};
const descriptor = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'currentTime');
if (descriptor && descriptor.set) {
Object.defineProperty(HTMLMediaElement.prototype, 'currentTime', {
configurable: true,
enumerable: true,
get: descriptor.get,
set: function(value) {
if (this.tagName === 'VIDEO' && this.closest('.javtrailers-web-fullscreen')) {
if (value === 0 && Date.now() - lastUserInteraction > 800) {
const current = descriptor.get.call(this);
if (current > 2) {
console.log('JavTrailers: Blocked currentTime reset to 0');
return;
}
}
}
return descriptor.set.call(this, value);
}
});
}
}
// 确保全屏样式只注入一次
function ensureFullscreenStyle() {
if (document.getElementById(STYLE_ID)) return;
const style = document.createElement('style');
style.id = STYLE_ID;
style.textContent = `
.javtrailers-web-fullscreen {
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100vw !important;
height: 100vh !important;
z-index: 2147483647 !important;
background: #000 !important;
margin: 0 !important;
padding: 0 !important;
}
.javtrailers-web-fullscreen video {
width: 100% !important;
height: 100% !important;
object-fit: contain !important;
}
`;
document.head.appendChild(style);
}
// 设置视频默认静音,并防止页面脚本把它改回
function muteVideos() {
const videos = document.querySelectorAll('video');
videos.forEach(video => {
if (!video.dataset.jtMuted) {
video.muted = true;
video.dataset.jtMuted = 'true';
video.addEventListener('volumechange', function onVolumeChange() {
if (!video.muted) {
video.muted = true;
console.log('JavTrailers: Re-muted video');
}
});
console.log('JavTrailers: Video muted');
}
});
}
// 查找最合适的视频容器
function findVideoContainer(video) {
let container = video.parentElement;
while (container && container !== document.body) {
if (container.tagName === 'DIV' || container.tagName === 'SECTION') {
break;
}
container = container.parentElement;
}
return container;
}
// 设置视频网页全屏(每个视频只处理一次,避免重复触发播放器重置)
function webFullscreenVideo() {
ensureFullscreenStyle();
const videos = document.querySelectorAll('video:not([data-jt-fullscreen])');
videos.forEach(video => {
if (video.readyState >= 2) {
const container = findVideoContainer(video);
if (container && container !== document.body) {
container.classList.add('javtrailers-web-fullscreen');
video.dataset.jtFullscreen = 'true';
video.play().catch(() => {});
console.log('JavTrailers: Web fullscreen enabled');
}
}
});
}
// 防止视频被页面脚本自动暂停并回到封面
function preventAutoPause() {
const videos = document.querySelectorAll('video:not([data-jt-pause-guard])');
videos.forEach(video => {
video.dataset.jtPauseGuard = 'true';
video.addEventListener('pause', function onPause() {
if (video.ended || video.currentTime <= 0) return;
if (video.duration && video.currentTime >= video.duration - 0.5) return;
setTimeout(() => {
if (Date.now() - lastUserInteraction < 500) return;
if (video.paused && !video.ended) {
video.play().catch(() => {});
}
}, 100);
});
});
}
// 持续守护:防止页面脚本重置进度或自动暂停
function keepVideoAlive() {
const lastTimes = new WeakMap();
setInterval(() => {
const videos = document.querySelectorAll('video');
videos.forEach(video => {
if (!video.duration) return;
const lastTime = lastTimes.get(video) || 0;
// 如果进度被非用户操作重置到 0,恢复
if (video.currentTime === 0 && lastTime > 2) {
video.currentTime = lastTime;
video.play().catch(() => {});
console.log('JavTrailers: Restored currentTime', lastTime);
}
lastTimes.set(video, video.currentTime);
// 如果页面脚本暂停了视频,恢复
if (video.paused && !video.ended && video.currentTime > 0 && video.currentTime < video.duration - 1) {
if (Date.now() - lastUserInteraction > 500) {
video.play().catch(() => {});
console.log('JavTrailers: Resumed video');
}
}
});
}, 500);
}
// 初始执行静音和全屏
muteVideos();
// 延迟执行全屏,等待视频加载
setTimeout(() => {
hookVideoMethods();
webFullscreenVideo();
preventAutoPause();
keepVideoAlive();
}, 1000);
// 监听新视频元素的添加
const videoObserver = new MutationObserver(() => {
muteVideos();
setTimeout(() => {
webFullscreenVideo();
preventAutoPause();
}, 500);
});
videoObserver.observe(document.body, { childList: true, subtree: true });
// --- JavTrailers 自动重定向逻辑---
// 如 SNOS-001,依次尝试 snos00001, 1snos00001, 118snos00001,如果都不行则进入搜索页
if (document.title.includes('Page not found') || document.title.includes('404')) {
const currentUrl = location.href;
const pathParts = location.pathname.split('/');
let vid = pathParts[pathParts.length - 1];
if (!vid) return;
console.log('JavTrailers 404 detected for:', vid);
const retryKey = 'javtrailers_retry_chain';
const retryChain = JSON.parse(sessionStorage.getItem(retryKey) || '[]');
if (!retryChain.includes(vid)) {
retryChain.push(vid);
sessionStorage.setItem(retryKey, JSON.stringify(retryChain));
}
let newVid = null;
let attempt = 0;
// 判断当前是第几次尝试
if (vid.match(/^[a-z]+\d{5}$/i)) {
attempt = 1; // snos00001
newVid = '1' + vid;
}
else if (vid.startsWith('1') && vid.match(/^1[a-z]+\d{5}$/i)) {
attempt = 2; // 1snos00001
const withoutOne = vid.substring(1);
newVid = '118' + withoutOne; // 118snos00001
}
else if (vid.startsWith('118') && vid.match(/^118[a-z]+\d{5}$/i)) {
attempt = 3; // 118snos00001
}
if (newVid && attempt < 3 && !retryChain.includes(newVid)) {
// 第1、2、3次重试
const newUrl = currentUrl.replace('/' + vid, '/' + newVid);
console.log(`Retry ${attempt}/3: ${vid} → ${newVid}`);
location.replace(newUrl);
}
else {
// 三次都失败 → 跳转搜索页
const searchMatch = vid.replace(/^\d+/, '').match(/^([a-z]+)0*(\d+)$/i);
let searchId = searchMatch
? `${searchMatch[1].toLowerCase()}-${searchMatch[2].padStart(3, '0')}`
: vid.replace(/^\d+/, '').replace(/(\d+)$/, '-$1');
const searchUrl = `https://javtrailers.com/search/${searchId}`;
console.log(`All 3 attempts failed. Jumping to search: ${searchUrl}`);
sessionStorage.removeItem(retryKey);
location.replace(searchUrl);
}
} else {
sessionStorage.removeItem('javtrailers_retry_chain');
}
return;
}
// --- JavDB 页面按钮插入逻辑---
function formatId(rawId) {
const match = rawId.match(/^([A-Za-z]+)-?(\d+)$/);
if (match) {
const prefix = match[1].toLowerCase();
const number = match[2].padStart(5, '0');
return prefix + number;
}
return rawId.replace('-', '').toLowerCase().trim();
}
function createJumpButton(id, isMini = false) {
const formattedId = formatId(id);
const url = `https://javtrailers.com/video/${formattedId}`;
let btn;
if (isMini) {
btn = document.createElement('span');
btn.innerHTML = '🎬 预告片';
btn.style.cssText = 'margin-left:8px; padding:0 4px; font-size:11px; background-color:#3e8ed0; color:#fff; border-radius:3px; cursor:pointer; display:inline-block; vertical-align:middle;';
btn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
window.open(url, '_blank');
});
} else {
btn = document.createElement('a');
btn.href = url;
btn.target = '_blank';
btn.className = 'button is-info is-outlined is-small';
btn.innerHTML = `<span class="icon is-small"><i class="icon-play-circle"></i></span><span>預告片</span>`;
btn.style.marginLeft = '5px';
btn.style.height = '2.25em';
}
return btn;
}
function handlePage() {
// 详情页:直接定位番号所在的 panel-block,避免依赖 label 文本和 pathname
const detailBlocks = document.querySelectorAll('.video-detail .panel-block.first-block:not([data-jt-done])');
detailBlocks.forEach(block => {
const valueSpan = block.querySelector('.value');
if (!valueSpan) return;
const id = valueSpan.textContent.trim();
if (!id) return;
const copyBtn = block.querySelector('.copy-to-clipboard');
const anchor = copyBtn || valueSpan;
if (block.querySelector('.jt-jump-button')) return;
const btn = createJumpButton(id, false);
btn.classList.add('jt-jump-button');
anchor.after(btn);
block.dataset.jtDone = 'true';
});
// 列表页
const items = document.querySelectorAll('.item');
items.forEach(item => {
const scoreEl = item.querySelector('.score');
const idEl = item.querySelector('.video-title strong');
if (scoreEl && idEl && !scoreEl.dataset.jumpAdded) {
const id = idEl.innerText.trim();
scoreEl.appendChild(createJumpButton(id, true));
scoreEl.dataset.jumpAdded = "true";
}
});
}
handlePage();
let debounceTimer;
const observer = new MutationObserver(() => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(handlePage, 100);
});
observer.observe(document.body, { childList: true, subtree: true });
})();