Sleazy Fork is available in English.

AV_AD_Block

missav 广告拦截与界面优化

// ==UserScript==
// @run-at       document-start
// @name         AV_AD_Block
// @description  missav 广告拦截与界面优化
// @icon         https://static.missav.com/img/favicon.png
// @namespace    loadingi.local
// @version      3.1
// @author       ch
// @match        https://missav.ai/*
// @match        https://missav.ws/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        unsafeWindow
// @grant        GM_xmlhttpRequest
// @compatible   chrome
// @compatible   firefox
// @compatible   edge
// @license      GPL-3.0-only
// ==/UserScript==

(function() {
    'use strict';

    // 统一管理所有选择器
    const SELECTORS = {
        // 样式相关选择器
        STYLES: {
            PROGRESS_BUTTONS: '.isolate.inline-flex.rounded-md.shadow-sm',
            PROGRESS_CONTROL: '.sm\\:hidden',
            LOOP_BUTTON: '.sm\\:ml-6 button',
            // INFO_TEXT: '.mb-1.text-secondary.break-all.line-clamp-2',
            // ASPECT_RATIO: '.aspect-w-16.aspect-h-9',
            PLAYER_CONTAINER: 'div.relative.-mx-4.sm\\:m-0.-mt-6',
            // VIDEO_WRAPPER: '.aspect-w-16.aspect-h-9.relative'
        },
        // 广告相关选择器
        ADS: {
            SCRIPTS: [
                "script[src*='app.1aad5686.js']",
                "script[src*='inpage.push.js']",
                "script[src*='hartattenuate.com']",
                "script[src*='ads']",
                "script[src*='pop']",
                "script[src*='banner']"
            ],
            ELEMENTS: [
                'div.sm\\:container.mx-auto.mb-5.px-4',
                'ul.mb-4.list-none.text-nord14.grid.grid-cols-2.gap-2',
                // class="relative ml-4" 无聊的东西 myavlive 之类的
                'div.relative.ml-4',
                'div.root--ujvuu', // 隐藏底部右下角浮窗广告
                'div.under_player',
                'div.space-y-5.mb-5',
                'div[class^="rootContent--"]',
                'div[class^="fixed right-2 bottom-2"]',
                'div[class^="space-y-6 mb-6"]',
                'div.space-y-2.mb-4.ml-4.list-disc.text-nord14',
                'div[id*="ads"]',
                'div[id*="banner"]',
                'div[class*="ads"]',
                'div[class*="banner"]',
                '.ad-container',
                '#ad-container'
            ],
            SCRIPT_PATTERNS: [
                'htmlAds',
                'popAds',
                'bannerAds',
                'adsConfig'
            ]
        },
        THEATER: {
            PLAYER_CONTAINER: 'body > div:nth-child(3) > div.sm\\:container.mx-auto.px-4.content-without-search.pb-12 > div > div.flex-1.order-first > div:nth-child(2) > div.relative.-mx-4.sm\\:m-0.-mt-6',
            PLAYER_WRAPPER: '.aspect-w-16.aspect-h-9',
            VIDEO_ELEMENT: 'video#player',
            PROGRESS: 'div.sm\\:hidden.flex.justify-between.-mx-4.px-4.pt-3.pb-1.bg-black',
            AB_LOOP: 'div.flex.items-center.flex-nowrap.leading-5',
            AB_LOOP_CONTROLS: '.theater-controls-abloop'
        }
    };

    // 添加统一的按钮样式常量
    const BUTTON_STYLES = {
        BASE: {
            backgroundColor: '#222',
            borderRadius: '15px',
            borderColor: 'black',
            borderWidth: '1px',
            color: 'burlywood',
            cursor: 'pointer',
            transition: 'all 0.3s ease',
            outline: 'none',
            minWidth: '80px',
            padding: '2px 4px',
            marginBottom: '10px',

        },
        HOVER: {
            backgroundColor: '#333'
        }
    };

    // 创建统一的按钮工厂函数
    function createStyledButton(text, onClick) {
        const button = document.createElement('button');
        Object.assign(button.style, BUTTON_STYLES.BASE);
        button.innerText = text;
        button.addEventListener('mouseover', () => Object.assign(button.style, BUTTON_STYLES.HOVER));
        button.addEventListener('mouseout', () => Object.assign(button.style, { backgroundColor: BUTTON_STYLES.BASE.backgroundColor }));
        button.addEventListener('click', onClick);
        return button;
    }

    // 统一的样式更新函数
    function updateStyles() {
        // 使用更高效的选择器
        const styleUpdates = [
            {
                selector: SELECTORS.STYLES.PROGRESS_BUTTONS,
                styles: { 
                    background: 'rgba(85, 35, 49, 0.37)'
                }
            },
            {
                selector: SELECTORS.STYLES.PROGRESS_CONTROL,
                styles: { 
                    display: 'flex',
                    visibility: 'visible',
                    opacity: '1'
                }
            },
            {
                selector: SELECTORS.STYLES.LOOP_BUTTON,
                styles: { borderWidth: '0px' }
            }
        ];

        styleUpdates.forEach(({selector, styles}) => {
            document.querySelectorAll(selector).forEach(el => {
                Object.assign(el.style, styles);
            });
        });

        // 设置背景
        document.body.style.backgroundColor = 'black';
    }

    // 优化的广告拦截函数
    function blockAds() {
        // 合并所有选择器为一个字符串
        const allSelectors = [
            ...SELECTORS.ADS.SCRIPTS,
            ...SELECTORS.ADS.ELEMENTS
        ].join(',');
        
        // 一次性查询所有需要删除的元素
        document.querySelectorAll(allSelectors).forEach(el => el?.remove());
        
        // 优化 iframe 移除
        const iframes = document.getElementsByTagName('iframe');
        Array.from(iframes).forEach(iframe => iframe.remove());
        
        // 优化脚本检查
        const scriptPattern = new RegExp(SELECTORS.ADS.SCRIPT_PATTERNS.join('|'));
        document.querySelectorAll('script').forEach(script => {
            if (scriptPattern.test(script.innerText)) {
                script.remove();
            }
        });
    }

    // 优化的播放器设置函数
    function setupPlayer() {
        // 移除所有带有 @click="pop()" 的元素的点击事件
        document.querySelectorAll('[\\@click="pop()"]').forEach(element => {
            element.removeAttribute('@click');
        });

        // 移除窗口失焦暂停
        const aspectElements = document.getElementsByClassName('aspect-w-16 aspect-h-9');
        if(aspectElements[11]) {
            aspectElements[11].removeAttribute('@click');
            aspectElements[11].removeAttribute('@keyup.space.window');
        }
    }

    // 更新影院模式样式
    function addTheaterModeStyles() {
        const style = document.createElement('style');
        style.textContent = `
            .theater-overlay {
                position: fixed;
                top: 0;
                left: 0;
                width: 100vw;
                height: 100vh;
                background: rgba(0, 0, 0, 0.95);
                z-index: 9998;
                display: none;
            }
            
            /* 修改播放器容器样式 */
            .theater-mode-container {
                position: fixed !important;
                top: 0 !important;
                left: 0 !important;
                width: 100vw !important;
                height: 100vh !important;
                transform: none !important;
                z-index: 9999 !important;
                margin: 0 !important;
                padding: 0 !important;
                display: flex !important;
                align-items: center !important;
                justify-content: center !important;
                background: transparent !important;
                pointer-events: auto !important;
            }
        
            
            /* 修改视频包装器样式 */
            .theater-mode-container .aspect-w-16.aspect-h-9 {
                position: relative !important;
                width: 100vw !important;
                max-width: none !important;
                height: 100vh !important;
                margin: 0 auto !important;
                pointer-events: auto !important;
            }
            
            /* 修改视频元素样式 */
            .theater-mode-container video {
                position: absolute !important;
                top: 0 !important;
                left: 0 !important;
                width: 100% !important;
                height: 100% !important;
                object-fit: contain !important;
                pointer-events: auto !important;
            }
            
            /* 确保所有父容器不限制尺寸和层级 */
            .theater-mode-container * {
                max-width: none !important;
                max-height: none !important;
                pointer-events: auto !important;
            }
            
            /* 修改播放器控制栏样式 */
            .theater-mode-container .plyr__controls {
                position: fixed !important;
                bottom: 0 !important;
                left: 0 !important;
                width: 100% !important;
                z-index: 10000 !important;
                // background: rgba(254, 98, 142, 0.27) !important;
                padding: 10px !important;
                opacity: 1 !important;
                visibility: visible !important;
                display: flex !important;
            }
            /* 降低导航栏层级*/ 
            .fixed.z-max.w-full.bg-gradient-to-b.from-darkest {
                z-index: 1 !important;
            }
            /* 影院模式下不显示导航栏*/ 
            .theater-mode-container .fixed.z-max.w-full.bg-gradient-to-b.from-darkest {
                display: none !important;
            }
            
            /* 确保时间显示可见 */
            .theater-mode-container .plyr__time {
                display: inline-block !important;
                color: white !important;
                opacity: 1 !important;
                visibility: visible !important;
            }
            
            /* 控制条样式 */
            .theater-controls-progress {
                position: fixed !important;
                bottom: 104px !important;
                z-index: 10000 !important;
                // background: rgba(254, 98, 142, 0.27) !important;
                background: transparent !important;
                padding: 10px !important;
                width: 100% !important;
                max-width: none !important;
                pointer-events: auto !important;
            }
            
            .theater-controls-abloop {
                position: fixed !important;
                bottom: 52px !important;
                left: 0px !important;
                width: 100% !important;
                z-index: 10000 !important;
                // background: rgba(254, 98, 142, 0.27) !important;
                padding: 10px !important;
                pointer-events: auto !important;
            }

             /* 添加音量控制器宽度设置 */
            .theater-mode-container .plyr__controls__item.plyr__volume {
                width: 40px !important;
            }

            /* 隐藏指定的控制按钮 */
            .theater-mode-container .plyr__controls__item[data-plyr="rewind"],
            .theater-mode-container .plyr__controls__item[data-plyr="fast-forward"],
            .theater-mode-container .plyr__control[data-plyr="settings"],
            .theater-mode-container .plyr__controls__item[data-plyr="pip"],
            .theater-mode-container .plyr__controls__item[data-plyr="fullscreen"] {
                display: none !important;
            }
        `;
        document.head.appendChild(style);
    }

    // 更新 toggleTheaterMode 函数
    function toggleTheaterMode() {
        const playerContainer = document.querySelector(SELECTORS.THEATER.PLAYER_CONTAINER);
        const progress = document.querySelector(SELECTORS.THEATER.PROGRESS);
        const abLoop = document.querySelector(SELECTORS.THEATER.AB_LOOP);
        
        // 获取或创建遮罩层
        let overlay = document.querySelector('.theater-overlay');
        if (!overlay) {
            overlay = document.createElement('div');
            overlay.className = 'theater-overlay';
            document.body.appendChild(overlay);
        }
        
        const isTheaterMode = overlay.style.display === 'none' || overlay.style.display === '';
        
        // 获取按钮并更新文本
        const theaterButton = document.querySelector('.theater-mode-button');
        const abLoopButton = document.querySelector('.ab-loop-button');
        
        if (theaterButton) {
            theaterButton.innerText = isTheaterMode ? '关闭影院' : '影院模式';
        }
        
        // 控制 AB 循环按钮的显示/隐藏
        if (abLoopButton) {
            abLoopButton.style.display = isTheaterMode ? 'block' : 'none';
        }
        
        if (isTheaterMode) {
            // 进入影院模式
            overlay.style.display = 'block';
            
            if (playerContainer) {
                playerContainer.classList.add('theater-mode-container');
                // 确保所有父容器都不限制尺寸和层级
                let parent = playerContainer.parentElement;
                while (parent && parent !== document.body) {
                    parent.style.setProperty('max-width', 'none', 'important');
                    parent.style.setProperty('max-height', 'none', 'important');
                    parent.style.setProperty('overflow', 'visible', 'important');
                    parent.style.setProperty('z-index', 'auto', 'important');
                    parent = parent.parentElement;
                }
            }
            
            if (progress) {
                progress.classList.add('theater-controls-progress');
            }
            
            if (abLoop) {
                abLoop.classList.add('theater-controls-abloop');
                // 默认隐藏 AB 循环控制栏
                abLoop.style.display = 'none';
            }
            
            document.addEventListener('keydown', handleEscKey);
        } else {
            // 退出影院模式
            overlay.style.display = 'none';
            
            if (playerContainer) {
                playerContainer.classList.remove('theater-mode-container');
                // 恢复父容器的原始样式
                let parent = playerContainer.parentElement;
                while (parent && parent !== document.body) {
                    parent.style.removeProperty('max-width');
                    parent.style.removeProperty('max-height');
                    parent.style.removeProperty('overflow');
                    parent.style.removeProperty('z-index');
                    parent = parent.parentElement;
                }
            }
            
            if (progress) {
                progress.classList.remove('theater-controls-progress');
            }
            
            if (abLoop) {
                abLoop.classList.remove('theater-controls-abloop');
            }
            
            document.removeEventListener('keydown', handleEscKey);
        }
    }

    // ESC键处理函数
    function handleEscKey(e) {
        if (e.key === 'Escape') {
            toggleTheaterMode();
        }
    }

    // 更新浮动按钮创建函数
    function createFloatingButtons() {
        const buttonContainer = document.createElement('div');
        Object.assign(buttonContainer.style, {
            position: 'fixed',
            top: '14px',
            right: '98px',
            zIndex: '10001',
            display: 'flex', 
            flexDirection: 'row',  // 改为水平排列
            gap: '10px'
        });

        // 创建影院模式按钮
        const theaterButton = createStyledButton('影院模式', toggleTheaterMode);
        theaterButton.className = 'theater-mode-button';
        buttonContainer.appendChild(theaterButton);
        
        // 创建 AB 循环按钮(初始文本为"显示AB循环")
        const abLoopButton = createStyledButton('A/B', toggleABLoopControls);
        abLoopButton.className = 'ab-loop-button';
        abLoopButton.style.display = 'none';  // 初始隐藏
        buttonContainer.appendChild(abLoopButton);
        
        document.body.appendChild(buttonContainer);
    }

    // 添加防抖函数
    function debounce(func, wait) {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }

    // 优化观察者
    function initObserver() {
        const debouncedUpdate = debounce(() => {
            blockAds();
            updateStyles();
            setupPlayer();
        }, 100);

        const observer = new MutationObserver(debouncedUpdate);
        
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    // 简化清理函数
    function cleanup() {
        document.removeEventListener('keydown', handleEscKey);
    }

    // 添加 AB 循环控制函数
    function toggleABLoopControls() {
        const abLoopControls = document.querySelector(SELECTORS.THEATER.AB_LOOP_CONTROLS);
        const abLoopButton = document.querySelector('.ab-loop-button');
        
        if (abLoopControls) {
            const isVisible = abLoopControls.style.display !== 'none';
            abLoopControls.style.display = isVisible ? 'none' : 'flex';
            
            // 更新按钮文本,默认显示"显示AB循环"
            if (abLoopButton) {
                abLoopButton.innerText = isVisible ? 'A/B' : 'no A/B';
            }
        }
    }

    // 主函数
    function init() {
        updateStyles();
        blockAds();
        setupPlayer();
        addTheaterModeStyles(); // 添加影院模式样式
        createFloatingButtons();
        initObserver();
        
    }

    // 当 DOM 加载完成后执行初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();