隐藏b站视频详情页右侧的"活动推广"和"大家围观的直播"以及首页广告

Hide specified Bilibili elements using MutationObserver

Version vom 21.12.2024. Aktuellste Version

// ==UserScript==
// @name         隐藏b站视频详情页右侧的"活动推广"和"大家围观的直播"以及首页广告
// @name:en      Hide promotions on Bilibili's video details page and homepage
// @namespace    http://tampermonkey.net/
// @version      0.1.25
// @description  Hide specified Bilibili elements using MutationObserver
// @description:en  Hide specified Bilibili elements using MutationObserver
// @author       aspen138
// @match        *://www.bilibili.com/video/*
// @match        *://www.bilibili.com/*
// @match        *://www.bilibili.com
// @match        *://search.bilibili.com/*
// @icon         https://www.bilibili.com/favicon.ico
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_notification
// @grant        GM_info
// @grant        window.onurlchange
// @license      MIT
// @sandbox      JavaScript
// ==/UserScript==



// ↓↓↓↓↓↓↓↓↓模板,建议直接复制 //

// 自定义 urlchange 事件(用来监听 URL 变化)
        function addUrlChangeEvent() {
            history.pushState = ( f => function pushState(){
                var ret = f.apply(this, arguments);
                window.dispatchEvent(new Event('pushstate'));
                window.dispatchEvent(new Event('urlchange'));
                return ret;
            })(history.pushState);

            history.replaceState = ( f => function replaceState(){
                var ret = f.apply(this, arguments);
                window.dispatchEvent(new Event('replacestate'));
                window.dispatchEvent(new Event('urlchange'));
                return ret;
            })(history.replaceState);

            window.addEventListener('popstate',()=>{
                window.dispatchEvent(new Event('urlchange'))
            });
        }


var menu_ALL = [
    ['menu_isEnableAppendCoverLink', '默认追加视频封面链接', '默认追加视频封面链接', true]
], menu_ID = [];
for (let i=0;i<menu_ALL.length;i++){ // 如果读取到的值为 null 就写入默认值
    if (GM_getValue(menu_ALL[i][0]) == null){GM_setValue(menu_ALL[i][0], menu_ALL[i][3])};
}


// 注册脚本菜单
function registerMenuCommand() {
    if (menu_ID.length >= menu_ALL.length){ // 如果菜单ID数组长度大于等于菜单数组长度,说明不是首次添加菜单,需要卸载所有脚本菜单
        for (let i=0;i<menu_ID.length;i++){
            GM_unregisterMenuCommand(menu_ID[i]);
        }
    }
    for (let i=0;i<menu_ALL.length;i++){ // 循环注册脚本菜单
        menu_ALL[i][3] = GM_getValue(menu_ALL[i][0]);
        menu_ID[i] = GM_registerMenuCommand(`${menu_ALL[i][3]?'✅':'❌'} ${menu_ALL[i][1]}`, function(){menu_switch(`${menu_ALL[i][3]}`,`${menu_ALL[i][0]}`,`${menu_ALL[i][2]}`)});
    }
}


// 菜单开关
function menu_switch(menu_status, Name, Tips) {
    if (menu_status == 'true'){
        GM_setValue(`${Name}`, false);
        GM_notification({text: `已关闭 [${Tips}] 功能\n(点击刷新网页后生效)`, timeout: 3500, onclick: function(){location.reload();}});
    }else{
        GM_setValue(`${Name}`, true);
        GM_notification({text: `已开启 [${Tips}] 功能\n(点击刷新网页后生效)`, timeout: 3500, onclick: function(){location.reload();}});
    }
    registerMenuCommand(); // 重新注册脚本菜单
};


// 返回菜单值
function menu_value(menuName) {
    for (let menu of menu_ALL) {
        if (menu[0] == menuName) {
            return menu[3]
        }
    }
}

for (let i=0;i<menu_ALL.length;i++){ // 如果读取到的值为 null 就写入默认值
    if (GM_getValue(menu_ALL[i][0]) == null){GM_setValue(menu_ALL[i][0], menu_ALL[i][3])};
}
registerMenuCommand();
if (window.onurlchange === undefined) {addUrlChangeEvent();} // Tampermonkey v4.11 版本添加的 onurlchange 事件 grant,可以监控 pjax 等网页的 URL 变化

// ↑↑↑↑↑↑↑↑↑↑↑↑模板,建议直接复制 //


function appendCoverLink() {
    let BVid = '';
    const match1 = location.pathname.match(/BV[^/]*/);
    if (match1) {
        BVid = match1[0];
    } else {
        // console.error("No match found");
    }

    // Select the parent container where the new element will be appended
    const parentContainer = document.querySelector(`.video-info-detail-list.video-info-detail-content:not(.${BVid}modified)`);

    if (!parentContainer) {
        console.warn('Parent container not found. Ensure the selector is correct.');
        return;
    }

    // Ensure the link is only appended once
    const existingCoverLink = parentContainer.querySelector(' .cover-link');
    if (existingCoverLink) {
        existingCoverLink.remove();
    }

    // Extract the cover image URL from the document head
    const imageMetaTag = document.head.querySelector('[itemprop="image"]');
    if (!imageMetaTag) {
        console.warn('Image meta tag with [itemprop="image"] not found.');
        return;
    }

    // Create the new container div with appropriate classes
    const coverItem = document.createElement('div');
    coverItem.classList.add('cover-item', 'item');

    // Create the SVG icon (optional)
    const coverIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    coverIcon.setAttribute('class', 'cover-icon');
    coverIcon.setAttribute('style', 'width:20px;height:20px;');
    coverIcon.setAttribute('viewBox', '0 0 20 20');
    coverIcon.setAttribute('width', '20');
    coverIcon.setAttribute('height', '20');

    // Example SVG path
    const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    path.setAttribute('d', 'M10 0 L20 20 L0 20 Z');
    path.setAttribute('fill', 'currentColor');
    coverIcon.appendChild(path);

    // Create the link element
    const coverLink = document.createElement('a');
    coverLink.setAttribute('target', '_blank');
    coverLink.setAttribute('rel', 'noopener noreferrer');
    coverLink.classList.add('cover-link');
    coverLink.setAttribute('title', 'You may need F5 refresh to get consistent cover');

    // Create the text node
    const linkText = document.createElement('span');
    linkText.classList.add('cover-text');
    linkText.textContent = 'Cover';

    // Assemble the link
    coverLink.appendChild(coverIcon);
    coverLink.appendChild(linkText);

    // Append the link to the new item container
    coverItem.appendChild(coverLink);

    setTimeout(() => {
        BVid = 'default';
        const match = location.pathname.match(/BV[^/]*/);
        if (match) {
            BVid = match[0];
        } else {
            // console.error("No match found");
        }
        // Mark this container as modified
        parentContainer.classList.add(BVid + 'modified');

        // Replace 'http' with 'https' if necessary
        const coverImgUrl =
            'https://' + imageMetaTag.getAttribute('content').replace('http', 'https').split('@')[0];
        console.log("coverImgUrl=", coverImgUrl);
        coverLink.setAttribute('href', coverImgUrl);

        // Ensure the link is only appended once
        const existingCoverLink = parentContainer.querySelector('.cover-link');
        if (existingCoverLink) {
            existingCoverLink.remove();
        }

        parentContainer.append(coverLink);
        console.log('Cover link appended successfully.');
    }, 2*1000);
}



(function () {
    'use strict';

    registerMenuCommand();

    if (window.location.hostname.includes('bilibili.com')) {
        const styleElement1 = document.createElement('style');
        styleElement1.textContent = `.login-tip, .vip-login, .vip-login-tip, .login-panel-popover { display: none !important; }`;
        document.head.appendChild(styleElement1);
    }


    if (document.cookie.includes('DedeUserID')) {
        //console.log("has loged in.");
        // add CSS to hide some elements
        const styleElement = document.createElement('style');
        styleElement.textContent = ` .desktop-download-tip { display: none !important; }`; //隐藏右下角的下载客户端的推广弹窗
        document.head.appendChild(styleElement);
    }
    else {
        //console.log("not loged in.");
        // add CSS to hide some elements
        const originAppendChild = Node.prototype.appendChild;
        Node.prototype.appendChild = function (childElement) {
            if (childElement.tagName === 'SCRIPT' && childElement.src.includes("login")) {
                console.log("src=", src);
                return null;
            }
            else {
                return originAppendChild.call(this, childElement);
            }
        }
    }

    // Select elements with href containing 'cm.bilibili.com/cm/api'
    var elements = document.querySelectorAll('a[href*="cm.bilibili.com/cm/api"]');
    elements.forEach(function(element) {
        // Find the closest parent with class 'bili-video-card'
        var parentCard = element.closest('.bili-video-card');
        if (parentCard) {
            // Store original height to maintain layout
            var originalHeight = parentCard.children[0].children[0].offsetHeight;

            // Create replacement message div
            var messageDiv = document.createElement('div');
            messageDiv.style.cssText = `
            background-color: #f0f0f0;
            color: #666;
            padding: 15px;
            text-align: center;
            font-size: 14px;
            height: ${originalHeight}px;
            display: flex;
            align-items: center;
            justify-content: center;
        `;
            messageDiv.textContent = "The AD content is hidden";

            // Replace content while keeping the card
            parentCard.innerHTML = '';
            parentCard.appendChild(messageDiv);
        }
    });

    // Enhanced function to thoroughly hide elements
    function hideElement(element) {
        if (!element) return;

        // Apply more aggressive hiding styles
        const hideStyles = {
            'display': 'none !important',
            'visibility': 'hidden !important',
            'opacity': '0 !important',
            'background': 'white !important',
            'color': 'white !important',
            'pointer-events': 'none !important',
            'height': '0 !important',
            'width': '0 !important',
            'overflow': 'hidden !important',
            'position': 'absolute !important',
            'z-index': '-9999 !important',
            'clip': 'rect(0, 0, 0, 0) !important'
        };

        // Apply styles using both direct style and cssText for maximum effectiveness
        Object.entries(hideStyles).forEach(([property, value]) => {
            element.style.setProperty(property, value.replace(' !important', ''), 'important');
        });

        // Hide all child elements recursively
        Array.from(element.children).forEach(child => {
            hideElement(child);
        });

        // Remove any inline event listeners
        element.onclick = null;
        element.onmouseover = null;
        element.onmouseenter = null;
        element.onmouseleave = null;
    }

    // Function to handle all target elements
    function hideAllTargetElements() {
        const targetElements = [
            '#slide_ad',
            '#right-bottom-banner',
            '.pop-live-small-mode.part-1',
            '.ad-floor-cover.b-img',
            '#bannerAd',
            '.vcd',
            'a[data-loc-id="4331"]',
            '#activity_vote',
            '.ad-report.video-card-ad-small',
            '.ad-report.ad-floor-exp',
            '.slide-ad-exp',
            '.activity-m-v1.act-now',
            '.video-page-special-card-small',
            '.btn-ad',
            'div[data-v-2ce37bb8].btn-ad',
            '.palette-button-adcard.is-bottom', // New element
            '.palette-button-adcard' // More specific selector for the new element
            //,'div[data-v-7b35db32].vip-login-tip'
        ];

        targetElements.forEach(selector => {
            const elements = document.querySelectorAll(selector);
            elements.forEach(hideElement);
        });
    }

    // Create a more specific MutationObserver
    const observer = new MutationObserver((mutations) => {
        mutations.forEach(mutation => {
            // Check for added nodes
            if (mutation.addedNodes.length) {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === 1) { // Element node
                        // Check if the added node is a target element
                        if (node.id === 'slide_ad' ||
                            node.classList.contains('slide-ad-exp') ||
                            node.classList.contains('ad-report') ||
                            node.classList.contains('activity-m-v1') ||
                            node.classList.contains('video-page-special-card-small') ||
                            node.classList.contains('btn-ad') ||
                            node.classList.contains('palette-button-adcard')) { // Added new class check
                            hideElement(node);
                        }
                        // Also check children of added nodes
                        const targetElements = node.querySelectorAll('#slide_ad, .slide-ad-exp, .ad-report, .activity-m-v1, .video-page-special-card-small, .btn-ad, .palette-button-adcard');
                        targetElements.forEach(hideElement);
                    }
                });
            }

            // Check for attribute changes
            if (mutation.type === 'attributes') {
                const element = mutation.target;
                if (element.id === 'slide_ad' ||
                    element.classList.contains('slide-ad-exp') ||
                    element.classList.contains('ad-report') ||
                    element.classList.contains('activity-m-v1') ||
                    element.classList.contains('video-page-special-card-small') ||
                    element.classList.contains('btn-ad') ||
                    element.classList.contains('palette-button-adcard')
                    // ||element.classList.contains('vip-login-tip')
                ) { // Added new class check
                    hideElement(element);
                }
            }
        });
    });

    // Configure the observer to watch for everything
    const observerConfig = {
        childList: true,
        subtree: true,
        attributes: true,
        attributeFilter: ['style', 'class']
    };

    // Initial hiding
    hideAllTargetElements();

    // Start observing
    observer.observe(document.body, observerConfig);

    // Set up periodic checks just in case
    const checkInterval = setInterval(hideAllTargetElements, 1000);

    // Cleanup after 30 seconds
    setTimeout(() => {
        clearInterval(checkInterval);
        observer.disconnect(); // Optionally disconnect the observer after cleanup
    }, 30*1000);

    // window.addEventListener('urlchange', function(){ //
    //     setTimeout(()=>{appendCoverLink();}, 1*1000);
    // })


     // Retrieve the current setting, default to false
    let isEnableAppendCoverLink = GM_getValue('menu_isEnableAppendCoverLink', false);
    if (isEnableAppendCoverLink){
        const checkIntervalAppendCoverLink = setInterval(()=>appendCoverLink(), 300);

        // Cleanup after 30 seconds
        setTimeout(() => {
            clearInterval(checkIntervalAppendCoverLink);
            observer.disconnect(); // Optionally disconnect the observer after cleanup
        }, 30*1000);
    }

})();