// ==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);
});
})();