Greasy Fork

来自缓存

知乎推荐流优化

优化知乎首页推荐流的内容,如移除灌水用户、按屏蔽词屏蔽等

当前为 2024-06-17 提交的版本,查看 最新版本

// ==UserScript==
// @name        知乎推荐流优化
// @namespace   ZhihuFeedOptimization
// @license     GPLv3
// @match       https://www.zhihu.com/
// @grant       GM_setValue
// @grant       GM_getValue
// @grant       GM_registerMenuCommand
// @grant       GM_unregisterMenuCommand
// @version     0.2.3
// @run-at      document-idle
// @author      lisolaris
// @icon        https://www.google.com/s2/favicons?sz=64&domain=zhihu.com
// @description 优化知乎首页推荐流的内容,如移除灌水用户、按屏蔽词屏蔽等
// ==/UserScript==

(function () {
    'use strict';

    const sleep = ms => new Promise(r => setTimeout(r, ms));

    const DEFAULTAVATARHASH = "abed1a8c04700ba7d72b45195223e0ff";

    const userChecked = new Set();
    const cardsToBeDeleted = new Set();

    const bannedWords = new Set();
    var bannedWordsJson = GM_getValue("bannedWords", "");
    if (bannedWordsJson.length)
        for (let word of JSON.parse(bannedWordsJson)) 
            bannedWords.add(word);

    var newCardsThreshold = parseInt(GM_getValue("threshold", "5"));

    function checkIfBannedWordInCard(newCards=null){
        for (let card of newCards){
            // console.log("checkIfBannedWordInCard(): " + card.textContent)
            // // 删除知乎自带的设置屏蔽词按钮(会员可用) 加入一个新的
            // let button = card.querySelector("button.Button.OptionsButton[aria-label='更多']");
            // button.addEventListener("click", (function(){
            //     let popWind = document.querySelector("div.Popover-content.Popover-content--bottom.Popover-content--arrowed.Popover-content-enter-done");
            //     popWind.querySelector("button.TopstoryItem-menuItem").remove();

            //     var addBlockButtonElem = document.createElement("button");
            //     addBlockButtonElem.className = "Button Menu-item AnswerItem-selfMenuItem Button--plain";
            //     addBlockButtonElem.type = "button";
            //     addBlockButtonElem.addEventListener("click", alert("???"));

            //     popWind.querySelector("div.Menu").appendChild(addBlockButtonElem);
            //     }));

            // 检查标题是否含有用户屏蔽词
            for (let word of bannedWords){
                if (card.querySelector("h2").textContent.includes(word)){
                    let cardItem = card.querySelector("div.ContentItem");
                    let extraInfo = JSON.parse(cardItem.getAttribute("data-za-extra-module"));
                    let userId = extraInfo.card.content.author_member_hash_id;
                    console.log(`%c知乎推荐流优化 待删除列表中加入: ${userId}, 原因: 用户屏蔽词 ${word}`, "color:#00A2E8");
                    cardsToBeDeleted.add(card);
                    break;
                }
            }
        }
    }

    async function checkIfAuthorDefaultAvatarInCard(newCards=null){
        var cards;
        if (!newCards) cards = document.getElementsByClassName("Card TopstoryItem TopstoryItem-isRecommend");
        else cards = newCards;

        const fetchPromises = [];
        for (let card of cards) {
            // 每个内容卡片都具有class: "ContentItem ArticleItem"或"ContentItem AnswerItem"
            let cardItem = card.querySelector("div.ContentItem");
            // console.log("card: " + card.textContent)

            let extraInfo = JSON.parse(cardItem.getAttribute("data-za-extra-module"));
            let userId = extraInfo.card.content.author_member_hash_id;

            // 此用户已经被检查过/将要检查的卡片已经在待删除列表里 跳过检查
            if (userChecked.has(userId) || cardsToBeDeleted.has(card)) continue;
            else {
                try{
                    // console.log("知乎推荐流优化 查询用户 " + userId);
                    fetchPromises.push(
                        fetch(`https://api.zhihu.com/people/${userId}/profile?profile_new_version=1`)
                            .then(response => response.json())
                            .then(data => {
                                // data.error: 大量请求知乎api后被反爬虫识别 需要到所给出的页面中进行真人验证
                                if (data.error){
                                    alert("知乎推荐流优化 需要进行真人验证,请在打开的窗口中完成!");
                                    window.open(data.error.redirect);
                                }
                                else {
                                    // console.log("用户 " + userId + " 头像URL " + data.avatar_url_template);
                                    if (data.avatar_url_template.toLowerCase().includes(DEFAULTAVATARHASH)){
                                        console.log(`%c知乎推荐流优化 待删除列表中加入: ${userId}, 原因: 默认头像`, "color:#00A2E8");
                                        cardsToBeDeleted.add(card);
                                    } 
                                    else{
                                        userChecked.add(userId.toLowerCase());
                                    }
                                }
                            })
                            .catch(error => {
                                console.error('发生错误:', error);
                            })
                    );
                }
                catch (e){
                    console.error(e);
                }
            }
        }
        await Promise.all(fetchPromises);

        console.log("知乎推荐流优化 完成检查");
        removeCard();
        fetchPromises.length = 0;
    }

    function removeCard() {
        if (cardsToBeDeleted.size > 0) {
            console.log("知乎推荐流优化 开始移除卡片");

            for (let card of cardsToBeDeleted){
                let cardItem = card.querySelector("div.ContentItem");
                let text = card.querySelector("span.RichText.ztext.CopyrightRichText-richText").textContent;
                const urls = [];
                if (card.querySelectorAll("meta[itemprop='url']").length != 0){
                    for (let url of card.querySelectorAll("meta[itemprop='url']")){
                        urls.push(url.getAttribute("content"));
                    }
                }
                console.log(`%c知乎推荐流优化 已移除卡片: ${cardItem.getAttribute("data-zop")}, 原链接: ${JSON.stringify(urls)}, 预览: ${text}`, "color:#FF00FF");
                // 不可使用card.remove() 会导致点击首页顶部推荐按钮刷新页面时出错(removeChild()失败)
                card.setAttribute("hidden", "")
            }
            cardsToBeDeleted.clear();
        }
    }

    function showArrayContent() {
        console.log(`toBeDeleted(${cardsToBeDeleted.size}): ` + JSON.stringify(Array.from(cardsToBeDeleted)));
        console.log(`userChecked(${userChecked.size}): ` + JSON.stringify(Array.from(userChecked)));
    }

    async function checkCards(newCards=null){
        console.log("知乎推荐流优化 检查新获得的推荐卡片列表……");

        checkIfBannedWordInCard(newCards);
        checkIfAuthorDefaultAvatarInCard(newCards);
    }

    // 当检查到推荐流列表发生更新时的回调函数
    // 在新增的卡片超过五个后即将新增的卡片传入findDefaultAvatarCard()对作者头像进行检查
    function isNodeAddedCallback(mutationRecords, observer){
        const newAddedCards = new Set();

        for (let mutation of mutationRecords){
            if (mutation.addedNodes.length != 0){
                if (mutation.addedNodes[0].className === "Card TopstoryItem TopstoryItem-isRecommend")
                    newAddedCards.add(mutation.addedNodes[0]);
            }
        }

        if (newAddedCards.size >= newCardsThreshold){
            checkCards(Array.from(newAddedCards));
            newAddedCards.clear();
        }
    }

    function setNewCardsCountThreshold(){
        let threshold = prompt("请输入数值:", newCardsThreshold);
        if (!isNaN(parseInt(threshold))){
            GM_setValue("threshold", parseInt(threshold));
        }
    }

    function showBannedWords(){
        bannedWordsJson = GM_getValue("bannedWords");
        alert(`知乎推荐流优化 屏蔽词列表: \n${bannedWordsJson}`);
    }

    function purgeBannedWords(){
        bannedWordsJson = "";
        bannedWords.clear();
        GM_setValue("bannedWords", bannedWordsJson);
        alert("知乎推荐流优化 已清空屏蔽词列表");
        console.log(Array.from(bannedWords));
    }

    function addBannedWords(){
        let words = prompt("请输入屏蔽词,输入多个时以','分隔: ");
        console.log("知乎推荐流优化 用户输入: " + words);
        if (words){
            words = words.replaceAll(/\s*/g,"").replaceAll(",", ",");

            let wordlist = words.split(",");
            for (let w of wordlist) bannedWords.add(w);
            console.log(Array.from(bannedWords));
            bannedWordsJson = JSON.stringify(Array.from(bannedWords));
            GM_setValue("bannedWords", bannedWordsJson);
            alert("知乎推荐流优化 已添加屏蔽词: \n" + JSON.stringify(wordlist));
        }
    }

    function removeBannedWords(){
        let words = prompt("请输入要移除的屏蔽词,输入多个时以','分隔: ");
        console.log("知乎推荐流优化 用户输入: " + words);
        if (words){
            words = words.replaceAll(/\s*/g,"").replaceAll(",", ",");

            let wordlist = words.split(",");
            for (let w of wordlist) bannedWords.delete(w);

            bannedWordsJson = JSON.stringify(Array.from(bannedWords));
            GM_setValue("bannedWords", bannedWordsJson);
            alert(`知乎推荐流优化 已删除屏蔽词 \n当前屏蔽词库: ${bannedWordsJson}`);
        }
    }

    GM_registerMenuCommand("添加屏蔽词", addBannedWords);
    GM_registerMenuCommand("删除屏蔽词", removeBannedWords);
    GM_registerMenuCommand("查看屏蔽词列表", showBannedWords);
    GM_registerMenuCommand("清空屏蔽词列表", purgeBannedWords);
    GM_registerMenuCommand("设置新卡片数量阈值", setNewCardsCountThreshold);

    console.log("知乎推荐流优化 已完成加载");
    console.log("知乎推荐流优化 用户屏蔽词库: " + JSON.stringify(Array.from(bannedWords)));
    console.log("知乎推荐流优化 新卡片数量阈值: " + newCardsThreshold)

    const recomBody = document.querySelector("div.Topstory-recommend");

    if (recomBody) {
        console.log("知乎推荐流优化 在推荐列表变动时检查卡片");
        const obconfig = {attributes: false, childList: true, subtree: true};
        const observer = new MutationObserver(isNodeAddedCallback);
        observer.observe(recomBody, obconfig);

        // console.log("知乎推荐流优化 开始初次检查");
        // sleep(1500);
        // findDefaultAvatarCard();

    }
    // setInterval(showArrayContent, 5000);
})();