Bilibili Blacklist

不可逆的默认屏蔽首页广告,并在左下角添加了屏蔽词功能,并能自定义每个屏蔽词的范围(真的会有人需要这种自定义吗)

// ==UserScript==
// @name         Bilibili Blacklist
// @namespace    http://tampermonkey.net/
// @version      0.8.4
// @description  不可逆的默认屏蔽首页广告,并在左下角添加了屏蔽词功能,并能自定义每个屏蔽词的范围(真的会有人需要这种自定义吗)
// @author       Aporia
// @match        *://*.bilibili.com/*
// @icon         https://www.google.com/s2/favicons?domain=google.cn
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addElement
// @grant        GM_xmlhttpRequest
// @require      https://cdn.staticfile.org/jquery/3.4.1/jquery.min.js
// @run-at document-end
// @license MIT

// ==/UserScript==
(function () {
    // Define page types
    let pageTypes = ['searchPage', 'mainPage', 'leaderboard', 'timeLine', 'recommand', 'reply'];
    let pageTypesCN = { searchPage: '搜索页面', mainPage: '主页面', leaderboard: '排行榜', timeLine: '动态', recommand: '推荐', reply: '回复' };

    // Initialize blacklist with some default values
    let blacklist = GM_getValue('blacklist');
    if (blacklist === undefined) {
        blacklist = [{
            keyword: "此时一位萌新路过",
            isRegexp: false,
            searchPage: true,
            mainPage: true,
            leaderboard: true,
            timeLine: true,
            recommand: true,
            reply: true
        }];
        GM_setValue('blacklist', JSON.stringify(blacklist));
    } else {
        // Parse the stored JSON string back to a JavaScript object
        blacklist = JSON.parse(blacklist);
        // Upgrade script: add the 'isRegexp' property to all items in the blacklist
        for (let i = 0; i < blacklist.length; i++) {
            if (!('isRegexp' in blacklist[i])) {
                blacklist[i].isRegexp = false;
            }
        }

        // Save the updated blacklist back to GM storage
        GM_setValue('blacklist', JSON.stringify(blacklist));
    }



    let blockPageTypes = {
        searchPage: {
            urlIncludes: 'search.bilibili.com',
            matchPairs: [
                { matchSelector: "div.bili-video-card__info--right", parentSelector: 'div.col_3' },
                { matchSelector: "div.media-card-content", parentSelector: "div.media-card" },
            ],
            cssModifications: {}
        },
        mainPage: {
            urlIncludes: 'www.bilibili.com',
            matchPairs: [
                { matchSelector: "div.bili-video-card__info--right", parentSelector: "div.bili-video-card" },
                { matchSelector: "div.bili-video-card__info--right", parentSelector: "div.feed-card" },
                { matchSelector: "div.bili-video-card__info--right", parentSelector: "div.floor-single-card" },
            ],
            cssModifications: '.recommended-container_floor-aside .container>*:nth-of-type(n + 8) {margin-top: 0px !important;}'
        },
        leaderboard: {
            urlIncludes: 'www.bilibili.com/v/popular',
            matchPairs: [
                { matchSelector: "div.video-card__info", parentSelector: "div.video-card" },
            ],
        },
        timeLine: {
            urlIncludes: 't.bilibili.com',
            matchPairs: [
                { matchSelector: "div.bili-dyn-content", parentSelector: "div.bili-dyn-list__item" },
            ],
        },
        recommand: {
            urlIncludes: 'www.bilibili.com/video/BV',
            matchPairs: [
                { matchSelector: "div.info", parentSelector: "div.video-page-card-small" },
            ],
        },
        reply: {
            urlIncludes: 'www.bilibili.com/video/BV',
            matchPairs: [
                { matchSelector: "div.root-reply", parentSelector: "div.content-warp" },
                { matchSelector: "span.reply-content-container.sub-reply-content", parentSelector: "div.sub-reply-item" },
            ],
        },
    };

    let prepareRegex = function () {
        // Find the page info for the current URL
        let pageInfos = [];
        for (let pageType in blockPageTypes) {
            if (window.location.href.includes(blockPageTypes[pageType].urlIncludes)) {
                let pageInfo = blockPageTypes[pageType];
                pageInfo.name = pageType;
                pageInfos.push(pageInfo);
            }
        }

        let containsStrings = [];
        if (pageInfos.length > 0) {
            pageInfos.forEach(pageInfo => {
                // Filter the blacklist to get the keywords that should be active on this page
                let activeKeywords = blacklist.filter(entry => entry[pageInfo.name])
                    .map(entry => entry.isRegexp ? entry.keyword : entry.keyword.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'))
                    .join('|');

                if (activeKeywords) {
                    containsStrings.push({
                        pageInfo: pageInfo,
                        containsString: ":containsRegex('" + activeKeywords + "')"
                    });
                }
            });
            return containsStrings;
        };
    }

    let block_blacklist = function (prepArray) {
        // Block AD
        $("svg.bili-video-card__info--ad").parents("div.bili-video-card").hide();
        $("svg.bili-video-card__info--ad").parents("div.feed-card").hide();
        $("div.video-page-special-card-small").hide();

        if (prepArray.length > 0) {
            prepArray.forEach(prep => {
                //console.log(prep.pageInfo.name)

                // Now hide all matching elements on the page
                prep.pageInfo.matchPairs.forEach(pair => {
                    $(pair.matchSelector + prep.containsString).parents(pair.parentSelector).hide();
                });

                // Apply CSS modifications
                if (prep.pageInfo.cssModifications) {
                    $('<style>').prop('type', 'text/css').html(prep.pageInfo.cssModifications).appendTo('head');
                }
            });
        }
    }

    let reblock_blacklist = function () {
        let prepArray = prepareRegex();
        block_blacklist(prepArray);
    }

    let wrapper = $('<div>', {
        css: {
            position: 'fixed',
            bottom: '10px',
            left: '-90px',
            zIndex: 9999,
            display: 'flex',
            flexDirection: 'row',
            opacity: 0.05
        },
        mouseenter: function () {
            $(this).animate({ left: '0px', opacity: 1 }, 200);
        },
        mouseleave: function () {
            $(this).animate({ left: '-90px', opacity: 0.05 }, 200);
        }
    });

    // Create the floating "B" button
    let buttonB = $('<button>', {
        text: '屏蔽',
        css: {
            backgroundColor: 'gray',
            color: 'white',
            borderRadius: '8px',
            padding: '10px',
            border: '2px solid white'
        },
        click: function () {
            let keyword = prompt('输入屏蔽关键词:');
            if (keyword) {
                // Add the keyword with all pages selected by default
                blacklist.push({
                    keyword: keyword,
                    isRegexp: false,
                    searchPage: true,
                    mainPage: true,
                    leaderboard: true,
                    timeLine: true,
                    recommand: true,
                    reply: true
                });

                // Save the updated blacklist to GM storage
                GM_setValue('blacklist', JSON.stringify(blacklist));

                reblock_blacklist();
            }
        }
    });

    // Create the floating "E" button
    let buttonE = $('<button>', {
        text: '管理',
        css: {
            backgroundColor: 'gray',
            color: 'white',
            borderRadius: '8px',
            padding: '10px',
            border: '2px solid white'
        },
        click: function () {
            if ($('#modal').length > 0) {
                $('#modal').remove();
                return;
            }

            // Build a custom modal dialog
            let modal = $('<div>', {
                id: 'modal',
                css: {
                    position: 'fixed',
                    width: '700px',
                    height: '400px',
                    top: '50%',
                    left: '50%',
                    transform: 'translate(-50%, -50%)',
                    backgroundColor: '#fff',
                    padding: '20px',
                    zIndex: 9999,
                    overflowY: 'auto',
                    border: '2px solid #000'
                }
            });

            // Add title
            let title = $('<h2>', {
                text: '屏蔽词管理',
                css: {
                    textAlign: 'center',
                    margin: '20px 0'
                }
            });
            modal.append(title);


            // Add exit button
            let exitButton = $('<button>', {
                text: '❌',
                css: {
                    position: 'absolute',
                    top: '10px',
                    right: '10px'
                },
                click: function () {
                    modal.remove();
                }
            });

            modal.append(exitButton);

            // Add each keyword to the modal with a '➖' button and checkboxes for each page type
            blacklist.forEach(function (entry, index) {
                let keyword = $('<span>', {
                    text: entry.keyword,
                    css: {
                        display: 'inline-block',
                        width: '100px',
                        overflow: 'hidden',
                        textOverflow: 'ellipsis',
                        whiteSpace: 'nowrap',
                        verticalAlign: 'middle'
                    }
                });

                let item = $('<div>', {
                    css: {
                        marginBottom: '10px'
                    }
                });

                item.append(keyword);

                let regexpCheckbox = $('<input>', {
                    type: 'checkbox',
                    checked: entry.isRegexp,
                    change: function () {
                        // Update the blacklist when the checkbox is toggled
                        entry.isRegexp = this.checked;
                        GM_setValue('blacklist', JSON.stringify(blacklist));
                        reblock_blacklist();
                    }
                });

                let label = $('<label>', {
                    text: '正则表达式',
                    css: {
                        marginLeft: '10px'
                    }
                });

                label.prepend(regexpCheckbox);
                item.append(label);

                let removeButton = $('<button>', {
                    text: '➖',
                    css: {
                        marginLeft: '10px'
                    },
                    click: function () {
                        // Get the keyword of the current item
                        let currentKeyword = entry.keyword;

                        // Filter out the item with the current keyword
                        blacklist = blacklist.filter(function (item) {
                            return item.keyword !== currentKeyword;
                        });

                        // Save the updated blacklist to GM storage
                        GM_setValue('blacklist', JSON.stringify(blacklist));

                        // Remove this keyword from the modal
                        item.remove();

                        reblock_blacklist();
                    }
                });

                item.append(removeButton);

                // Add a checkbox for each page type
                pageTypes.forEach(function (pageType) {
                    let checkbox = $('<input>', {
                        type: 'checkbox',
                        checked: entry[pageType],
                        change: function () {
                            // Update the blacklist when a checkbox is toggled
                            entry[pageType] = this.checked;
                            GM_setValue('blacklist', JSON.stringify(blacklist));
                            reblock_blacklist();
                        }
                    });

                    let label = $('<label>', {
                        text: pageTypesCN[pageType],
                        css: {
                            marginLeft: '10px'
                        }
                    });

                    label.prepend(checkbox);
                    item.append(label);
                });

                modal.append(item);
            });

            // Add save button
            let saveButton = $('<button>', {
                text: '✔️',
                css: {
                    display: 'block',
                    margin: '0 auto',
                    marginTop: '10px'
                },
                click: function () {
                    modal.remove();
                    location.reload(); // refresh the page
                }
            });

            modal.append(saveButton);

            $('body').append(modal);
        }
    });

    //define containsRegex
    $.expr[":"].containsRegex = $.expr.createPseudo(function (arg) {
        var regexp = new RegExp(arg, 'i');
        return function (elem) {
            return regexp.test($(elem).text());
        };
    });

    let prepArray = prepareRegex();

    wrapper.append(buttonB, buttonE);
    $('body').append(wrapper);

    // Run the initial blacklist block
    block_blacklist(prepArray);

    let running = false;
    const observer = new MutationObserver(function (mutationsList, observer) {
        if (!running) {
            running = true;
            requestAnimationFrame(function () {
                block_blacklist(prepArray);
                running = false;
            });
        }
    });

    observer.observe(document.body, { childList: true, subtree: true });

})();