抖音体验增强

自动跳过直播、屏蔽账号关键字、跳过广告并自动设置最高分辨率,均可通过界面按钮控制开启/关闭

// ==UserScript==
// @name 抖音体验增强
// @namespace Violentmonkey Scripts
// @match https://www.douyin.com/?*
// @match *://*.douyin.com/*
// @match *://*.iesdouyin.com/*
// @exclude *://lf-zt.douyin.com*
// @grant none
// @version 1.4
// @description 自动跳过直播、屏蔽账号关键字、跳过广告并自动设置最高分辨率,均可通过界面按钮控制开启/关闭
// @author Frequenk
// @license GPL-3.0 License
// @run-at document-start
// ==/UserScript==

/*
功能说明(每次对功能进行修改后,请更新此说明和description、version):
1. 跳过直播功能
    - 自动检测当前视频是否为直播
    - 如果是直播,自动按下向下键切换到下一个视频
    - 可通过界面按钮开启/关闭此功能

2. 自动最高分辨率功能
    - 自动检测视频播放器的分辨率选项
    - 按优先级关键词顺序选择:4K > 2K > 1080P > 720P > 540P > 智能 (例如,"1080P 高清" 会匹配 "1080P")
    - 找到4K分辨率后会自动关闭此功能
    - 可通过界面按钮开启/关闭此功能

3. 屏蔽账号关键字功能
    - 自动检测当前视频的账号名称
    - 如果账号名称包含预设的关键字,自动按下向下键切换到下一个视频
    - 可通过界面按钮开启/关闭此功能
    - 屏蔽关键字数组可在代码顶部 `blockedAccountKeywords` 变量中配置

4. 自动跳过广告功能
    - 自动检测当前视频是否为广告
    - 如果是广告,自动按下向下键切换到下一个视频
    - 可通过界面按钮开启/关闭此功能

5. 界面控制
    - 在视频播放器设置面板中添加四个开关按钮
    - 如果页面存在多个视频播放器,则会在每个播放器的设置面板中插入这些按钮
    - 实时显示功能开启/关闭状态
    - 支持动态切换功能状态

6. 兼容性
    - 支持抖音主站及子域名
    - 自动适配页面变化
    - 使用原生JavaScript,无需额外依赖
*/

(function() {
    'use strict';

    // --- 配置区域 ---
    let skipLiveEnabled = true;
    let autoHighResolutionEnabled = true;
    let blockAccountByKeywordEnabled = true;
    let skipAdEnabled = true;
    
    const priorityOrder = ["4K", "2K", "1080P", "720P", "540P", "智能"];
    const blockedAccountKeywords = ["店", "甄选"];
    
    const SELECTORS = {
        activeVideo: "[data-e2e='feed-active-video']",
        resolutionOptions: ".xgplayer-playing div.virtual > div.item",
        accountName: '[data-e2e="feed-video-nickname"]',
        settingsPanel: 'xg-icon.xgplayer-autoplay-setting',
        adIndicator: 'svg[viewBox="0 0 30 16"]'
    };

    // --- UI 渲染 ---
    function updateToggleButtons(className, isEnabled) {
        document.querySelectorAll(`.${className} .xg-switch`).forEach(sw => {
            sw.classList.toggle('xg-switch-checked', isEnabled);
            sw.setAttribute('aria-checked', String(isEnabled));
        });
    }

    function createToggleButton(text, className, isEnabled, onToggle) {
        const btnContainer = document.createElement('xg-icon');
        btnContainer.className = `xgplayer-autoplay-setting ${className}`;
        btnContainer.innerHTML = `
            <div class="xgplayer-icon">
                <div class="xgplayer-setting-label">
                    <button aria-checked="${isEnabled}" class="xg-switch ${isEnabled ? 'xg-switch-checked' : ''}">
                        <span class="xg-switch-inner"></span>
                    </button>
                    <span class="xgplayer-setting-title">${text}</span>
                </div>
            </div>`;
        btnContainer.querySelector('button').addEventListener('click', (e) => {
            const newState = e.currentTarget.getAttribute('aria-checked') === 'false';
            updateToggleButtons(className, newState);
            onToggle(newState);
        });
        return btnContainer;
    }

    const buttonConfigs = [
        { text: '跳过直播', className: 'skip-live-button', get: () => skipLiveEnabled, set: state => skipLiveEnabled = state },
        { text: '寻找最高分辨率', className: 'auto-high-resolution-button', get: () => autoHighResolutionEnabled, set: state => autoHighResolutionEnabled = state },
        { text: '屏蔽账号关键字', className: 'block-account-keyword-button', get: () => blockAccountByKeywordEnabled, set: state => blockAccountByKeywordEnabled = state },
        { text: '跳过广告', className: 'skip-ad-button', get: () => skipAdEnabled, set: state => skipAdEnabled = state }
    ];

    function insertButtons() {
        document.querySelectorAll(SELECTORS.settingsPanel).forEach(panel => {
            const parent = panel.parentNode;
            if (!parent) return;
            let lastInjectedButton = panel;
            buttonConfigs.forEach(config => {
                let button = parent.querySelector(`.${config.className}`);
                if (!button) {
                    button = createToggleButton(config.text, config.className, config.get(), config.set);
                    parent.insertBefore(button, lastInjectedButton.nextSibling);
                }
                lastInjectedButton = button;
            });
        });
    }

    // --- 核心功能 ---
    function skipVideo() {
        document.dispatchEvent(new KeyboardEvent('keydown', { keyCode: 40, bubbles: true }));
    }

    function mainLoop() {
        insertButtons();
        const activeVideo = document.querySelector(SELECTORS.activeVideo);

        if (!activeVideo) {
            if (skipLiveEnabled) skipVideo();
            return;
        }

        if (skipAdEnabled) {
            const adIndicator = activeVideo.querySelector(SELECTORS.adIndicator);
            if (adIndicator) {
                console.log("检测到广告,已跳过");
                skipVideo();
                return;
            }
        }

        if (blockAccountByKeywordEnabled) {
            const accountNameEl = activeVideo.querySelector(SELECTORS.accountName);
            const accountName = accountNameEl?.textContent.trim();
            if (accountName && blockedAccountKeywords.some(keyword => accountName.includes(keyword))) {
                console.log(`检测到屏蔽关键字,已跳过账号: ${accountName}`);
                skipVideo();
                return;
            }
        }

        if (autoHighResolutionEnabled) {
            const options = Array.from(activeVideo.querySelectorAll(SELECTORS.resolutionOptions))
                .map(el => {
                    const text = el.textContent.trim().toUpperCase();
                    return { element: el, text, priority: priorityOrder.findIndex(p => text.includes(p)) };
                })
                .filter(opt => opt.priority !== -1)
                .sort((a, b) => a.priority - b.priority);

            if (options.length > 0 && !options[0].element.classList.contains("selected")) {
                const bestOption = options[0];
                bestOption.element.click();
                console.log(`已切换至最高分辨率: ${bestOption.element.textContent}`);
                
                if (bestOption.text.includes("4K")) {
                    autoHighResolutionEnabled = false;
                    updateToggleButtons('auto-high-resolution-button', false);
                    console.log("已找到4K分辨率,自动关闭寻找最高分辨率功能");
                }
            }
        }
    }

    setInterval(mainLoop, 300);
})();