您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
屏蔽过低赞或过高赞的回答。可通过油猴菜单自定义阈值。
// ==UserScript== // @name 知乎首页自定义过滤器(支持过低赞、过高赞、创建时间) // @namespace https://example.com/ // @version 2.0.0 // @description 屏蔽过低赞或过高赞的回答。可通过油猴菜单自定义阈值。 // @author davis.zheng // @match https://www.zhihu.com/ // @run-at document-idle // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @license MIT // ==/UserScript== (() => { 'use strict'; // --- 默认配置 --- const DEFAULT_MIN_UPVOTES = 500; // 默认的最低赞同阈值 (低于此值屏蔽) const DEFAULT_MAX_UPVOTES = Infinity; // 示例值:7000。默认的最高赞同阈值 (高于此值屏蔽, Infinity 表示不设上限) const DEFAULT_MAX_DATE = '9999-12-31'; // 示例值:2024-01-01。 新增:默认的日期阈值 (晚于此日期的回答将被屏蔽) const REMOVE_MODE = true; // true: 直接 remove 容器,不留空白;false: display:none 可按热键恢复 const TOGGLE_HOTKEY = 'h'; // 折叠模式下切换显示/隐藏的热键 const HIDDEN_ATTR = 'data-vote-hidden'; // --- 读取用户自定义配置 --- // 使用 GM_getValue 读取保存的设置,如果未设置,则使用默认值 const minUpvotes = GM_getValue('minUpvotes', DEFAULT_MIN_UPVOTES); const maxUpvotes = GM_getValue('maxUpvotes', DEFAULT_MAX_UPVOTES); const maxDateStr = GM_getValue('maxDate', DEFAULT_MAX_DATE); // <--- 新增:读取保存的日期字符串 const maxDate = new Date(maxDateStr); // <--- 新增:创建一个 Date 对象用于比较 // --- 注册菜单命令,用于自定义阈值 --- GM_registerMenuCommand(`设置屏蔽低赞阈值 (当前: ${minUpvotes})`, () => { const input = prompt('请输入新的【最低】赞数阈值 (低于此值将被屏蔽,输入0则不屏蔽低赞):', minUpvotes); if (input === null) return; // 用户点击了取消 const newValue = parseInt(input, 10); if (!isNaN(newValue) && newValue >= 0) { GM_setValue('minUpvotes', newValue); alert(`低赞屏蔽阈值已设置为 ${newValue}。\n页面将刷新以应用新设置。`); location.reload(); } else { alert('输入无效,请输入一个非负整数。'); } }); GM_registerMenuCommand(`设置屏蔽高赞阈值 (当前: ${maxUpvotes === Infinity ? '不限' : maxUpvotes})`, () => { const currentDisplay = maxUpvotes === Infinity ? '' : maxUpvotes; const input = prompt('请输入新的【最高】赞数阈值 (高于此值将被屏蔽,留空则不屏蔽高赞):', currentDisplay); if (input === null) return; // 用户点击了取消 if (input.trim() === '') { GM_setValue('maxUpvotes', Infinity); alert('已取消高赞屏蔽。\n页面将刷新以应用新设置。'); location.reload(); } else { const newValue = parseInt(input, 10); if (!isNaN(newValue) && newValue >= 0) { GM_setValue('maxUpvotes', newValue); alert(`高赞屏蔽阈值已设置为 ${newValue}。\n页面将刷新以应用新设置。`); location.reload(); } else { alert('输入无效,请输入一个非负整数。'); } } }); GM_registerMenuCommand(`设置屏蔽时间 (晚于 ${maxDateStr} 的回答将被屏蔽)`, () => { const input = prompt('请输入屏蔽日期 (格式 YYYY-MM-DD),晚于此日期的回答将被屏蔽。\n留空则不按时间屏蔽。', maxDateStr); if (input === null) return; // 用户点击了取消 if (input.trim() === '') { // 用户留空,表示不进行时间屏蔽。我们存一个非常遥远的未来日期来实现这个效果。 GM_setValue('maxDate', '9999-12-31'); alert('已取消时间屏蔽。\n页面将刷新以应用新设置。'); location.reload(); return; } // 校验输入是否为 YYYY-MM-DD 格式的有效日期 if (/^\d{4}-\d{2}-\d{2}$/.test(input) && !isNaN(new Date(input))) { GM_setValue('maxDate', input); alert(`屏蔽时间已设置为 ${input}。\n页面将刷新以应用新设置。`); location.reload(); } else { alert('输入格式无效,请输入 YYYY-MM-DD 格式的有效日期。'); } }); // --- 核心功能函数 (与原版基本一致,仅修改判断逻辑) --- // 工具:解析文本里的数字(含 K/万/亿) function parseCount(str) { if (!str) return NaN; str = String(str).replace(/\s+/g, ''); const m = str.match(/([\d.,]+)\s*([kK]|万|亿)?/); if (!m) return NaN; const n = parseFloat((m[1] || '').replace(/,/g, '')); if (isNaN(n)) return NaN; switch (m[2]) { case 'k': case 'K': return Math.round(n * 1000); case '万': return Math.round(n * 10000); case '亿': return Math.round(n * 100000000); default: return n; } } // 从容器读取赞同数 function getVoteCountFromContainer(container) { const meta = container.querySelector('meta[itemprop="upvoteCount"]'); if (meta && meta.content) { const v = parseInt(meta.content, 10); if (!Number.isNaN(v)) return v; } const upBtn = container.querySelector('button.VoteButton:not(.VoteButton--down)'); if (upBtn) { const aria = upBtn.getAttribute('aria-label') || ''; let n = parseCount(aria); if (!isNaN(n)) return n; n = parseCount(upBtn.textContent || ''); if (!isNaN(n)) return n; } return NaN; } // 从容器读取创建日期 function getCreationDateFromContainer(container) { const meta = container.querySelector('meta[itemprop="dateCreated"]'); if (meta && meta.content) { const date = new Date(meta.content); // 确保解析出的日期是有效的 if (!isNaN(date.getTime())) { return date; } } return null; // 如果找不到或日期无效,返回 null } // 查找需要评估的“外层卡片/列表项”容器 function findAllAnswerContainers(root = document) { const sels = [ '.Card.TopstoryItem' ]; const set = new Set(); sels.forEach(sel => { root.querySelectorAll(sel).forEach(el => set.add(el)); }); return Array.from(set); } function hideContainer(container) { if (!container) return; if (REMOVE_MODE) { container.remove(); } else { container.style.display = 'none'; container.setAttribute(HIDDEN_ATTR, '1'); } } function showContainer(container) { if (!container) return; container.style.display = ''; container.removeAttribute(HIDDEN_ATTR); } // !! 核心判断逻辑修改处 !! function evaluateAndHide(root = document) { const containers = findAllAnswerContainers(root); containers.forEach(container => { if (!REMOVE_MODE && container.hasAttribute(HIDDEN_ATTR)) return; const v = getVoteCountFromContainer(container); const creationDate = getCreationDateFromContainer(container); // <--- 新增:获取回答创建日期 let shouldHide = false; // 条件1: 赞同数不符合范围 if (!Number.isNaN(v) && (v < minUpvotes || v > maxUpvotes)) { shouldHide = true; } // 条件2: 回答时间太新 (仅在赞同数符合范围时检查) if (!shouldHide && creationDate && creationDate > maxDate) { // <--- 新增:日期判断逻辑 console.log(creationDate) shouldHide = true; } if (shouldHide) { hideContainer(container); } }); } function init() { evaluateAndHide(); const mo = new MutationObserver(mutations => { let need = false; for (const m of mutations) { if (m.addedNodes && m.addedNodes.length) { need = true; break; } } if (need) { if (evaluateAndHide._tid) cancelAnimationFrame(evaluateAndHide._tid); evaluateAndHide._tid = requestAnimationFrame(() => evaluateAndHide(document)); } }); mo.observe(document.body, { childList: true, subtree: true }); if (!REMOVE_MODE) { window.addEventListener('keydown', e => { if (e.key.toLowerCase() !== TOGGLE_HOTKEY) return; const hidden = document.querySelectorAll(`[${HIDDEN_ATTR}]`); const allHidden = Array.from(hidden).every(el => el.style.display === 'none'); if (allHidden) hidden.forEach(showContainer); else hidden.forEach(hideContainer); }); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();