彪扑

(从“烂虎扑屏蔽器”修改而来。)根据屏蔽标题和作者的关键词

// ==UserScript==
// @name         彪扑
// @namespace    https://greasyfork.org/gmail
// @version      0.3.2
// @description  (从“烂虎扑屏蔽器”修改而来。)根据屏蔽标题和作者的关键词
// @author       biopsy
// @license      Apache Licence 2.0
// @match        https://bbs.hupu.com/*
// @icon         https://w1.hoopchina.com.cn/images/pc/old/favicon.ico
// @grant        unSafeWindow
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_log
// @grant        GM_notification
// @grant        GM_listValues
// @grant        GM_deleteValue
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js
// ==/UserScript==

(function () {
    'use strict';
    const Keys = {blacklist: "banKeyword", tour: "firstTime"};

    function appendAssets() {
        const $head = $("head");
        $head.append($(`<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">`));
        $head.append($(`<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">`));
        $head.append($(`<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>`));
    }

    function appendElements() {
        // 添加右下角fab
        const $body = $("body");
        $body.append($(`<div class="fixed-action-btn">
  <a id="fab" class="btn-floating btn-large white">
    <i class="red-text large material-icons">lightbulb_outline</i>
  </a>
  <ul>
    <li id="fab-first"><a class="btn-floating white tooltip" data-tooltip="调试"><i class="red-text material-icons">mode_edit</i></a></li>
    <li id='showBanList'><a class="btn-floating white tooltip" data-tooltip="屏蔽词"><i class="red-text material-icons">reorder</i></a></li>
  </ul>
</div>
`));
        // 添加屏蔽词的chips
        $body.append($(`<div id="keywordList" class="card-panel teal lighten-1" style="background:grey;display:none; z-index:999;text-align:center;width: 66vw;height: 20vh;position: fixed;inset:0;margin: auto;">
    <div class="white-text">语法: %A% 开头屏蔽作者昵称, %I%开头屏蔽作者ID</div>
    <div class="chips chips-placeholder chips-initial"/>
    </div>`));
        // 添加个click事件,用于判断是否点击在chips组件之外
        $('#container').click(() => $('#keywordList').fadeOut(500));

        // 初始化 FAB
        M.FloatingActionButton.init(document.querySelectorAll('.fixed-action-btn'), {direction: 'left'});

        // 初始化 tooltip
        M.Tooltip.init(document.querySelectorAll('.tooltip'), {
            enterDelay: 200, exitDelay: 0, inDuration: 200, outDuration: 200, position: 'top', transitionMovement: 10
        });
    }


    // 添加新用户引导
    function addTour(count) {
        $('body').append($(`<div class="tap-target" data-target="fab">
    <div class="tap-target-content">
      <h5 style="color:white">自定义设置</h5><br/>
      <p style="color:white">在这里进行自定义配置</p><br/>
      <p style="color:white">此新用户引导只显示3次,这是第` + count + `次</p>
    </div>
  </div>`))

        // FeatureDiscovery初始化
        //$('.tap-target').tapTarget()
        let tapTargetElems = document.querySelectorAll('.tap-target')
        let tapTargetOptions = {
            open() {
                console.log('open')
            }, close() {
                console.log('close')
            }
        }
        M.TapTarget.init(tapTargetElems, tapTargetOptions)
        $('.tap-target').tapTarget('open')
    }


    // 显示屏蔽关键字列表(设置过多关键字会导致显示有点不和谐,待修复)
    function showBanKeyword() {
        // 挂载需要填入数据并设置可见性为true
        if ($('#keywordList').length > 0) {
            let elems = document.querySelectorAll('.chips')
            const words = GM_getValue(Keys.blacklist)
            const data = words.map(tag => ({tag}));
            let options = {
                data,
                placeholder: '添加一个屏蔽词',
                secondaryPlaceholder: '继续添加',
                onChipAdd: saveChipsData,
                onChipDelete: saveChipsData,
            }
            M.Chips.init(elems, options)
            alterChipsCardSize(data.length)
        }
    }

    // 根据chips数量来改变该card-panel的大小
    function alterChipsCardSize(length) {
        let height = ((length / 4 & 0) + 1) * 32 + 120;
        $('#keywordList').css({'display': 'inline', 'height': height + 'px'})
    }

    // 用于chips触发“添加”和“删除”事件后保存数据
    function saveChipsData() {
        let data = M.Chips.getInstance($('.chips')).chipsData
        let banKeyword = []
        data.forEach(function (e) {
            let keyword = e.tag
            banKeyword.push(keyword)
        })
        GM_log(banKeyword)
        alterChipsCardSize(banKeyword.length)
        // 将数据存储起来
        GM_setValue(Keys.blacklist, banKeyword)
    }


    //region filter

    /**
     * 语法: %A% %I%
     * @param {string}str
     * @return {(function(text:string): boolean)}
     */
    function createMatcher(str) {
        function createIndexMatcher(key) {
            return (text) => text.indexOf(key) > -1;
        }

        function createRegMatcher(key) {
            const pattern = new RegExp(key);
            return (text) => pattern.test(text);
        }

        if (str.startsWith('/') && s.endsWith('/')) {
            return createRegMatcher(str.substring(1, str.length - 1));
        } else {
            let fuzzy = false;
            if (str.indexOf('*') > -1) {
                fuzzy = true;
                str = str.replaceAll('*', '.*');
            }
            if (str.indexOf('?') > -1) {
                fuzzy = true;
                str = str.replaceAll('?', '.?');
            }
            return fuzzy ? createRegMatcher(str) : createIndexMatcher(str);
        }
    }

    /**
     *
     * @param {string[]} arr
     * @return {function(text:string): boolean}
     */
    function createFluxMatcher(arr) {
        const matchers = arr.map(x => createMatcher(x.trim()));
        return function (text) {
            if (!text) return false;
            for (let i = 0; i < matchers.length; i++) {
                if (matchers[i](text)) {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * @param {string[]} words
     * @return {{filterAuthor: boolean, author({id: string, name: string}): boolean, title(string): boolean}}
     */
    function createFilter(words = []) {
        const titles = [], ids = [], names = [];
        for (let i = 0; i < words.length; i++) {
            const s = words[i];
            if (s.length > 3) {
                const codes = [0, 1, 2].map(i => s.charCodeAt(i));
                if (codes[0] === 37 && codes[2] === 37) {
                    const c = codes[1];
                    if (c === 65 || c === 97) { // A
                        names.push(s.substring(3));
                    } else if (c === 73 || c === 105) { //I
                        ids.push(s.substring(3));
                    } else {
                        titles.push(s.substring(3));
                    }
                } else {
                    titles.push(s);
                }
            } else {
                titles.push(s);
            }
        }
        const titleMatcher = createFluxMatcher(titles);
        const idMatcher = createFluxMatcher(ids);
        const nameMatcher = createFluxMatcher(names);
        return {
            filterAuthor: ids.length > 0 || names.length > 0, title(text) {
                return titleMatcher(text);
            }, author({id, name}) {
                if (idMatcher(id)) return true;
                return nameMatcher(name);
            }
        }
    }

    function hidePostList(filter) {
        const articles = document.querySelectorAll('.bbs-sl-web-post li');

        function shouldHide(elem) {
            if (filter.filterAuthor) {
                const el = elem.querySelector('.post-auth a');
                const name = el.innerText;
                const id = el.getAttribute('href').split('/').at(-1);
                if (filter.author({id, name})) {
                    console.info('hide author:', name, id);
                    return true;
                }
            }
            const title = elem.querySelector('.post-title').innerText;
            if (filter.title(title)) {
                console.info('hide title:', title);
                return true;
            }
            return false;
        }

        for (let i = 0; i < articles.length; i++) {
            const foo = articles[i];
            if (shouldHide(foo)) {

                $(foo).css({'background-color': '#81c784', 'color': 'grey'}).fadeOut(500);
            }
        }
    }

    function hideAd() {
        // 屏蔽虎扑游戏
        $('.game-center-sidebar').remove()
        $('.game-center-entrance-container-title').remove()
        $('#game-center-entrance-container').remove()
        const $hotGame = $(':contains("热门游戏-即点即玩")');
        const el = $hotGame[$hotGame.length - 2]
        if (el) el.innerHTML = '';
    }

    //endregion

    appendAssets();
    appendElements();

    $(document).ready(function () {
        $('#showBanList').click(() => showBanKeyword());

        // 调试
        $('#fab-first').click(() => {
            const arr = GM_getValue(Keys.blacklist);
            console.info('黑名单', arr)
            const filter = createFilter();
            console.info('filter author', filter.filterAuthor)
            console.info(filter)
            console.info('author id', filter.author({id: '1234567', name: 'foobar'}))
            console.info('author name', filter.author({id: 'gg', name: '彭嘎'}))
            console.info('title contains', filter.title('战绩六芒星'))
            console.info('title *', filter.title('一起迎着上'))
            window.filter = filter;
        });
        // hideAd();
        const filter = createFilter(GM_getValue(Keys.blacklist));
        hidePostList(filter);
    })
})();