您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
可以按照标签过滤知乎首页信息流,隐藏含不喜欢标签的回答、文章及视频。并且可以在信息流标题下显示发布时间与编辑时间。
// ==UserScript== // @name 知乎首页信息流过滤 // @namespace https://zhaoji.wang/ // @version 0.2.2 // @description 可以按照标签过滤知乎首页信息流,隐藏含不喜欢标签的回答、文章及视频。并且可以在信息流标题下显示发布时间与编辑时间。 // @author Zhaoji Wang // @license Apache-2.0 // @match https://www.zhihu.com/ // @match https://www.zhihu.com/follow // @icon https://www.google.com/s2/favicons?domain=zhihu.com // @require https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js // @require https://cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.min.js // @require https://cdn.bootcdn.net/ajax/libs/localforage/1.9.0/localforage.min.js // @require https://cdn.bootcdn.net/ajax/libs/dayjs/1.10.6/dayjs.min.js // @require https://cdn.bootcdn.net/ajax/libs/dayjs/1.10.6/locale/zh-cn.min.js // @require https://cdn.bootcdn.net/ajax/libs/dayjs/1.10.6/plugin/duration.min.js // @require https://cdn.bootcdn.net/ajax/libs/dayjs/1.10.6/plugin/relativeTime.min.js // @grant GM_xmlhttpRequest // @connect www.zhihu.com // @connect zhuanlan.zhihu.com // ==/UserScript== "use strict"; $(() => { // 加载 Day.js dayjs.locale("zh-cn"); dayjs.extend(dayjs_plugin_duration); dayjs.extend(dayjs_plugin_relativeTime); // 将 GM_xmlhttpRequest 函数 Promise 化 const get = (url) => new Promise((resolve, reject) => GM_xmlhttpRequest({ method: "GET", url, onload(response) { resolve(response.responseText); }, onerror(error) { reject(error); } }) ); $(".Topstory-mainColumn").prepend('<div id="filter-rules"></div>'); const app = new Vue({ el: "#filter-rules", template: ` <div id="filter-rules" style="background: #fff; box-shadow: 0 1px 3px rgb(18 18 18 / 10%); padding: 20px; border-bottom: 1px solid #f0f2f7; margin-bottom: 10px; border-radius: 2px;" v-if="isLoadConfigDone" > <div class="header" style="position: relative;"> <div style="font-size: 18px; font-weight: 600; line-height: 1.6;"> 过滤规则 </div> <div class="action-bar" style="position: absolute; right: 0; top: 2px;" > <button @click="addRule()" style="line-height: 2; padding: 0 12px; color: #06f; text-align: center; border: 1px solid; border-radius: 3px; cursor: pointer; font-size: 12px;" > 添加标签 </button> <button @click="toggleBarDisplayStatus()" style="line-height: 2; padding: 0 12px; color: #06f; text-align: center; border: 1px solid; border-radius: 3px; cursor: pointer; font-size: 12px;" > {{ barIsShown ? '折叠' : '展开' }} </button> </div> </div> <div v-show="barIsShown" style="line-height: 1.67; margin-top: 9px;" > <div class="rule-tag" title="点击可删除该标签" style="position: relative; display: inline-block; height: 30px; padding: 0 12px; font-size: 14px; line-height: 30px; color: #06f; border-radius: 100px; background: rgba(0,102,255,.1); margin: 3px 5px 3px 0; vertical-align: middle; cursor: pointer;" v-if="rules.length" v-for="(v, i) in rules" :key="v" @click="removeRule(i)" > {{ v }} </div> <p v-if="!rules.length"> 当前尚未设置规则 </p> </div> </div> `, data() { return { isLoadConfigDone: false, rules: [], titles: [], tags: {}, times: {}, barIsShown: true }; }, methods: { async loadConfig() { let config = await localforage.getItem("zhihu-filter-config"); if (!config) { config = await localforage.setItem("zhihu-filter-config", { barIsShown: true, rules: [] }); } this.barIsShown = config.barIsShown; this.rules = config.rules; this.isLoadConfigDone = true; }, async saveConfig() { await localforage.setItem("zhihu-filter-config", { barIsShown: this.barIsShown, rules: this.rules }); }, async toggleBarDisplayStatus() { this.barIsShown = !this.barIsShown; await this.saveConfig(); }, async addRule() { const newTag = prompt("请输入需要被过滤的标签"); if (newTag) { this.rules = Array.from(new Set([...this.rules, newTag])); await this.saveConfig(); } }, async removeRule(index) { this.rules.splice(index, 1); await this.saveConfig(); }, updateTitles() { this.titles = Array.from($(".ContentItem-title a")).map((v) => ({ title: $(v).text(), href: $(v).attr("href") })); setTimeout(this.updateTitles, 100); }, updateTagsAndTimes() { this.titles.forEach(async (v) => { if (!this.tags[v.title]) { if (v.href.includes("question") && !v.href.includes("answer")) { // 知乎问题 const html = await get(v.href); const tags = Array.from($(".QuestionTopic", html)).map((e) => $(e).text() ); const { created: createdTime, updatedTime } = Object.values( JSON.parse( Array.from($(html)).filter( (v) => v.id === "js-initialData" )[0].innerHTML ).initialState.entities.questions )[0]; this.tags[v.title] = tags; this.times[v.title] = { createdTime, updatedTime }; } else if (v.href.includes("question") && v.href.includes("answer")) { // 知乎问题的回答 const html = await get(v.href); const tags = Array.from($(".QuestionTopic", html)).map((e) => $(e).text() ); const { createdTime, updatedTime } = Object.values( JSON.parse( Array.from($(html)).filter( (v) => v.id === "js-initialData" )[0].innerHTML ).initialState.entities.answers )[0]; this.tags[v.title] = tags; this.times[v.title] = { createdTime, updatedTime }; } else if (v.href.includes("zhuanlan")) { // 知乎专栏的文章 const html = await get(v.href); const tags = Array.from($(".Tag.Topic", html)).map((e) => $(e).text() ); const { created: createdTime, updated: updatedTime } = Object.values( JSON.parse( Array.from($(html)).filter( (v) => v.id === "js-initialData" )[0].innerHTML ).initialState.entities.articles )[0]; this.tags[v.title] = tags; this.times[v.title] = { createdTime, updatedTime }; } else if (v.href.includes("zvideo")) { // 知乎视频 const html = await get(v.href); const tags = Array.from($(".ZVideoTag", html)).map((e) => $(e).text() ); const { publishedAt: createdTime, updatedAt: updatedTime } = Object.values( JSON.parse( Array.from($(html)).filter( (v) => v.id === "js-initialData" )[0].innerHTML ).initialState.entities.zvideos )[0]; this.tags[v.title] = tags; this.times[v.title] = { createdTime, updatedTime }; } else { this.tags[v.title] = true; } } }); setTimeout(this.updateTagsAndTimes, 1000); }, updateQuestionsDisplayStatus() { Array.from($(".TopstoryItem")).forEach((v, i) => { const title = $(v).find(".ContentItem-title a").text(); if ( !$(v).is(":hidden") && this.tags[title] && this.tags[title] !== true && this.tags[title].some((tag) => this.rules.includes(tag)) ) { $(v).hide(); console.log("已过滤问题:", title); } }); setTimeout(this.updateQuestionsDisplayStatus, 100); }, updateQuestionsTimeMark() { Array.from($(".TopstoryItem")).forEach((v, i) => { const $title = $(v).find(".ContentItem-title a"); const title = $title.text(); if ( !$(v).is(":hidden") && !$(v).find(".time-mark").length && this.times[title] && this.times[title] !== true ) { const createdTime = this.times[title].createdTime; const updatedTime = this.times[title].updatedTime; const createdTimeStr = `${dayjs .duration(dayjs().diff(this.times[title].createdTime * 1000)) .humanize()}前`; const updatedTimeStr = `${dayjs .duration(dayjs().diff(this.times[title].updatedTime * 1000)) .humanize()}前`; if (createdTime === updatedTime) { $title.parent().after( `<div class="time-mark" style="font-size: 14px; color: #8590a6; line-height: 1.67; margin-top: 5px; font-weight: 400;">发布于 ${createdTimeStr}</div>` ); } else { if (createdTimeStr === updatedTimeStr) { $title.parent().after( `<div class="time-mark" style="font-size: 14px; color: #8590a6; line-height: 1.67; margin-top: 5px; font-weight: 400;">编辑于 ${updatedTimeStr}</div>` ); } else { $title.parent().after( `<div class="time-mark" style="font-size: 14px; color: #8590a6; line-height: 1.67; margin-top: 5px; font-weight: 400;">发布于 ${createdTimeStr} → 编辑于 ${updatedTimeStr}</div>` ); } } } }); setTimeout(this.updateQuestionsTimeMark, 100); } }, async created() { await this.loadConfig(); this.updateTitles(); this.updateTagsAndTimes(); this.updateQuestionsDisplayStatus(); this.updateQuestionsTimeMark(); } }); });