JavDB to JavTrailers

JavDB 跳转 JavTrailers; From JavDB jump to JavTrailers.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

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

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

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

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

// ==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 });

})();