新浪微博热搜榜-关键词屏蔽(增强版)

屏蔽微博热搜榜中标签为:剧集、综艺等明显买量条目、热搜广告、热搜关键词。可自定义标签及关键词,并支持json导入导出标签和关键词列表。

// ==UserScript==
// @name         新浪微博热搜榜-关键词屏蔽(增强版)
// @name:en      Sina Weibo Hot Search List - Keyword Blocking (Enhanced Version)
// @namespace    http://tampermonkey.net/
// @version      0.1.0
// @description  屏蔽微博热搜榜中标签为:剧集、综艺等明显买量条目、热搜广告、热搜关键词。可自定义标签及关键词,并支持json导入导出标签和关键词列表。
// @description:en  Block the following tags in Weibo's hot search list: dramas, variety shows and other obvious purchase items, hot search ads, hot search keywords. You can customize tags and keywords, and support json import and export of tag and keyword lists.
// @author       kawatabi
// @match        https://s.weibo.com/top/*
// @icon         https://www.sina.com.cn/favicon.ico
// @grant        none
// @license      GPL-3.0 License
// @require      https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/3.2.1/jquery.min.js
// ==/UserScript==

(function () {
    'use strict';

    let xAdListEl // 设置页面
    let xAdCloseBtn  //设置页面关闭按钮
    let xAdTips //提示标签
    let keywordsListMap //热搜关键词
    let tagListMap //标签

    let adWrapper = $(".data ").find("tbody")
    let adTd01Arr = adWrapper.find(".td-01") // 全部的标签 用于检索标签中为 • 的广告
    let adTd02Tags = adWrapper.find(".td-02").find("span") // 关键词类型内数组
    let adTd02Keyword = adWrapper.find(".td-02").find("a") // 关键词类型内数组
    let adLens = adTd01Arr.length //热搜长度 如果大于50 则说明有上升的热搜

    /** ==================== storageUtils ====================*/

    /**
     *  保存内容到 localStorage 中
     * @param storageName 本地仓库名称
     * @param storageVal 需要存储的值
     * @returns {*} 成功返回存储名称 否则返回null
     */
    let saveStorage = function (storageName, storageVal) {
        if (storageName.trim() === "") {
            throw new Error("saveStorage error:storageName does not empty")
        }
        try {
            localStorage.setItem(storageName, JSON.stringify(storageVal))
            return storageName
        } catch (e) {
            throw new Error("saveStorage error: save fail" + e)
        }
    }

    /**
     *  根据传入的 storageName 获取 localStorage 中的值
     * @param storageName 名称
     * @returns {any} 经过转换 的值
     */
    let getStorage = function (storageName) {
        if (storageName.trim() === "") {
            throw new Error("getStorage error:storageName does not empty")
        }
        try {
            return JSON.parse(localStorage.getItem(storageName));
        } catch (e) {
            throw new Error("getStorage error:get fail" + e)
        }
    }

    //屏蔽的热搜列表
    let blockList = []

    init()

    function init() {
        addElementToBody()
        addHeadLink()
        initModalBox()
        handleMenuBtnClick()
        handleCloseBtnClick()
        initData()
        renderDelKeywordsList()
        renderDelTagList()
        disableAdItem()
        disableTags()
        disableKeyword()
        resetIndex()
        handleAddTagBtnClick()
        handleAddKeywordBtnClick()
        handleExportBtnClick()
        handleImportBtnClick()
        renderBlockListToBody()
    }

    /**
     * 渲染频闭列表到页面中
     */
    function renderBlockListToBody() {
        let blockListEl = $(".x-ad-block-list-wrap")
        blockListEl.siblings("span").text("屏蔽列表(只会显示本次被屏蔽的热搜)共 " + blockList.length + " 条")
        let str = ""
        $.each(blockList, function (i, item) {
            str += `<div class="u-item"><a href="${item.el.get(0)}" target="_blank" title="点击前往热搜:${item.el.text()}">${i + 1},  屏蔽类型:${item.type},屏蔽内容:${item.el.text()}  </a></div>`
        })
        blockListEl.html(str)
    }

    /**
     * 初始化时候读取本地过滤列表
     */
    function initData() {
        keywordsListMap = getStorage("keywordsListMap") || ["时代少年团", "肖战", "王一博"]
        tagListMap = getStorage("tagListMap") || ["剧集", "综艺", "电影", "盛典", "音乐", "演出"]
        saveStorage("keywordsListMap", keywordsListMap)
        saveStorage("tagListMap", tagListMap)
    }

    /**
     * 添加标签按钮被点击
     */
    function handleAddTagBtnClick() {
        $(".x-btn-add-tag").on("click", function () {
            let tag = $(this).siblings(".x-add-ipt-tag").val()
            if (tag.trim() != "") {
                tagListMap.push(tag)
                xAdTips.text("添加成功")
                saveStorage("tagListMap", tagListMap)
                setTimeout(function () {
                    xAdTips.text("")
                }, 3000)
            } else {
                xAdTips.text("输入标签名称后在添加")
            }
        })
    }

    /**
     * 添加关键词按钮被点击
     */
    function handleAddKeywordBtnClick() {
        $(".x-btn-add-keyword").on("click", function () {
            let keywords = $(this).siblings(".x-add-ipt-keyword").val()
            if (keywords.trim() != "") {
                keywordsListMap.push(keywords)
                saveStorage("keywordsListMap", keywordsListMap)
                xAdTips.text("添加成功")
                setTimeout(function () {
                    xAdTips.text("")
                }, 3000)
            } else {
                xAdTips.text("输入关键词后在添加")
            }
        })
    }

    /**
     * 处理导出按钮点击事件
     */
    function handleExportBtnClick() {
        $(".x-btn-export").on("click", function () {
            const data = {
                tagListMap: tagListMap,
                keywordsListMap: keywordsListMap
            };
            const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(data));
            const downloadAnchorNode = document.createElement('a');
            downloadAnchorNode.setAttribute("href", dataStr);
            downloadAnchorNode.setAttribute("download", "weibo_filter_data.json");
            document.body.appendChild(downloadAnchorNode);
            downloadAnchorNode.click();
            downloadAnchorNode.remove();
        });
    }

    /**
     * 处理导入按钮点击事件
     */
    function handleImportBtnClick() {
        $(".x-btn-import").on("click", function () {
            const input = document.createElement('input');
            input.type = 'file';
            input.accept = 'application/json';
            input.onchange = function (event) {
                const file = event.target.files[0];
                const reader = new FileReader();
                reader.onload = function (e) {
                    const result = JSON.parse(e.target.result);
                    if (result.tagListMap && result.keywordsListMap) {
                        tagListMap = result.tagListMap;
                        keywordsListMap = result.keywordsListMap;
                        saveStorage("tagListMap", tagListMap);
                        saveStorage("keywordsListMap", keywordsListMap);
                        renderDelTagList();
                        renderDelKeywordsList();
                        alert("导入成功");
                    } else {
                        alert("文件格式不正确");
                    }
                };
                reader.readAsText(file);
            };
            input.click();
        });
    }

    /**
     * 重新为热搜编号
     */
    function resetIndex() {
        //如果总长度大于50 则说明有上升的热搜
        if (adLens > 50) {
            let resetEl = adWrapper.find(".td-01") //重新获取最新的子元素
            let continueFirstFlag = false
            if (resetEl.find(".icon-top").length > 0) {
                continueFirstFlag = true
            }
            for (let i = 0; i < resetEl.length; i++) {
                if (continueFirstFlag) {
                    continueFirstFlag = !continueFirstFlag
                    continue
                }
                $(resetEl[i]).text(i)
            }
        }
    }

    /**
     * 隐藏包含 disableStartName 集合中关键字的热搜
     */
    function disableKeyword() {
        $.each(keywordsListMap, function (nameIndex, keyword) {
            $.each(adTd02Keyword, function (i, adKeyword) {
                if ($(adKeyword).text().indexOf(keyword) !== -1) {
                    $(adKeyword).parents("tr").remove()
                    blockList.push({
                        el: $(adKeyword).parents("tr").children(".td-02").children("a"),
                        type: `关键词(${keyword})`
                    })
                }
            })
        })
    }

    /**
     * 隐藏tag为 tagListMap 定义的内容的热搜
     */
    function disableTags() {
        $.each(tagListMap, function (i, tagItem) {
            $.each(adTd02Tags, function (i, adTagItem) {
                if ($(adTagItem).text().indexOf(tagItem) !== -1) {
                    $(adTagItem).parents("tr").remove()
                    blockList.push({
                        el: $(adTagItem).parents("tr").children(".td-02").children("a"),
                        type: `标签(${tagItem})`
                    })
                }
            })
        })
    }

    /**
     * 隐藏广告
     */
    function disableAdItem() {
        $.each(adTd01Arr, function (i, item) {
            if ($(item).text() === "•") {
                $(item).parents("tr").remove()
            }
        })
    }

    /**
     * 渲染删除关键词列表 并且将删除后的数组保存到本地
     */
    function renderDelKeywordsList() {
        let delKeywordsListEl = $(".del-keywords-list")
        let delKeywordsStr = ""
        $.each(keywordsListMap, function (i, item) {
            delKeywordsStr +=
                '<div class="u-item"><span class="x-ad-txt">' + item + '</span><span class="x-delete-btn"  title="删除关键词  ' + item + '">删除</span></div>'
        })
        delKeywordsListEl.html(delKeywordsStr)

        delKeywordsListEl.on("click", ".x-delete-btn", function () {
            let index = $(this).parents(".u-item").index()
            $(this).parents(".u-item").remove()
            keywordsListMap.splice(index, 1)
            saveStorage("keywordsListMap", keywordsListMap)
        })
    }

    /**
     * 渲染删除tag列表 并且将删除后的数组保存到本地
     */
    function renderDelTagList() {
        let delTagListEl = $(".del-tag-list")
        let delTagStr = ""
        $.each(tagListMap, function (i, item) {
            delTagStr +=
                '<div class="u-item"><span class="x-ad-txt">' + item + '</span><span class="x-delete-btn" title="删除标签 ' + item + '">删除</span></div>'
        })
        delTagListEl.html(delTagStr)
        delTagListEl.on("click", ".x-delete-btn", function () {
            let index = $(this).parents(".u-item").index()
            $(this).parents(".u-item").remove()
            tagListMap.splice(index, 1)
            saveStorage("tagListMap", tagListMap)
        })
    }

    /**
     * 拖动模态框
     */
    function initModalBox() {
        //拖动功能
        let modalBox = document.querySelector(".x-ad-btn-setting");
        modalBox.addEventListener("mousedown", function (e) {  //鼠标按下的时候,得到鼠标在盒子里面的坐标
            let x = e.pageX - modalBox.offsetLeft;
            let y = e.pageY - modalBox.offsetTop;
            document.addEventListener("mousemove", move);  //鼠标移动的时候,得到模态框的坐标
            function move(e) {
                modalBox.style.left = e.pageX - x + "px";
                modalBox.style.top = e.pageY - y + "px";
            }

            document.addEventListener("mouseup", function () {  //鼠标弹起的时候,解除鼠标移动事件
                document.removeEventListener("mousemove", move);
            })
        })
        $(modalBox).on("click", function () {
            xAdListEl.fadeToggle()
        })
    }

    /**
     * 添加自定义内容标签到页面中
     */
    function addElementToBody() {
        let settingsEl = '<div class="x-ad-btn-setting" title="热搜关键词设置,按住鼠标左键可拖动">热</div>'
        let xAdEl = `
                <div class="x-ad-list" style="display: none">
                    <div class="x-ad-header">
                        <div class="x-ad-title">热搜关键词设置(刷新页面生效)</div>
                        <div class="x-ad-tips"></div>
                        <div class="x-ad-close-btn" title="关闭设置界面">X</div>
                    </div>
                    <div class="x-ad-wrapper">
                        <div class="a-ad-menu-list">
                            <div class="a-ad-menu-list-item x-active-menu">添加过滤信息</div>
                            <div class="a-ad-menu-list-item">删除过滤信息</div>
                            <div class="a-ad-menu-list-item">导入/导出</div>
                            <div class="a-ad-menu-list-item">屏蔽列表</div>
                        </div>
                        <div class="x-ad-container">
                            <!--添加新过滤内容-->
                            <div class="x-ad-add-wrap">
                                <div class="x-add-tags">
                                    <span class="add-tips">添加新标签</span>
                                    <p class="x-add-item">
                                <input type="text" class="x-add-ipt x-add-ipt-tag" placeholder="请输入需要过滤的标签名称">
                                <button class="x-btn x-btn-add-tag">添加</button>
                                </p>
                                </div>
                                <div class="x-add-keywords">
                                    <span class="add-tips">添加新关键词</span>
                                    <p class="x-add-item">
                            <input type="text" class="x-add-ipt x-add-ipt-keyword" placeholder="请输入需要过滤的关键词">
                            <button class="x-btn x-btn-add-keyword">添加</button>
                            </p>
                                </div>
                            </div>
                            <!--删除已添加的项目-->
                            <div class="x-ad-del-wrap">
                                <div class="x-del-tags">
                                    <span>全部标签列表(新增加的要刷新后才显示)</span>
                                    <div class="u-list del-tag-list"></div>
                                </div>
                                <div class="x-del-keyword">
                                    <span>全部关键词列表(新增加的要刷新后才显示)</span>
                                    <div class="u-list del-keywords-list"></div>
                                </div>
                            </div>
                            <!-- 导入导出 -->
                            <div class="x-ad-import-export-wrap" style="display:none;">
                                <button class="x-btn x-btn-export">导出</button>
                                <button class="x-btn x-btn-import">导入</button>
                            </div>
                            <div class="x-ad-block-list">
                                <span>屏蔽列表(只会显示本次被屏蔽的热搜)</span>
                                <div class=" u-list x-ad-block-list-wrap">
                                </div>
                            </div>
                        </div>
                    </div>
                </div>`
        $("body").append(settingsEl)
        $("body").append(xAdEl)
        xAdListEl = $(".x-ad-list")
        xAdCloseBtn = $(".x-ad-close-btn")
        xAdTips = $(".x-ad-tips")
    }

    /**
     * 关闭按钮被点击
     */
    function handleCloseBtnClick() {
        xAdCloseBtn.on("click", function () {
            xAdListEl.fadeToggle()
        })
    }

    /**
     * 添加菜单栏点击切换事件
     */
    function handleMenuBtnClick() {
        $(".a-ad-menu-list-item").on("click", function () {
            $(this).addClass("x-active-menu").siblings().removeClass("x-active-menu")
            let index = $(this).index()
            $(this).parents(".a-ad-menu-list").siblings(".x-ad-container").children().eq(index).show().siblings().hide()
        })
    }

    /**
     *添加head中的style标签
     */
    function addHeadLink() {
        let head = document.querySelector("head");
        let styleEl = addElStyle();
        head.appendChild(styleEl);
    }

    /**
     *
     * 添加css样式方法,脚本的所有css 都将在这里定义
     * @returns style 标签
     */
    function addElStyle() {
        let style = document.createElement("style");
        style.type = "text/css";
        style.innerHTML = `
        * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
        }

        .x-ad-btn-setting {
        display: flex;
        justify-content: center;
        align-items: center;
        position: fixed;
        left: 0;
        top: 50%;
        transform: translate(0%, -50%);
        border-radius: 5px;
        width: 40px;
        height: 40px;
        text-align: center;
        font-size: 16px;
        background-color: #4FC3F7;
        cursor: pointer;
        user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        color: #fff;
        }

        /*设置列表*/
        .x-ad-list {
        display: flex;
        flex-direction: column;
        position: fixed;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        width: 500px;
        height: 500px;
        border-radius: 5px;
        background-color: #fff;
        box-shadow: 0 0 10px rgba(0, 0, 0, .3);
        }

        /*标题*/
        .x-ad-header {
        position: relative;
        display: flex;
        padding: 5px 10px;
        font-size: 18px;
        border-bottom: 1px solid rgba(0, 0, 0, .1);
        }

        /*标题文字*/
        .x-ad-title {
        font-size: 18px;
        }

        /*提示文字*/
        .x-ad-tips, .x-ad-close-btn {
        position: absolute;
        right: 40px;
        color: green;
        font-size: 16px;
        }

        .x-ad-close-btn {
        padding: 10px;
        top: -5px;
        right: 5px;
        color: #333;
        cursor: pointer;
        transition: all .3s ease;
        }

        .x-ad-close-btn:hover {
        transform: rotate(90deg);
        }

        /*主要设置部分*/
        .x-ad-wrapper {
        display: flex;
        flex: 1;
        background-color: #EEEEEE;

        }

        /*设置菜单 列表*/
        .a-ad-menu-list {
        display: flex;
        text-align: center;
        flex-direction: column;
        width: 100px;
        height: 100%;
        cursor: pointer;
        }

        /*每一个菜单项*/
        .a-ad-menu-list-item {
        padding: 5px;
        }

        /*鼠标经过菜单显示的颜色*/
        .a-ad-menu-list-item:hover {
        background-color: #FAFAFA;
        }

        /*主要内容部分*/
        .x-ad-container {
        flex: 1;
        background-color: #ffffff;
        }

        /*添加关键词*/
        .x-ad-add-wrap, .x-ad-del-wrap,.x-ad-block-list {
        padding: 10px;
        }
        .x-ad-del-wrap,.x-ad-block-list{
        display:none;
        }


        /*每一个添加的行*/
        .x-add-item {
        margin: 16px 0;
        display: flex;
        /*justify-content: center;*/
        align-items: center;
        }

        /*输入框*/
        .x-add-ipt {
        width: 250px;
        outline: none;
        height: 30px;
        border-radius: 2px;
        padding: 0 5px;
        color: #333;
        border: 1px solid #eee;
        }

        /*添加按钮*/
        .x-btn {
        width: 80px;
        height: 30px;
        border: none;
        cursor: pointer;

        }

        /*活跃的菜单*/
        .x-active-menu {
        background-color: #fff;
        }


        /*列表盒子*/
        .u-list {
        margin: 5px 10px;
        background-color: rgba(238, 238, 238, .4);
        min-height: 186px;
        max-height: 186px;
        overflow-y: auto;
        border-radius: 2px;
        }

        /*列表盒子中的每一个项*/
        .u-item {
        padding: 5px;
        display: flex;
        justify-content: space-between;
        }

        /*双数设置背景颜色*/
        .u-item:nth-child(even) {
        background-color: rgba(244, 255, 255, 0.5);
        }

        /*经过每一个项*/
        .u-item:hover {
        background-color: #FFB74D;
        }

        /*删除按钮*/
        .x-delete-btn {
        cursor: pointer;
        user-select: none;
        -ms-user-select: none;
        }

        /*鼠标经过删除按钮*/
        .x-delete-btn:hover {
        color: #ffffff;
        }

        /*删除关键词盒子*/
        .x-del-keyword {
        margin-top: 15px;
        }
    `;

        return style;
    }


})();