// ==UserScript== // @name 真·知乎屏蔽 Truly Zhihu Shield // @namespace https://github.com/Virtual-Dimension/TrulyShield/blob/master/TrulyZhihuShield.js // @version 0.4.0 // @description Shield information you don't want to see. // @author Ciyang // @license GPL-3.0 // @match https://www.zhihu.com/* // @match http://www.zhihu.com/* // @grant GM_xmlhttpRequest // @downloadURL https://update.greasyfork.icu/scripts/392142/%E7%9C%9F%C2%B7%E7%9F%A5%E4%B9%8E%E5%B1%8F%E8%94%BD%20Truly%20Zhihu%20Shield.user.js // @updateURL https://update.greasyfork.icu/scripts/392142/%E7%9C%9F%C2%B7%E7%9F%A5%E4%B9%8E%E5%B1%8F%E8%94%BD%20Truly%20Zhihu%20Shield.meta.js // ==/UserScript== 'use strict'; (() => { let getContentElement = () => { return document.getElementById('root'); }; let isCard = (className) => { return className.search('NestComment') !== -1 || className.search('Card') !== -1 || className.search('List-item') !== -1 || className.search('openComment') !== -1; }; let mRequest = (method, href, fn) => { let xmlHttp = new window.XMLHttpRequest(); xmlHttp.open(method, href, true); xmlHttp.onreadystatechange = () => { if (xmlHttp.readyState === 4 && xmlHttp.status === 200) { fn(xmlHttp.responseText); } }; xmlHttp.send(); }; let userShieldMap = new Map(); let excludeEditor = () => { return window.location.href.search("www.zhihu.com/people/") !== -1 || window.location.href.search("www.zhihu.com/org/") !== -1; }; let getUserShielded = (username, href, fn) => { if (!userShieldMap.has(username)) { mRequest('get', href, (resText) => { let res = (resText.search('已屏蔽') !== -1); userShieldMap.set(username, res); fn(res); }); } else { fn(userShieldMap.get(username)); } }; let shieldKeywords = []; let shieldCount = 0; let checkShielded = () => { let questionList = document.querySelectorAll('a[data-za-detail-view-element_name]'); for (const question of questionList) { for (const item of shieldKeywords) { if (question.innerText.indexOf(item) !== -1) { let card = question; while (card.id !== 'root' && (!card.className || !isCard(card.className))) { card = card.parentElement; } if (card.id !== 'root') { card.remove(); // Remove or Change to shield info ++shieldCount; } break; } } } let userList = document.getElementsByClassName('UserLink-link'); for (const user of userList) { if (user.innerText && user.href) { let card = user; while (card.id !== 'root' && (!card.className || !isCard(card.className))) { card = card.parentElement; } if (card.id !== 'root') { ((userElement, cardElement) => { let url = new window.URL(userElement.href, window.location.href); getUserShielded(userElement.innerText, url.href, res => { if (res) { cardElement.remove(); // Remove or Change to shield info ++shieldCount; } }); })(user, card); } } } }; let timer; let moCallback = (mutationsList, observer) => { for (const mutation of mutationsList) { if (mutation.type === 'childList' && mutation.addedNodes && mutation.addedNodes.length) { if (timer) clearTimeout(timer); timer = setTimeout(checkShielded, 50); } } }; let observerConfig = { childList: true, subtree: true }; let openFlag = false; let checkFullPageModal = () => { if (!openFlag && document.getElementsByClassName('Modal--fullPage').length) { openFlag = true; let modal = document.getElementsByClassName('Modal--fullPage')[0]; let observer = new window.MutationObserver(moCallback); observer.observe(modal, observerConfig); if (timer) clearTimeout(timer); timer = setTimeout(checkShielded, 50); } if (!document.getElementsByClassName('Modal--fullPage').length) { openFlag = false; } }; let downloadObject = (filename, object) => { let downloadElement = document.createElement('a'); downloadElement.href = ['data:application/json;', window.JSON.stringify(object)]; downloadElement.download = filename; downloadElement.click(); }; let addShieldMenu = () => { let shieldMenu = document.createElement('div'); shieldMenu.style.border = 'solid 1px #0000001c'; shieldMenu.style.position = 'fixed'; shieldMenu.style.top = '10%'; shieldMenu.style.left = '0.5%'; shieldMenu.style.maxWidth = '40px'; shieldMenu.style.textAlign = "center"; let addButton = document.createElement('button'); addButton.innerHTML = ''; addButton.style.margin = '1px'; addButton.onclick = async () => { let res = window.prompt('输入想添加的屏蔽关键词,添加单个不需要输入引号。如果想添加多个请用\"[]\"包括并使用\",\"分割。可输入一个外链直接导入词库。', '[\"XXX\",\"YYY\"]'); if (res === null || res === '') { return; } let cnt = 0; try { let url = new window.URL(res); res = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", headers: { "Accept": "application/json" }, url: url.href, onload: (response) => { resolve(response.responseText); } }); }); } catch (err) { } try { res = window.JSON.parse(res); for (const str of res) { if (shieldKeywords.indexOf(str) === -1) { shieldKeywords.push(str); ++cnt; } } } catch (err) { if (shieldKeywords.indexOf(res) === -1) { shieldKeywords.push(res); ++cnt; } } if (cnt) { window.localStorage.setItem('TrulyShield', window.JSON.stringify(shieldKeywords)); } window.alert(['成功添加', cnt, "个屏蔽关键字"].join('')); }; let removeButton = document.createElement('button'); removeButton.innerHTML = '' removeButton.style.margin = '1px'; removeButton.onclick = () => { let res = window.prompt('输入想移除的屏蔽关键词,使用/all清空屏蔽关键词。'); if (res === null || res === '') { return; } let res2 = res.toLowerCase(); let cnt = 0; if (res2 === '/all') { cnt += shieldKeywords.length; shieldKeywords = []; } else { res2 = shieldKeywords.indexOf(res); if (res2 !== -1) { shieldKeywords.slice(res2, 1); ++cnt; } } if (cnt) { window.localStorage.setItem('TrulyShield', window.JSON.stringify(shieldKeywords)); } window.alert(['成功移除', cnt, "个屏蔽关键字"].join('')); }; let downloadButton = document.createElement('button'); downloadButton.innerHTML = '' downloadButton.style.margin = '1px'; downloadButton.onclick = () => { downloadObject("data.json", shieldKeywords); }; let shieldCountElement = document.createElement('button'); shieldCountElement.style.border = 'solid 1px #3498db'; shieldCountElement.style.borderRadius = '50%'; shieldCountElement.style.margin = '1px'; shieldCountElement.style.overflow = 'hidden'; shieldCountElement.style.width = '32px'; shieldCountElement.style.height = '32px'; setInterval(() => { shieldCountElement.innerText = shieldCount }, 500); shieldMenu.appendChild(addButton); shieldMenu.appendChild(removeButton); shieldMenu.appendChild(downloadButton); shieldMenu.appendChild(shieldCountElement); document.body.appendChild(shieldMenu); }; let initShieldKeywords = () => { let res = []; try { let data = window.localStorage.getItem('TrulyShield'); if (data && data.length) { res = window.JSON.parse(data); } } catch (err) { window.localStorage.setItem('TrulyShield', window.JSON.stringify(res)); } shieldKeywords = res; }; window.addEventListener('load', (event) => { initShieldKeywords(); addShieldMenu(); if (excludeEditor()) { userShieldMap.set(document.getElementsByClassName('ProfileHeader-name')[0].innerText, false); } if (timer) clearTimeout(timer); timer = setTimeout(checkShielded, 50); let targetNode = getContentElement(); let observer = new window.MutationObserver(moCallback); observer.observe(targetNode, observerConfig); setInterval(checkFullPageModal, 10); }); })();