Zhihu Blocklist Helper

完善知乎黑名单功能,屏蔽黑名单用户回答

当前为 2020-10-09 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Zhihu Blocklist Helper
// @name:zh      知乎黑名单助手
// @namespace    https://github.com/mixterjim
// @version      0.7
// @author       MixterJim
// @description  完善知乎黑名单功能,屏蔽黑名单用户回答
// @homepage     https://greasyfork.org/scripts/XXXXX
// @icon         https://static.zhihu.com/static/favicon.ico
// @supportURL   https://gist.github.com/mixterjim/a1f4d28e7aafcff12d4fa1d18db85fb6
// @include      *://www.zhihu.com/*
// @connect      www.zhihu.com
// @run-at       document-body
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @nocompat     Chrome
// @license      GPL-3.0
// @create       2020.08.20
// @note         2020.08.31-V0.7 增加设置面板
// @thanks       Google Closure
// ==/UserScript==


(function() {
    var zbh = {
        array: {
            // *
            //  * Removes all duplicates from an array.
            //  *
            //  * Runtime: N,
            //  * Worstcase space: 2N
            //  *
            //  * @param {Array} arr The array from which to remove.
            //  *     duplicates.

            removeDuplicates: function(arr) {
                let seen = {},
                    cursorInsert = 0,
                    cursorRead = 0;
                while (cursorRead < arr.length) {
                    let current = arr[cursorRead++];
                    if (!Object.prototype.hasOwnProperty.call(seen, current)) {
                        seen[current] = true;
                        arr[cursorInsert++] = current;
                    }
                };
                arr.length = cursorInsert;
            },

            /**
             * Removes the first occurrence of a particular value from an array.
             * @param {IArrayLike<T>} arr Array from which to remove
             *     value.
             * @param {T} obj Object to remove.
             * @template T
             */
            remove: function(arr, obj) {
                var i = arr.indexOf(obj);
                if (i >= 0) {
                    Array.prototype.splice.call(arr, i, 1);
                }
            },
        },

        /**
         * Get blocked id from API.
         *
         * @param {Array} arr The array in which to save the results.
         * @param {string} url An link for which we are getting blocked id.
         * @param {number|string} offset The index at where we start to
         *      get the blocked id.
         */
        getBlockedIdList: function(arr, url, offset) {
            let request = new XMLHttpRequest();
            request.onload = function() {
                request.response.data.forEach(function(item, index, array) {
                    arr.push(item.id);
                });
                if (request.response.paging.totals > arr.length) {
                    zbh.getBlockedIdList(arr, url, arr.length);
                } else {
                    zbh.array.removeDuplicates(arr);
                    GM_setValue("blockedIdList", arr);
                }
            };
            request.open("GET", url + offset, true);
            request.responseType = 'json';
            request.send();
        },

        /**
         * Set block button.
         *
         * @param {string} name The author name.
         * @param {string} isBlocked Is the author blocded?
         * @param {object} button The block botton.
         */
        blockButton: function(name, isBlocked, button) {
            let request = new XMLHttpRequest();
            if (isBlocked === "true") {
                request.open("DELETE", "https://www.zhihu.com/api/v4/members/" + name + "/actions/block", true);
                button.textContent = "Block";
                button.setAttribute('blocked', "false");
                button.className = "Button Button--primary Button--blue";
                zbh.array.remove(blockedIdList, button.id)
            } else {
                request.open("POST", "https://www.zhihu.com/api/v4/members/" + name + "/actions/block", true);
                button.textContent = "Blocked";
                button.setAttribute('blocked', "true");
                button.className = "Button Button--primary Button--red";
                blockedIdList.push(button.id);
            }
            request.send();
            GM_setValue("blockedIdList", blockedIdList);
            console.log(blockedIdList)
        },

        settingButtonClick: function() {
            if (document.querySelector(".setting-panel").style.display == "block") {
                document.querySelector(".setting-panel").style.display = "none";
            } else {
                document.querySelector(".isBlockHidden").checked = Config.isBlockHidden;
                document.querySelector(".isBlockTempShow").checked = Config.isBlockTempShow;
                document.querySelector(".isBlockBtnNotHidden").checked = Config.isBlockBtnNotHidden;
                document.querySelector(".isBlockPost").checked = Config.isBlockPost;
                document.querySelector(".setting-panel").style.display = "block";
            }
        },

        hidenAnswer: function() {
            let answerItemList = document.querySelectorAll(".List-item");
            while (index < answerItemList.length) {
                let item = answerItemList[index].querySelector(".ContentItem.AnswerItem");
                let name = answerItemList[index].querySelector(".UserLink-link").href.slice(29);
                let id = JSON.parse(item.getAttribute("data-za-extra-module")).card.content.author_member_hash_id;
                if (blockedIdList.indexOf(id) != -1) {
                    item.hidden = true;
                    item.querySelector(".AuthorInfo.AnswerItem-authorInfo.AnswerItem-authorInfo--related").innerHTML += '<button style="float:right" class="Button Button--primary Button--red" blocked="true" id=' + index + ' value=' + name + '>Blocked</button>';
                    answerItemList[index].innerHTML += '<div class="Blockeditem" style="text-align:center"><span class="blockShow" id=' + id + '>Block by ' + JSON.parse(item.getAttribute("data-zop")).authorName + '</span></div>';
                    // answerItemList[index].querySelector(".blockShow").onclick=function(){answerItemList[this.id].querySelector(".ContentItem.AnswerItem").hidden = false;this.hidden = true};
                    // <BUG> 取消隐藏后原 click 侦听器消失
                } else {
                    item.querySelector(".AuthorInfo.AnswerItem-authorInfo.AnswerItem-authorInfo--related").innerHTML += '<button style="float:right" class="Button Button--primary Button--blue" blocked="false" id=' + id + ' value=' + name + '>Block</button>';
                }
                item.querySelector(".Button.Button--primary").onclick = function() {
                    zbh.blockButton(this.value, this.getAttribute("blocked"), this);
                };
                index++;
            };
            return index;
        },

        hidenPost: function() {
            let itemList = document.querySelectorAll(".Card.TopstoryItem.TopstoryItem-isRecommend");
            while (questionIndex < itemList.length) {
                let item = itemList[questionIndex].firstChild;
                try {
                    let itemType = JSON.parse(item.getAttribute("data-za-extra-module")).card.content.type;
                    if (itemType != "Answer") {
                        let data = JSON.parse(item.firstChild.getAttribute("data-zop"));
                        let authorName, postName;
                        if (data != null) {
                            authorName = data.authorName;
                            postName = data.title;
                        } else {
                            authorName = "视频";
                            postName = item.querySelector(".ContentItem-title").textContent;
                        }
                        let title = authorName + " 《" + postName + "》";
                        item.firstChild.hidden = true;
                        console.log(questionIndex, "hidden")
                        item.innerHTML += '<div class="Blockeditem" style="text-align:center"><span class="blockShow" id=' + questionIndex + '>Block by ' + title + '</span></div>';
                        itemList[questionIndex].querySelector(".blockShow").onclick = function() {
                            itemList[this.id].firstChild.firstChild.hidden = false;
                            this.hidden = true
                        };
                        // <BUG> 取消隐藏后原 click 侦听器消失
                    }
                } catch (e) {
                    /* 此处应该是广告 */
                    questionIndex++;
                    continue;
                }
                questionIndex++;
            }
        },

        zbhsettingButton: function() {
            let settingButton = `
      <div class="Setting">
          <button class="Button AppHeader-notifications css-79elbk Button--plain">
              <span style="display: inline-flex; align-items: center;">​&#8203;
                  <svg class="Zi Zi--Settings" fill="currentColor" viewBox="0 0 24 24" width="22" height="22">
                      <path d="M20.868 17.185a.896.896 0 0 1-.452.137c-.123 0-1.397-.26-1.617-.233-1.354.014-1.78 1.276-1.835 1.742-.055.453 0 .892.191 1.303a.8.8 0 0 1-.068.851C16.224 21.877 14.922 22 14.73 22a.548.548 0 0 1-.356-.151c-.11-.096-.685-1.138-1.069-1.468-1.304-.955-2.247-.329-2.63 0-.398.33-.672.7-.836 1.125a.632.632 0 0 1-.329.37c-1.354.426-2.918-.919-3.014-1.056a.564.564 0 0 1-.123-.356c-.014-.138.383-1.276.342-1.688-.342-1.9-1.836-1.687-2.096-1.673a3.192 3.192 0 0 0-.918.178.873.873 0 0 1-.59-.055c-.887-.462-1.136-2.332-1.109-2.51.055-.315.192-.521.438-.604.425-.164.809-.452 1.151-.85.931-1.262.343-2.25 0-2.634-.342-.356-.726-.645-1.15-.809-.138-.041-.234-.151-.33-.316-.38-1.434.613-2.552.867-2.77.255-.22.6-.055.723 0 .425.164.877.219 1.343.15C6.7 6.636 6.784 5.141 6.81 4.908c.014-.247-.11-1.29-.137-1.4a.488.488 0 0 1 .027-.315C7.317 2.178 9.071 2 9.222 2a.56.56 0 0 1 .439.178c.11.124.63 1.111 1 1.4.4.338 1.583.83 2.59.013.397-.274.959-1.29 1.082-1.413A.55.55 0 0 1 14.717 2c1.56 0 2.329 1.029 2.438 1.22a.458.458 0 0 1 .069.371c-.028.151-.329 1.152-.26 1.605.365 1.537 1.383 1.742 1.89 1.783.493.028 1.644-.356 1.809-.343a.63.63 0 0 1 .424.206c.535.31.85 1.715.905 2.14.027.233-.014.439-.11.562-.11.138-1.165.714-1.48 1.112-.855.982-.342 2.25-.068 2.606.26.37 1.22.905 1.288.96.15.137.26.302.315.494.146 1.413-.89 2.387-1.069 2.47zm-8.905-.535c.644 0 1.246-.123 1.822-.356a4.576 4.576 0 0 0 1.493-1.016 4.694 4.694 0 0 0 1-1.495c.247-.562.357-1.18.357-1.81 0-.659-.11-1.262-.356-1.825a4.79 4.79 0 0 0-1-1.481 4.542 4.542 0 0 0-1.494-1.002 4.796 4.796 0 0 0-3.631 0 4.627 4.627 0 0 0-1.48 1.002c-.424.425-.767.919-1 1.481a4.479 4.479 0 0 0-.37 1.825c0 .644.124 1.248.37 1.81a4.62 4.62 0 0 0 1 1.495c.425.426.918.768 1.48 1.016a4.677 4.677 0 0 0 1.809.356z" fill-rule="evenodd"></path>
                  </svg>
              </span>
          </button>
      </div>
    `;
            let settingPanel = `
      <div class="setting-panel">
        <style type="text/css">
          .setting-panel {
            position: fixed;
            top: 3vw;
            right: 18vw;
            z-index: 203;
            background-color: #fff;
            padding-bottom: 8px;
            display: none;
            border: 1px solid #ebebeb;
            border-radius: 4px;
            -webkit-box-shadow: 0 5px 20px rgba(26,26,26,.1);
          }
          .setting-header {
            padding: 14px 0;
            text-align: center;
            border-bottom: 1px solid rgb(246, 246, 246);
          }
          .setting-list {
            margin: 8px auto 9px;
            padding: 0 16px;
          }
          .setting-button {
            margin: 0 16px;
            border-top: 1px solid rgb(246, 246, 246);
            padding: 10px 8px;
          }
          .setting-list li {
            position: relative;
            font-size: 14px;
            line-height: 24px;
            color: #1a1a1a;
          }
          .clear {
            float: right;
            color: #8590a6;
            font-size: 13px;
          }
        </style>
        <div class="setting-header">知乎黑名单助手</div>
        <div class="setting-list">
          <li>
            <label><input type="checkbox" class="isBlockHidden">隐藏拦截条目</label>
            <label><input type="checkbox" class="isBlockTempShow">临时显示拦截条目</label>
          </li>
          <li>
            <label><input type="checkbox" class="isBlockBtnNotHidden">显示拉黑按钮</label>
          </li>
          <li>
            <label><input type="checkbox" class="isBlockPost">屏蔽专栏</label>
            <button class="clear" title="恢复默认设置,清空黑名单">复位</button>
          </li>
        </div>
        <div class="setting-button">
          <span class="cancel" style="position: relative; float: left;">取消</span>
          <span class="save" style="position: relative; float: right;">保存</span>
        </div>
        <script type="text/javascript">
          // document.(".cancel").addEventListener("click", function(){document.querySelector(".setting-panel").style.display = "none";});
          // document.getElementById("isBlockHidden").checked=Config.isBlockHidden;
        </script>
      </div>
    `;
            document.querySelector(".AppHeader-userInfo").insertAdjacentHTML("afterbegin", settingButton);
            document.querySelector(".Setting").addEventListener("click", zbh.settingButtonClick);
            document.querySelector(".Sticky.AppHeader.AppHeader--old").insertAdjacentHTML("afterend", settingPanel);
            document.querySelector(".cancel").addEventListener("click", function() {
                document.querySelector(".setting-panel").style.display = "none";
            });
            document.querySelector(".save").addEventListener("click", function() {
                Config.isBlockHidden = document.querySelector(".isBlockHidden").checked;
                Config.isBlockTempShow = document.querySelector(".isBlockTempShow").checked;
                Config.isBlockBtnNotHidden = document.querySelector(".isBlockBtnNotHidden").checked;
                Config.isBlockPost = document.querySelector(".isBlockPost").checked;
                GM_setValue("Config", Config);
                document.querySelector(".setting-panel").style.display = "none";
            });
            document.querySelector(".clear").addEventListener("click", function() {
                GM_deleteValue("blockedIdList");
                GM_deleteValue("Config");
                location.reload();
                // document.querySelector(".setting-panel").style.display = "none";
            });
        },

    };

    /*--------------------------------Main-----------------------------------*/

    var Config = GM_getValue("Config", {
        isBlockHidden: false, // 是否隐藏已拦截的条目
        isBlockTempShow: false, //是否允许临时显示拦截条目
        isBlockBtnNotHidden: false, // 是否隐藏拉黑按钮
        isBlockPost: true, //是否屏蔽专栏
    });

    var getBlockedUserUrl = "/api/v3/settings/blocked_users?limit=20&offset=";

    var blockedIdList = GM_getValue("blockedIdList", []);
    if (blockedIdList.length === 0) {
        console.log("Loading");
        zbh.getBlockedIdList(blockedIdList, getBlockedUserUrl, blockedIdList.length);
    }

    // if (/.*zhihu.com\/question.*/.test(document.URL)) {
    // // if (/.*zhihu.com\/question.*/.test(document.URL) && !/answer/.test(document.URL)) {
    //     var index = 0;
    //     setInterval(zbh.hidenAnswer, 1000);
    //     document.addEventListener('readystatechange', function() {
    //         document.querySelectorAll(".QuestionMainAction.ViewAll-QuestionMainAction").forEach(function(item, index, array) {
    //             item.addEventListener('click',function(){
    //                 window.location.href=item.getAttribute("href")
    //             })
    //             console.log(item)
    //         });
    //     });
    //     console.log("Start hidenAnswer")
    // }

    if (/.*zhihu.com\/question.*/.test(document.URL)) {
    // if (/.*zhihu.com\/question.*/.test(document.URL) && !/answer/.test(document.URL)) {
        var index = 0;
        setInterval(zbh.hidenAnswer, 1000);
        document.addEventListener('readystatechange', function() {
            var viewAllButton = document.getElementsByClassName("QuestionMainAction ViewAll-QuestionMainAction")
            for(var i=0;i<viewAllButton.length;i++) {
                viewAllButton[i].addEventListener('click',function(){
                    window.location.href=viewAllButton[i].href;
                });
                console.log("Start hidenAnswer")
            }
        });
    }

    if ((location.pathname === "/" || /.*zhihu.com\/search?.*/.test(document.URL)) & Config.isBlockPost) {
        var questionIndex = 0;
        setInterval(zbh.hidenPost, 1000);
    }

    if (/.*zhihu.com\/people\/.*/.test(document.URL)) {
        document.addEventListener('readystatechange', function() {
            if (document.readyState === 'complete') {
                try {
                    document.querySelector("div.MemberButtonGroup.ProfileButtonGroup.ProfileHeader-buttons Button.Button--primary.Button--red").addEventListener("click", function() {
                        GM_deleteValue("blockedIdList");
                    })
                } catch (e) {
                    console.log('ok')
                }
            }
        });
    }

    document.addEventListener('readystatechange', function() {
        if (document.readyState === 'complete') {
            zbh.zbhsettingButton();
        }
    });

    document.addEventListener(
        /* 解除复制版权限制,删除复制时附带的版权声明 */
        "copy",
        function(e) {
            e.stopPropagation();
        },
        true
    );

})();