Bilibili 广告标签添加器

在哔哩哔哩视频页面添加Ad标记

// ==UserScript==
// @name         Bilibili 广告标签添加器
// @namespace    http://tampermonkey.net/
// @version      0.5
// @description  在哔哩哔哩视频页面添加Ad标记
// @author       luzhuheng
// @match        *://*.bilibili.com/*
// @icon         https://www.bilibili.com/favicon.ico
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @run-at       document-idle
// @license MIT
// ==/UserScript==

(function() {
    'use strict';
    
    // 极简配置
    const config = {
        debug: true,  // 开启调试模式,方便排查问题
        serverUrl: GM_getValue('serverUrl', 'https://bilibili-ad-tag.fly.dev')  // 默认服务器地址
    };
    
    // 创建设置面板
    function createSettingsPanel() {
        // 检查是否已存在设置面板
        if (document.getElementById('bilibili-ad-settings')) {
            return;
        }
        
        // 创建设置面板容器
        const settingsPanel = document.createElement('div');
        settingsPanel.id = 'bilibili-ad-settings';
        settingsPanel.style.position = 'fixed';
        settingsPanel.style.top = '50%';
        settingsPanel.style.left = '50%';
        settingsPanel.style.transform = 'translate(-50%, -50%)';
        settingsPanel.style.backgroundColor = '#fff';
        settingsPanel.style.border = '1px solid #ccc';
        settingsPanel.style.borderRadius = '8px';
        settingsPanel.style.padding = '20px';
        settingsPanel.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.2)';
        settingsPanel.style.zIndex = '10000';
        settingsPanel.style.minWidth = '300px';
        
        // 创建标题
        const title = document.createElement('h3');
        title.textContent = 'Bilibili Ad标签设置';
        title.style.margin = '0 0 15px 0';
        title.style.color = '#fb7299';
        title.style.borderBottom = '1px solid #eee';
        title.style.paddingBottom = '10px';
        
        // 创建服务器地址输入框
        const inputContainer = document.createElement('div');
        inputContainer.style.marginBottom = '15px';
        
        const inputLabel = document.createElement('label');
        inputLabel.textContent = '服务器地址:';
        inputLabel.style.display = 'block';
        inputLabel.style.marginBottom = '5px';
        inputLabel.style.fontWeight = 'bold';
        
        const currentUrl = GM_getValue('serverUrl', 'https://bilibili-ad-tag.fly.dev');
        const input = document.createElement('input');
        input.type = 'text';
        input.value = currentUrl;
        input.style.width = '100%';
        input.style.padding = '8px';
        input.style.boxSizing = 'border-box';
        input.style.border = '1px solid #ddd';
        input.style.borderRadius = '4px';
        
        inputContainer.appendChild(inputLabel);
        inputContainer.appendChild(input);
        
        // 创建按钮容器
        const buttonContainer = document.createElement('div');
        buttonContainer.style.display = 'flex';
        buttonContainer.style.justifyContent = 'space-between';
        
        // 保存按钮
        const saveButton = document.createElement('button');
        saveButton.textContent = '保存';
        saveButton.style.padding = '8px 15px';
        saveButton.style.backgroundColor = '#fb7299';
        saveButton.style.color = 'white';
        saveButton.style.border = 'none';
        saveButton.style.borderRadius = '4px';
        saveButton.style.cursor = 'pointer';
        
        // 重置按钮
        const resetButton = document.createElement('button');
        resetButton.textContent = '重置为默认';
        resetButton.style.padding = '8px 15px';
        resetButton.style.backgroundColor = '#f0f0f0';
        resetButton.style.color = '#666';
        resetButton.style.border = '1px solid #ddd';
        resetButton.style.borderRadius = '4px';
        resetButton.style.cursor = 'pointer';
        
        // 取消按钮
        const cancelButton = document.createElement('button');
        cancelButton.textContent = '取消';
        cancelButton.style.padding = '8px 15px';
        cancelButton.style.backgroundColor = '#f0f0f0';
        cancelButton.style.color = '#666';
        cancelButton.style.border = '1px solid #ddd';
        cancelButton.style.borderRadius = '4px';
        cancelButton.style.cursor = 'pointer';
        
        buttonContainer.appendChild(saveButton);
        buttonContainer.appendChild(resetButton);
        buttonContainer.appendChild(cancelButton);
        
        // 添加事件监听器
        saveButton.addEventListener('click', function() {
            const newUrl = input.value.trim();
            if (newUrl !== '') {
                GM_setValue('serverUrl', newUrl);
                config.serverUrl = newUrl; // 立即更新当前配置
                showMessage('服务器地址已更新为: ' + newUrl);
                document.body.removeChild(settingsPanel);
            } else {
                showMessage('服务器地址不能为空', 'error');
            }
        });
        
        resetButton.addEventListener('click', function() {
            const defaultUrl = 'http://8.138.20.3:8967';
            input.value = defaultUrl;
            GM_setValue('serverUrl', defaultUrl);
            config.serverUrl = defaultUrl; // 立即更新当前配置
            showMessage('服务器地址已重置为默认值');
        });
        
        cancelButton.addEventListener('click', function() {
            document.body.removeChild(settingsPanel);
        });
        
        // 组装面板
        settingsPanel.appendChild(title);
        settingsPanel.appendChild(inputContainer);
        settingsPanel.appendChild(buttonContainer);
        
        // 添加到页面
        document.body.appendChild(settingsPanel);
    }
    
    // 显示消息提示
    function showMessage(text, type = 'success') {
        const message = document.createElement('div');
        message.textContent = text;
        message.style.position = 'fixed';
        message.style.top = '10%';
        message.style.left = '50%';
        message.style.transform = 'translate(-50%, -50%)';
        message.style.padding = '10px 20px';
        message.style.backgroundColor = type === 'success' ? 'rgba(0, 0, 0, 0.7)' : 'rgba(255, 0, 0, 0.7)';
        message.style.color = 'white';
        message.style.borderRadius = '4px';
        message.style.zIndex = '10000';
        document.body.appendChild(message);
        
        setTimeout(() => {
            document.body.removeChild(message);
        }, 2000);
    }
    
    // 注册(不可用)设置菜单
    GM_registerMenuCommand('设置服务器地址', createSettingsPanel);
    
    // 注册(不可用)重置菜单
    GM_registerMenuCommand('重置为默认服务器', function() {
        const defaultUrl = 'https://bilibili-ad-tag.fly.dev';
        GM_setValue('serverUrl', defaultUrl);
        config.serverUrl = defaultUrl;
        showMessage('服务器地址已重置为默认值');
    });
    
    
    // 日志函数
    function log(message) {
        if (config.debug) {
            console.log(`[Bilibili AD标签] ${message}`);
        }
    }

    // 检查当前页面是否为视频页面
    function isVideoPage() {
        return /\/video\/[BbAa][Vv][0-9a-zA-Z]+/.test(window.location.href);
    }
    
    // 从URL中提取视频ID
    function getVideoId() {
        const match = window.location.href.match(/\/video\/([BbAa][Vv][0-9a-zA-Z]+)/);
        return match ? match[1] : null;
    }
    
    // 发送广告标记到服务器
    function sendAdTagToServer(videoId) {
        // 服务器API地址
        const apiUrl = `${config.serverUrl}/api/tag/ad`;
        
        // 创建请求数据
        const data = {
            video_id: videoId
        };
        
        // 发送POST请求
        fetch(apiUrl, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(data)
        })
        .then(response => response.json())
        .then(result => {
            if (config.debug) {
                console.log(`[Bilibili Ad标签] 服务器响应:`, result);
            }
        })
        .catch(error => {
            if (config.debug) {
                console.error(`[Bilibili Ad标签] 发送请求失败:`, error);
            }
        });
    }
    
    // 添加广告标记工具
    function addAdTool(isAd) {
        try {
            // 检查是否已添加广告标记工具
            if (document.getElementById('bilibili-ad-tool')) {
                return;
            }
            
            // 查找视频工具栏
            const videoTool = document.querySelector('.video-tool-more-dropdown');
            
            if (!videoTool) {
                log('未找到视频工具栏元素');
                return;
            }
            
            // 获取视频ID
            const videoId = getVideoId();
            if (!videoId) {
                log('无法获取视频ID');
                return;
            }
            
            // 创建广告标记工具
            const adTool = document.createElement('div');
            adTool.id = 'bilibili-ad-tool';
            adTool.className = 'video-toolbar-right-item dropdown-item';
            
            // 创建图标容器
            const iconContainer = document.createElement('div');
            iconContainer.className = 'video-note-icon video-toolbar-item-icon';
            
            // 添加SVG图标
            iconContainer.innerHTML = `<svg t="1743880648034" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2719" width="24" height="24">
                <path d="M992 160v576H32V160h960m0-32H32c-17.6 0-32 14.4-32 32v576c0 17.6 14.4 32 32 32h960c17.6 0 32-14.4 32-32V160c0-17.6-14.4-32-32-32z" fill="" p-id="2720"></path>
                <path d="M112 880h800c9.6 0 16 6.4 16 16s-6.4 16-16 16H112c-9.6 0-16-6.4-16-16s6.4-16 16-16z" fill="" p-id="2721"></path>
                <path d="M334.4 275.2l171.2 382.4h-40l-57.6-124.8h-158.4L192 657.6H152l172.8-382.4h9.6z m-4.8 81.6l-62.4 137.6h124.8l-62.4-137.6zM563.2 657.6v-368H640c52.8 0 91.2 3.2 115.2 11.2 24 8 44.8 19.2 64 36.8 17.6 16 32 36.8 41.6 60.8 9.6 24 14.4 51.2 14.4 83.2s-8 62.4-22.4 89.6-35.2 49.6-60.8 64c-25.6 14.4-62.4 20.8-110.4 20.8h-118.4z m35.2-35.2H640c44.8 0 76.8-1.6 96-6.4s36.8-12.8 52.8-25.6c14.4-12.8 27.2-28.8 33.6-48 8-19.2 11.2-40 11.2-64s-4.8-46.4-12.8-67.2c-9.6-20.8-22.4-36.8-38.4-51.2s-36.8-22.4-59.2-27.2c-22.4-4.8-56-6.4-100.8-6.4h-25.6l1.6 296z" fill="" p-id="2722"></path>
            </svg>`;
            
            // 创建文本容器
            const textContainer = document.createElement('span');
            textContainer.className = 'video-note-info video-toolbar-item-text';
            textContainer.textContent = isAd ? '申请移除' : '广告标记';
            
            // 组装工具
            adTool.appendChild(iconContainer);
            adTool.appendChild(textContainer);

            // 添加点击事件
            adTool.addEventListener('click', function() {
                if (isAd) {
                    // 发送删除申请到服务器
                    fetch(`${config.serverUrl}/api/tag/remove-request`, {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json'
                        },
                        body: JSON.stringify({ video_id: videoId })
                    })
                    .then(response => response.json())
                    .then(result => {
                        if (result.success) {
                            const message = document.createElement('div');
                            message.textContent = '已申请移除广告标记,请等待审核';
                            message.style.position = 'fixed';
                            message.style.top = '50%';
                            message.style.left = '50%';
                            message.style.transform = 'translate(-50%, -50%)';
                            message.style.padding = '10px 20px';
                            message.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
                            message.style.color = 'white';
                            message.style.borderRadius = '4px';
                            message.style.zIndex = '10000';
                            document.body.appendChild(message);
                            
                            setTimeout(() => {
                                document.body.removeChild(message);
                            }, 1000);
                        }
                    });
                } else {
                    // 发送到服务器
                    sendAdTagToServer(videoId);
                    
                    // 提示用户
                    const message = document.createElement('div');
                    message.textContent = '已标记为广告';
                    message.style.position = 'fixed';
                    message.style.top = '50%';
                    message.style.left = '50%';
                    message.style.transform = 'translate(-50%, -50%)';
                    message.style.padding = '10px 20px';
                    message.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
                    message.style.color = 'white';
                    message.style.borderRadius = '4px';
                    message.style.zIndex = '10000';
                    document.body.appendChild(message);
                    
                    setTimeout(() => {
                        document.body.removeChild(message);
                    }, 1000);
                }
            });
            
            // 将广告标记工具添加到工具栏
            videoTool.appendChild(adTool);
            
            log('已添加广告标记工具');
        } catch (error) {
            // 记录详细错误信息
            log('添加广告标记工具失败: ' + error.message);
        }
    }

    // 在detailContent元素后添加test标签
    function addAdTag(isAd) {        
        try {
            // 检查是否已添加ad标签
            if (document.getElementById('bilibili-ad-tag')) {
                return;
            }
            
            if (isAd) {
                    // 查找detailContent元素
                    const detailContent = document.querySelector('.video-info-detail-content');
                    
                    if (!detailContent) {
                        log('未找到detailContent元素');
                        return;
                    }
                    
                    // 创建ad标签容器
                    const adTag = document.createElement('div');
                    adTag.id = 'bilibili-ad-tag';
                    adTag.className = 'view item'; // 使用与其他项目相同的类名
                    
                    // 创建文本容器
                    const textContainer = document.createElement('div');
                    textContainer.className = 'view-text';
                    textContainer.textContent = 'AD';

                    adTag.appendChild(textContainer);
                    
                    // 将AD标签添加到detailContent元素中
                    detailContent.appendChild(adTag);

                    // 提示用户
                    const adMessage = document.createElement('div');
                    adMessage.textContent = '该视频被标记为广告';
                    adMessage.style.position = 'fixed';
                    adMessage.style.top = '10%';
                    adMessage.style.left = '50%';
                    adMessage.style.transform = 'translate(-50%, -50%)';
                    adMessage.style.padding = '10px 20px';
                    adMessage.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
                    adMessage.style.color = 'white';
                    adMessage.style.borderRadius = '4px';
                    adMessage.style.zIndex = '10000';
                    document.body.appendChild(adMessage);

                    setTimeout(() => {
                        document.body.removeChild(adMessage);
                    }, 2000);
                }
            } catch (error) {
                // 静默失败,不影响页面功能
                log('添加AD标签失败: ' + error.message);
            }
    }
    
    // 初始化视频页面功能
    function initVideoPageFeatures() {
        // 确保视频播放器已加载
        if (!document.querySelector('video') && !document.querySelector('.bpx-player')) {
            // 如果播放器还没加载,再等待一段时间
            setTimeout(initVideoPageFeatures, 1000);
            return;
        }
        
        // 添加Ad标签并获取是否为广告
        let isAd = false;
        const videoId = getVideoId();
        if (videoId) {
            fetch(`${config.serverUrl}/api/tag/check?video_id=${videoId}`)
                .then(response => response.json())
                .then(result => {
                    isAd = result.success && result.data.isAd;
                    addAdTag(isAd);
                    addAdTool(isAd);
                })
                .catch(error => {
                    if (config.debug) {
                        console.error('查询广告标记失败:', error);
                    }
                    addAdTag(false);
                    addAdTool(false);
                });
        } else {
            addAdTag(false);
            addAdTool(false);
        }
    }
    
    // 处理页面变化
    function handlePageChange() {
        // 检查是否为视频页面
        if (isVideoPage()) {
            // 初始化视频页面功能
            initVideoPageFeatures();
        }
    }
    
    // 使用MutationObserver监听页面变化
    function setupMutationObserver() {
        // 创建一个观察器实例
        const observer = new MutationObserver((mutations) => {
            // 检查URL是否变化(哔哩哔哩是SPA应用)
            const currentUrl = window.location.href;
            if (currentUrl !== lastUrl) {
                lastUrl = currentUrl;
                handlePageChange();
            }
        });
        
        // 配置观察选项
        const config = { childList: true, subtree: true };
        
        // 开始观察
        observer.observe(document.body, config);
    }
    
    // 使用更安全的方式初始化
    function safeInitialize() {
        // 确保页面已完全加载
        if (document.readyState !== 'complete') {
            window.addEventListener('load', () => setTimeout(safeInitialize, 1000));
            return;
        }
        
        // 初始化时处理当前页面
        handlePageChange();
        
        // 设置页面变化监听
        setupMutationObserver();
        
        // 监听URL变化(处理浏览器前进后退)
        window.addEventListener('popstate', handlePageChange);
    }
    
    // 存储上一次URL,用于检测变化
    let lastUrl = window.location.href;
    
    // 延迟初始化,确保不干扰页面加载
    setTimeout(safeInitialize, 3000);
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址