知乎增强

移除登录弹窗、屏蔽首页视频、默认收起回答、快捷收起回答/评论(左键两侧)、快捷回到顶部(右键两侧)、屏蔽用户、屏蔽关键词、移除高亮链接、屏蔽盐选内容、净化搜索热门、净化标题消息、展开问题描述、显示问题作者、置顶显示时间、完整问题时间、区分问题文章、直达问题按钮、默认高清原图、默认站外直链

当前为 2022-12-14 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name 知乎增强
  3. // @name:zh-CN 知乎增强
  4. // @name:zh-TW 知乎增強
  5. // @name:en Zhihu enhancement
  6. // @version 2.2.3
  7. // @author X.I.U
  8. // @description 移除登录弹窗、屏蔽首页视频、默认收起回答、快捷收起回答/评论(左键两侧)、快捷回到顶部(右键两侧)、屏蔽用户、屏蔽关键词、移除高亮链接、屏蔽盐选内容、净化搜索热门、净化标题消息、展开问题描述、显示问题作者、置顶显示时间、完整问题时间、区分问题文章、直达问题按钮、默认高清原图、默认站外直链
  9. // @description:zh-TW 移除登錄彈窗、屏蔽首頁視頻、默認收起回答、快捷收起回答/評論、快捷回到頂部、屏蔽用戶、屏蔽關鍵詞、移除高亮鏈接、屏蔽鹽選內容、淨化搜索熱門、淨化標題消息、置頂顯示時間、完整問題時間、區分問題文章、默認高清原圖、默認站外直鏈...
  10. // @description:en A more personalized Zhihu experience~
  11. // @match *://www.zhihu.com/*
  12. // @match *://zhuanlan.zhihu.com/*
  13. // @exclude https://www.zhihu.com/signin*
  14. // @icon https://static.zhihu.com/heifetz/favicon.ico
  15. // @grant GM_xmlhttpRequest
  16. // @grant GM_registerMenuCommand
  17. // @grant GM_unregisterMenuCommand
  18. // @grant GM_openInTab
  19. // @grant GM_getValue
  20. // @grant GM_setValue
  21. // @grant GM_notification
  22. // @grant GM_info
  23. // @grant window.onurlchange
  24. // @license GPL-3.0 License
  25. // @run-at document-end
  26. // @namespace https://greasyfork.org/scripts/4122051
  27. // @supportURL https://github.com/XIU2/UserScript
  28. // @homepageURL https://github.com/XIU2/UserScript
  29. // ==/UserScript==
  30.  
  31. 'use strict';
  32. var menu_ALL = [
  33. ['menu_defaultCollapsedAnswer', '默认收起回答', '默认收起回答', true],
  34. ['menu_collapsedAnswer', '一键收起回答', '一键收起回答', true],
  35. ['menu_collapsedNowAnswer', '快捷收起回答/评论 (点击两侧空白处)', '快捷收起回答/评论', true],
  36. ['menu_backToTop', '快捷回到顶部 (右键两侧空白处)', '快捷回到顶部', true],
  37. ['menu_blockUsers', '屏蔽指定用户', '屏蔽指定用户', true],
  38. ['menu_customBlockUsers', '自定义屏蔽用户', '自定义屏蔽用户', ['故事档案局', '盐选推荐', '盐选科普', '盐选成长计划', '知乎盐选会员', '知乎盐选创作者', '盐选心理', '盐选健康必修课', '盐选奇妙物语', '盐选生活馆', '盐选职场', '盐选文学甄选', '盐选作者小管家', '盐选博物馆', '盐选点金', '盐选测评室', '盐选科技前沿', '盐选会员精品']],
  39. ['menu_blockKeywords', '屏蔽指定关键词', '屏蔽指定关键词', true],
  40. ['menu_customBlockKeywords', '自定义屏蔽关键词', '自定义屏蔽关键词', []],
  41. ['menu_blockType', '屏蔽指定类别 (视频/文章等)', '勾选 = 屏蔽该类别的信息流', ''],
  42. ['menu_blockTypeVideo', '视频 [首页、搜索页、问题页]', '视频(首页、搜索页、问题页)', true],
  43. ['menu_blockTypeArticle', '文章 [首页、搜索页]', '文章(首页、搜索页)', false],
  44. ['menu_blockTypeTopic', '话题 [搜索页]', '话题(搜索页)', false],
  45. ['menu_blockTypeSearch', '杂志文章、盐选专栏、相关搜索等 [搜索页]', '相关搜索、杂志、盐选等(搜索页)', false],
  46. ['menu_blockYanXuan', '屏蔽盐选内容 [问题页]', '屏蔽盐选内容(问题页)', false],
  47. ['menu_cleanSearch', '净化搜索热门 (默认搜索词及热门搜索)', '净化搜索热门', false],
  48. ['menu_cleanTitles', '净化标题消息 (标题中的私信/消息)', '净化标题提醒', false],
  49. ['menu_questionRichTextMore', '展开问题描述', '展开问题描述', false],
  50. ['menu_publishTop', '置顶显示时间', '置顶显示时间', true],
  51. ['menu_typeTips', '区分问题文章', '区分问题文章', true],
  52. ['menu_toQuestion', '直达问题按钮', '直达问题按钮', true]
  53. ], menu_ID = [];
  54. for (let i=0;i<menu_ALL.length;i++){ // 如果读取到的值为 null 就写入默认值
  55. if (GM_getValue(menu_ALL[i][0]) == null){GM_setValue(menu_ALL[i][0], menu_ALL[i][3])};
  56. }
  57. registerMenuCommand();
  58.  
  59. // 注册脚本菜单
  60. function registerMenuCommand() {
  61. if (menu_ID.length > menu_ALL.length){ // 如果菜单ID数组多于菜单数组,说明不是首次添加菜单,需要卸载所有脚本菜单
  62. for (let i=0;i<menu_ID.length;i++){
  63. GM_unregisterMenuCommand(menu_ID[i]);
  64. }
  65. }
  66. for (let i=0;i<menu_ALL.length;i++){ // 循环注册脚本菜单
  67. menu_ALL[i][3] = GM_getValue(menu_ALL[i][0]);
  68. if (menu_ALL[i][0] === 'menu_customBlockUsers') {
  69. if (menu_value('menu_blockUsers')) menu_ID[i] = GM_registerMenuCommand(`#️⃣ ${menu_ALL[i][1]}`, function(){customBlockUsers()});
  70. } else if (menu_ALL[i][0] === 'menu_customBlockKeywords') {
  71. if (menu_value('menu_blockKeywords')) menu_ID[i] = GM_registerMenuCommand(`#️⃣ ${menu_ALL[i][1]}`, function(){customBlockKeywords()});
  72. } else if (menu_ALL[i][0] === 'menu_blockType') {
  73. menu_ID[i] = GM_registerMenuCommand(`#️⃣ ${menu_ALL[i][1]}`, function(){menu_setting('checkbox', menu_ALL[i][1], menu_ALL[i][2], true, [menu_ALL[i+1], menu_ALL[i+2], menu_ALL[i+3], menu_ALL[i+4], menu_ALL[i+5]])});
  74. } else if (menu_ALL[i][0] != 'menu_blockTypeVideo' && menu_ALL[i][0] != 'menu_blockTypeArticle' && menu_ALL[i][0] != 'menu_blockTypeTopic' && menu_ALL[i][0] != 'menu_blockTypeSearch' && menu_ALL[i][0] != 'menu_blockYanXuan') {
  75. menu_ID[i] = GM_registerMenuCommand(`${menu_ALL[i][3]?'✅':'❌'} ${menu_ALL[i][1]}`, function(){menu_switch(`${menu_ALL[i][3]}`,`${menu_ALL[i][0]}`,`${menu_ALL[i][2]}`)});
  76. }
  77. }
  78. menu_ID[menu_ID.length] = GM_registerMenuCommand('💬 反馈 & 建议', function () {window.GM_openInTab('https://github.com/XIU2/UserScript#xiu2userscript', {active: true,insert: true,setParent: true});window.GM_openInTab('https://greasyfork.org/zh-CN/scripts/419081/feedback', {active: true,insert: true,setParent: true});});
  79. }
  80.  
  81.  
  82. // 菜单开关
  83. function menu_switch(menu_status, Name, Tips) {
  84. if (menu_status == 'true'){
  85. GM_setValue(`${Name}`, false);
  86. GM_notification({text: `已关闭 [${Tips}] 功能\n(点击刷新网页后生效)`, timeout: 3500, onclick: function(){location.reload();}});
  87. }else{
  88. GM_setValue(`${Name}`, true);
  89. GM_notification({text: `已开启 [${Tips}] 功能\n(点击刷新网页后生效)`, timeout: 3500, onclick: function(){location.reload();}});
  90. }
  91. registerMenuCommand(); // 重新注册脚本菜单
  92. };
  93.  
  94.  
  95. // 返回菜单值
  96. function menu_value(menuName) {
  97. for (let menu of menu_ALL) {
  98. if (menu[0] == menuName) {
  99. return menu[3]
  100. }
  101. }
  102. }
  103.  
  104.  
  105. // 脚本设置
  106. function menu_setting(type, title, tips, line, menu) {
  107. let _br = '', _html = `<style class="zhihuE_SettingStyle">.zhihuE_SettingRoot {position: absolute;top: 50%;left: 50%;-webkit-transform: translate(-50%, -50%);-moz-transform: translate(-50%, -50%);-ms-transform: translate(-50%, -50%);-o-transform: translate(-50%, -50%);transform: translate(-50%, -50%);width: auto;min-width: 400px;max-width: 600px;height: auto;min-height: 150px;max-height: 400px;color: #535353;background-color: #fff;border-radius: 3px;}
  108. .zhihuE_SettingBackdrop_1 {position: fixed;top: 0;right: 0;bottom: 0;left: 0;z-index: 203;display: -webkit-box;display: -ms-flexbox;display: flex;-webkit-box-orient: vertical;-webkit-box-direction: normal;-ms-flex-direction: column;flex-direction: column;-webkit-box-pack: center;-ms-flex-pack: center;justify-content: center;overflow-x: hidden;overflow-y: auto;-webkit-transition: opacity .3s ease-out;transition: opacity .3s ease-out;}
  109. .zhihuE_SettingBackdrop_2 {position: absolute;top: 0;right: 0;bottom: 0;left: 0;z-index: 0;background-color: rgba(18,18,18,.65);-webkit-transition: background-color .3s ease-out;transition: background-color .3s ease-out;}
  110. .zhihuE_SettingRoot .zhihuE_SettingHeader {padding: 10px 20px;color: #fff;font-weight: bold;background-color: #3994ff;border-radius: 3px 3px 0 0;}
  111. .zhihuE_SettingRoot .zhihuE_SettingMain {padding: 10px 20px;border-radius: 0 0 3px 3px;}
  112. .zhihuE_SettingHeader span {float: right;cursor: pointer;}
  113. .zhihuE_SettingMain input {margin: 10px 6px 10px 0;cursor: pointer;vertical-align:middle}
  114. .zhihuE_SettingMain label {margin-right: 20px;user-select: none;cursor: pointer;vertical-align:middle}
  115. .zhihuE_SettingMain hr {border: 0.5px solid #f4f4f4;}
  116. [data-theme="dark"] .zhihuE_SettingRoot {color: #adbac7;background-color: #343A44;}
  117. [data-theme="dark"] .zhihuE_SettingHeader {color: #d0d0d0;background-color: #2D333B;}
  118. [data-theme="dark"] .zhihuE_SettingMain hr {border: 0.5px solid #2d333b;}</style>
  119. <div class="zhihuE_SettingBackdrop_1"><div class="zhihuE_SettingBackdrop_2"></div><div class="zhihuE_SettingRoot">
  120. <div class="zhihuE_SettingHeader">${title}<span class="zhihuE_SettingClose" title="点击关闭"><svg class="Zi Zi--Close Modal-closeIcon" fill="currentColor" viewBox="0 0 24 24" width="24" height="24"><path d="M13.486 12l5.208-5.207a1.048 1.048 0 0 0-.006-1.483 1.046 1.046 0 0 0-1.482-.005L12 10.514 6.793 5.305a1.048 1.048 0 0 0-1.483.005 1.046 1.046 0 0 0-.005 1.483L10.514 12l-5.208 5.207a1.048 1.048 0 0 0 .006 1.483 1.046 1.046 0 0 0 1.482.005L12 13.486l5.207 5.208a1.048 1.048 0 0 0 1.483-.006 1.046 1.046 0 0 0 .005-1.482L13.486 12z" fill-rule="evenodd"></path></svg></span></div>
  121. <div class="zhihuE_SettingMain"><p>${tips}</p><hr>`
  122. if (line) _br = '<br>'
  123. for (let i=0; i<menu.length; i++) {
  124. if (GM_getValue(menu[i][0])) {
  125. _html += `<label><input name="zhihuE_Setting" type="checkbox" value="${menu[i][0]}" checked="checked">${menu[i][1]}</label>${_br}`
  126. } else {
  127. _html += `<label><input name="zhihuE_Setting" type="checkbox" value="${menu[i][0]}">${menu[i][1]}</label>${_br}`
  128. }
  129. }
  130. _html += `</div></div></div>`
  131. document.body.insertAdjacentHTML('beforeend', _html); // 插入网页末尾
  132. setTimeout(function() { // 延迟 100 毫秒,避免太快
  133. // 关闭按钮 点击事件
  134. document.querySelector('.zhihuE_SettingClose').onclick = function(){this.parentElement.parentElement.parentElement.remove();document.querySelector('.zhihuE_SettingStyle').remove();}
  135. // 点击周围空白处 = 点击关闭按钮
  136. document.querySelector('.zhihuE_SettingBackdrop_2').onclick = function(event){if (event.target == this) {document.querySelector('.zhihuE_SettingClose').click();};}
  137. // 复选框 点击事件
  138. document.getElementsByName('zhihuE_Setting').forEach(function (checkBox) {
  139. checkBox.addEventListener('click', function(){if (this.checked) {GM_setValue(this.value, true);} else {GM_setValue(this.value, false);}});
  140. })
  141. }, 100)
  142. }
  143.  
  144.  
  145. // 添加收起回答观察器
  146. function getCollapsedAnswerObserver() {
  147. if (!window._collapsedAnswerObserver) {
  148. const observer = new MutationObserver(mutations => {
  149. for (const mutation of mutations) {
  150. if (mutation.target.hasAttribute('script-collapsed')) return
  151. // 短的回答
  152. if (mutation.target.classList.contains('RichContent')) {
  153. for (const addedNode of mutation.addedNodes) {
  154. if (addedNode.nodeType != Node.ELEMENT_NODE) continue
  155. if (addedNode.className != 'RichContent-inner') continue
  156. if (addedNode.offsetHeight < 400) break
  157. //console.log('111',addedNode, addedNode.classList, addedNode.classList.contains('RichContent-inner'), addedNode.offsetHeight, addedNode.textContent.length)
  158. const button = mutation.target.querySelector('.ContentItem-actions.Sticky [data-zop-retract-question]');
  159. if (button) {
  160. mutation.target.setAttribute('script-collapsed', '');
  161. button.click();
  162. return
  163. }
  164. }
  165. // 长的回答
  166. } else if (mutation.target.tagName === 'DIV' && !mutation.target.style.cssText && !mutation.target.className) {
  167. if (mutation.target.parentElement.hasAttribute('script-collapsed')) return
  168. //console.log('222',mutation.target, mutation.target.querySelector('.ContentItem-actions.Sticky [data-zop-retract-question]'))
  169. const button = mutation.target.querySelector('.ContentItem-actions.Sticky [data-zop-retract-question]');
  170. if (button) {
  171. mutation.target.parentElement.setAttribute('script-collapsed', '');
  172. button.click();
  173. return
  174. }
  175. }
  176. }
  177. })
  178.  
  179. observer.start = function() {
  180. if (!this._active) {
  181. this.observe(document, { childList: true, subtree: true });
  182. this._active = true;
  183. }
  184. }
  185. observer.end = function() {
  186. if (this._active) {
  187. this.disconnect();
  188. }
  189. }
  190.  
  191. window.addEventListener('urlchange', function() {
  192. observer[location.href.indexOf('/answer/') === -1 ? 'start' : 'end']();
  193. })
  194. window._collapsedAnswerObserver = observer;
  195. }
  196. return window._collapsedAnswerObserver
  197. }
  198.  
  199.  
  200. // 默认收起回答
  201. function defaultCollapsedAnswer() {
  202. if (!menu_value('menu_defaultCollapsedAnswer')) return
  203. const observer = getCollapsedAnswerObserver();
  204. if (location.href.indexOf('/answer/') === -1) {
  205. observer.start();
  206. }
  207. }
  208.  
  209.  
  210. // 一键收起回答(全部)
  211. function collapsedAnswer() {
  212. if (!menu_value('menu_collapsedAnswer')) return
  213. //console.log('1111', document.querySelector('.CornerAnimayedFlex'))
  214. if (document.querySelector('.CornerAnimayedFlex') && !document.getElementById('collapsed-button')) {
  215. //console.log('2222')
  216. document.head.appendChild(document.createElement('style')).textContent = '.CornerButton{margin-bottom:8px !important;}.CornerButtons{bottom:45px !important;}';
  217. document.querySelector('.CornerAnimayedFlex').insertAdjacentHTML('afterBegin', '<button id="collapsed-button" data-tooltip="收起全部回答" data-tooltip-position="left" data-tooltip-will-hide-on-click="false" aria-label="收起全部回答" type="button" class="' + document.querySelector('.CornerAnimayedFlex>button').className + '"><svg class="ContentItem-arrowIcon is-active" aria-label="收起全部回答" fill="currentColor" viewBox="0 0 24 24" width="24" height="24"><path d="M16.036 19.59a1 1 0 0 1-.997.995H9.032a.996.996 0 0 1-.997-.996v-7.005H5.03c-1.1 0-1.36-.633-.578-1.416L11.33 4.29a1.003 1.003 0 0 1 1.412 0l6.878 6.88c.782.78.523 1.415-.58 1.415h-3.004v7.005z"></path></svg></button>');
  218. document.getElementById('collapsed-button').onclick = function () {
  219. if (location.pathname === '/' || location.pathname === '/hot' || location.pathname === '/follow') {
  220. document.querySelectorAll('.ContentItem-rightButton').forEach(function (el) {
  221. if (el.hasAttribute('data-zop-retract-question')) {
  222. el.click()
  223. }
  224. });
  225. } else {
  226. document.querySelectorAll('[script-collapsed]').forEach(function(scriptCollapsed) {
  227. scriptCollapsed.querySelectorAll('.ContentItem-actions [data-zop-retract-question], .ContentItem-actions.Sticky [data-zop-retract-question]').forEach(function(button) {
  228. button.click();
  229. })
  230. })
  231. document.querySelectorAll('.RichContent:not([script-collapsed]) .ContentItem-actions.Sticky [data-zop-retract-question]').forEach(function(button) {
  232. let el = button.parentElement;
  233. while (!el.classList.contains('RichContent')) {
  234. el = el.parentElement;
  235. }
  236. if (el) {
  237. el.setAttribute('script-collapsed', '');
  238. }
  239. button.click();
  240. })
  241. const observer = getCollapsedAnswerObserver();
  242. observer.start();
  243. if (!menu_value('menu_defaultCollapsedAnswer') && !observer._disconnectListener) {
  244. window.addEventListener('urlchange', function() {
  245. observer.end();
  246. window._collapsedAnswerObserver = null;
  247. })
  248. observer._disconnectListener = true;
  249. }
  250. }
  251. }
  252. }
  253. }
  254.  
  255.  
  256. // 收起当前回答、评论(监听点击事件,点击网页两侧空白处)
  257. function collapsedNowAnswer(selectors) {
  258. backToTop(selectors) // 快捷回到顶部
  259. if (!menu_value('menu_collapsedNowAnswer')) return
  260. document.querySelector(selectors).onclick = function(event){
  261. if (event.target == this) {
  262. // 下面这段主要是 [收起回答],顺便 [收起评论](如果展开了的话)
  263. let rightButton = document.querySelector('.ContentItem-actions.Sticky.RichContent-actions.is-fixed.is-bottom')
  264. if (rightButton) { // 悬浮在底部的 [收起回答](此时正在浏览回答内容 [中间区域])
  265. // 固定的 [收起评论](先看看是否展开评论)
  266. let commentCollapseButton = rightButton.querySelector('button.Button.ContentItem-action.Button--plain.Button--withIcon.Button--withLabel:first-of-type')
  267. //console.log('111')
  268. if (commentCollapseButton && commentCollapseButton.textContent.indexOf('收起评论') > -1) commentCollapseButton.click();
  269. // 再去收起回答
  270. rightButton = rightButton.querySelector('.ContentItem-rightButton[data-zop-retract-question]')
  271. //console.log('222')
  272. if (rightButton) rightButton.click();
  273.  
  274. } else { // 固定在回答底部的 [收起回答](此时正在浏览回答内容 [尾部区域])
  275.  
  276. // 悬浮的 [收起评论](此时正在浏览评论内容 [中间区域])
  277. //if (getXpath('//button[text()="收起评论"]',document.querySelector('.Comments-container'))) {getXpath('//button[text()="收起评论"]',document.querySelector('.Comments-container')).click();console.log('asfaf')}
  278.  
  279. let answerCollapseButton_ = false;
  280. for (let el of document.querySelectorAll('.ContentItem-rightButton[data-zop-retract-question]')) { // 遍历所有回答底部的 [收起] 按钮
  281. if (isElementInViewport(el)) { // 判断该 [收起] 按钮是否在可视区域内
  282. // 固定的 [收起评论](先看看是否展开评论,即存在 [收起评论] 按钮)
  283. let commentCollapseButton = el.parentNode.querySelector('button.Button.ContentItem-action.Button--plain.Button--withIcon.Button--withLabel:first-of-type')
  284. // 如果展开了评论,就收起评论
  285. //console.log('333')
  286. //if (commentCollapseButton && commentCollapseButton.textContent.indexOf('收起评论') > -1) commentCollapseButton.click();
  287. if (commentCollapseButton && commentCollapseButton.textContent.indexOf('收起评论') > -1) {
  288. commentCollapseButton.click();
  289. if (!isElementInViewport(commentCollapseButton)) scrollTo(0,el.offsetTop+50)
  290. }
  291. //console.log('444')
  292. el.click() // 再去收起回答
  293. answerCollapseButton_ = true; // 如果找到并点击收起了,就没必要执行下面的代码了(可视区域中没有 [收起回答] 时)
  294. break
  295. }
  296. }
  297. // 针对完全看不到 [收起回答] 按钮时(如 [头部区域],以及部分明明很长却不显示悬浮横条的回答)
  298. if (!answerCollapseButton_) {
  299. for (let el of document.querySelectorAll('.List-item, .Card.AnswerCard, .Card.TopstoryItem')) { // 遍历所有回答主体元素
  300. if (isElementInViewport_(el)) { // 判断该回答是否在可视区域内
  301. // 固定的 [收起评论](先看看是否展开评论,即存在 [收起评论] 按钮)
  302. let commentCollapseButton = el.querySelector('button.Button.ContentItem-action.Button--plain.Button--withIcon.Button--withLabel:first-of-type')
  303. // 如果展开了评论,就收起评论
  304. //console.log('555',commentCollapseButton)
  305. if (commentCollapseButton && commentCollapseButton.textContent.indexOf('收起评论') > -1) {
  306. commentCollapseButton.click();
  307. if (!isElementInViewport(commentCollapseButton)) scrollTo(0,el.offsetTop+50)
  308. }
  309. let answerCollapseButton__ = el.querySelector('.ContentItem-rightButton[data-zop-retract-question]');
  310. //console.log('666')
  311. if (answerCollapseButton__) answerCollapseButton__.click() // 再去收起回答
  312. break
  313. }
  314. }
  315. }
  316. }
  317.  
  318. // 下面这段只针对 [收起评论](如果展开了的话)
  319. let commentCollapseButton_ = false, commentCollapseButton__ = false;
  320. // 悬浮的 [收起评论](此时正在浏览评论内容 [中间区域])
  321. let commentCollapseButton = getXpath('//button[text()="收起评论"]',document.querySelector('.Comments-container'))
  322. if (commentCollapseButton) {
  323. //console.log('777', commentCollapseButton)
  324. commentCollapseButton.click();
  325. } else { // 固定的 [收起评论](此时正在浏览评论内容 [头部区域])
  326. let commentCollapseButton_1 = document.querySelectorAll('.ContentItem-actions > button.Button.ContentItem-action.Button--plain.Button--withIcon.Button--withLabel:first-of-type, .ContentItem-action > button.Button.Button--plain.Button--withIcon.Button--withLabel:first-of-type')
  327. if (commentCollapseButton_1.length > 0) {
  328. for (let el of commentCollapseButton_1) {
  329. if (el.textContent.indexOf('收起评论') > -1) {
  330. if (isElementInViewport(el)) {
  331. //console.log('888')
  332. el.click()
  333. commentCollapseButton_ = true // 如果找到并点击了,就没必要执行下面的代码了(可视区域中没有 [收起评论] 时)
  334. break
  335. }
  336. }
  337. }
  338. }
  339. if (commentCollapseButton_ == false) { // 可视区域中没有 [收起评论] 时(此时正在浏览评论内容 [头部区域] + [尾部区域](不上不下的,既看不到固定的 [收起评论] 又看不到悬浮的 [收起评论])),需要判断可视区域中是否存在评论元素
  340. let commentCollapseButton_1 = document.querySelectorAll('.Comments-container')
  341. if (commentCollapseButton_1.length > 0) {
  342. for (let el of commentCollapseButton_1) {
  343. if (isElementInViewport(el)) {
  344. let parentElement = findParentElement(el, 'List-item') || findParentElement(el, 'Card '),
  345. commentCollapseButton = parentElement.querySelector('.ContentItem-actions > button.Button.ContentItem-action.Button--plain.Button--withIcon.Button--withLabel:first-of-type')
  346. if (commentCollapseButton.textContent.indexOf('收起评论') > -1) {
  347. //console.log('999')
  348. commentCollapseButton.click()
  349. if (!isElementInViewport(commentCollapseButton)) {console.log(parentElement,parentElement.offsetTop,parentElement.offsetHeight);scrollTo(0,parentElement.offsetTop+parentElement.offsetHeight-50)}
  350. commentCollapseButton__ = true // 如果找到并点击了,就没必要执行下面的代码了(可视区域中没有 评论元素 时)
  351. break
  352. }
  353. }
  354. }
  355. }
  356. if (commentCollapseButton__ == false) { // 如果上面的都没找到,那么就尝试寻找评论末尾的 [评论回复框]
  357. let commentCollapseButton_2 = document.querySelectorAll('.Editable-content')
  358. if (commentCollapseButton_2.length > 0) {
  359. for (let el of commentCollapseButton_2) {
  360. if (isElementInViewport(el)) {
  361. let parentElement = findParentElement(el, 'List-item') || findParentElement(el, 'Card '),
  362. commentCollapseButton = parentElement.querySelector('.ContentItem-actions > button.Button.ContentItem-action.Button--plain.Button--withIcon.Button--withLabel:first-of-type')
  363. //console.log(commentCollapseButton)
  364. if (commentCollapseButton.textContent.indexOf('收起评论') > -1) {
  365. //console.log('101010')
  366. commentCollapseButton.click()
  367. if (!isElementInViewport(commentCollapseButton)) {console.log(parentElement,parentElement.offsetTop,parentElement.offsetHeight);scrollTo(0,parentElement.offsetTop+parentElement.offsetHeight-50)}
  368. break
  369. }
  370. }
  371. }
  372. }
  373. }
  374. }
  375. }
  376. }
  377. }
  378. }
  379.  
  380.  
  381. // 回到顶部(监听点击事件,鼠标右键点击网页两侧空白处)
  382. function backToTop(selectors) {
  383. if (!menu_value('menu_backToTop')) return
  384. document.querySelector(selectors).oncontextmenu = function(event){
  385. if (event.target == this) {
  386. event.preventDefault();
  387. window.scrollTo(0,0)
  388. }
  389. }
  390. }
  391.  
  392.  
  393. //获取元素是否在可视区域(完全可见)
  394. function isElementInViewport(el) {
  395. let rect = el.getBoundingClientRect();
  396. return (
  397. rect.top >= 0 &&
  398. rect.left >= 0 &&
  399. rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
  400. rect.right <= (window.innerWidth || document.documentElement.clientWidth)
  401. );
  402. }
  403. //获取元素是否在可视区域(部分可见)
  404. function isElementInViewport_(el) {
  405. let rect = el.getBoundingClientRect();
  406. return (
  407. rect.top < (window.innerHeight || document.documentElement.clientHeight) &&
  408. rect.bottom > 0
  409. );
  410. }
  411.  
  412.  
  413. // 自定义屏蔽用户
  414. function customBlockUsers() {
  415. let nowBlockUsers = '';
  416. menu_value('menu_customBlockUsers').forEach(function(item){nowBlockUsers += '|' + item})
  417. //console.log(nowBlockUsers.replace('|',''))
  418. let newBlockUsers = prompt('编辑 [自定义屏蔽用户]\n(不同用户名之间使用 "|" 分隔,例如:用户A|用户B|用户C )', nowBlockUsers.replace('|',''));
  419. if (newBlockUsers === '') {
  420. GM_setValue('menu_customBlockUsers', []);
  421. registerMenuCommand(); // 重新注册脚本菜单
  422. } else if (newBlockUsers != null) {
  423. GM_setValue('menu_customBlockUsers', newBlockUsers.split('|'));
  424. registerMenuCommand(); // 重新注册脚本菜单
  425. }
  426. };
  427.  
  428.  
  429. // 屏蔽指定用户
  430. function blockUsers(type) {
  431. if (!menu_value('menu_blockUsers')) return
  432. if (!menu_value('menu_customBlockUsers') || menu_value('menu_customBlockUsers').length < 1) return
  433. switch(type) {
  434. case 'index':
  435. blockUsers_('.Card.TopstoryItem.TopstoryItem-isRecommend', 'Card TopstoryItem TopstoryItem-isRecommend');
  436. break;
  437. case 'question':
  438. blockUsers_question();
  439. break;
  440. case 'search':
  441. blockUsers_search();
  442. break;
  443. case 'topic':
  444. blockUsers_('.List-item.TopicFeedItem', 'List-item TopicFeedItem');
  445. break;
  446. case 'people':
  447. blockUsers_button_people(); // 添加屏蔽用户按钮(用户主页)
  448. break;
  449. }
  450. blockUsers_comment(); // 评论区
  451. blockUsers_button(); // 加入黑名单按钮
  452.  
  453. function blockUsers_(className1, className2) {
  454. // 前几条因为是直接加载的,而不是动态插入网页的,所以需要单独判断
  455. function blockKeywords_now() {
  456. document.querySelectorAll(className1).forEach(function(item1){
  457. let item = item1.querySelector('.ContentItem.AnswerItem, .ContentItem.ArticleItem'); // 用户名所在元素
  458. if (item) {
  459. for (const keyword of menu_value('menu_customBlockUsers')) { // 遍历用户名黑名单
  460. if (keyword != '' && item.dataset.zop.indexOf('authorName":"' + keyword + '",') > -1) { // 找到就删除该信息流
  461. console.log(item.dataset.zop);
  462. item1.hidden = true;
  463. break;
  464. }
  465. }
  466. }
  467. })
  468. }
  469.  
  470. blockKeywords_now();
  471. window.addEventListener('urlchange', function(){
  472. setTimeout(blockKeywords_now, 1000); // 网页 URL 变化后再次执行
  473. })
  474.  
  475. // 这个是监听网页插入事件,用来判断后续网页动态插入的元素
  476. const callback = (mutationsList, observer) => {
  477. for (const mutation of mutationsList) {
  478. for (const target of mutation.addedNodes) {
  479. if (target.nodeType != 1) return
  480. if (target.className === className2) {
  481. let item = target.querySelector('.ContentItem.AnswerItem, .ContentItem.ArticleItem'); // 用户名所在元素
  482. if (item) {
  483. for (const keyword of menu_value('menu_customBlockUsers')) { // 遍历用户名黑名单
  484. if (keyword != '' && item.dataset.zop.indexOf('authorName":"' + keyword + '",') > -1) { // 找到就删除该信息流
  485. console.log(item.dataset.zop);
  486. target.hidden = true;
  487. break;
  488. }
  489. }
  490. }
  491. }
  492. }
  493. }
  494. };
  495. const observer = new MutationObserver(callback);
  496. observer.observe(document, { childList: true, subtree: true });
  497. }
  498.  
  499.  
  500. function blockUsers_question() {
  501. const blockUsers_question_ = (mutationsList, observer) => {
  502. for (const mutation of mutationsList) {
  503. for (const target of mutation.addedNodes) {
  504. if (target.nodeType != 1) return
  505. if (target.className === 'List-item' || target.className === 'Card AnswerCard') {
  506. let item1 = target.querySelector('.ContentItem.AnswerItem');
  507. if (item1) {
  508. menu_value('menu_customBlockUsers').forEach(function(item2){ // 遍历用户黑名单
  509. if (item1.dataset.zop.indexOf('authorName":"' + item2 + '",') > -1) { // 找到就删除该回答
  510. console.log(item1.dataset.zop)
  511. target.hidden = true;
  512. }
  513. })
  514. }
  515. }
  516. }
  517. }
  518. };
  519.  
  520. const blockUsers_question_answer_ = (mutationsList, observer) => {
  521. for (const mutation of mutationsList) {
  522. for (const target of mutation.addedNodes) {
  523. if (target.nodeType != 1) return
  524. target.querySelectorAll('.List-item, .Card.AnswerCard').forEach(function(item){
  525. let item1 = item.querySelector('.ContentItem.AnswerItem');
  526. if (item1) {
  527. menu_value('menu_customBlockUsers').forEach(function(item2){ // 遍历用户黑名单
  528. if (item1.dataset.zop.indexOf('authorName":"' + item2 + '",') > -1) { // 找到就删除该回答
  529. console.log(item1.dataset.zop)
  530. item.hidden = true;
  531. }
  532. })
  533. }
  534. })
  535. }
  536. }
  537. };
  538.  
  539. if (location.pathname.indexOf('/answer/') > -1) { // 回答页(就是只有三个回答的页面)
  540. const observer = new MutationObserver(blockUsers_question_answer_);
  541. observer.observe(document, { childList: true, subtree: true });
  542. } else { // 问题页(可以显示所有回答的页面)
  543. const observer = new MutationObserver(blockUsers_question_);
  544. observer.observe(document, { childList: true, subtree: true });
  545. }
  546.  
  547. // 针对的是打开网页后直接加载的前面几个回答(上面哪些是针对动态加载的回答)
  548. document.querySelectorAll('.List-item, .Card.AnswerCard').forEach(function(item){
  549. let item1 = item.querySelector('.ContentItem.AnswerItem');
  550. if (item1) {
  551. menu_value('menu_customBlockUsers').forEach(function(item2){ // 遍历用户黑名单
  552. if (item1.dataset.zop.indexOf('authorName":"' + item2 + '",') > -1) { // 找到就删除该回答
  553. console.log(item1.dataset.zop)
  554. item.hidden = true;
  555. }
  556. })
  557. }
  558. })
  559. }
  560.  
  561. function blockUsers_search() {
  562. function blockUsers_now() {
  563. if (location.search.indexOf('type=content') === -1) return // 目前只支持搜索页的 [综合]
  564. document.querySelectorAll('.Card.SearchResult-Card[data-za-detail-view-path-module="AnswerItem"], .Card.SearchResult-Card[data-za-detail-view-path-module="PostItem"]').forEach(function(item1){
  565. let item = item1.querySelector('.RichText.ztext.CopyrightRichText-richText b'); // 用户名所在元素
  566. if (item) {
  567. for (const keyword of menu_value('menu_customBlockUsers')) { // 遍历用户名黑名单
  568. if (keyword != '' && item.textContent === keyword) { // 找到就删除该信息流
  569. console.log(item.textContent);
  570. item1.hidden = true;
  571. break;
  572. }
  573. }
  574. }
  575. })
  576. }
  577.  
  578. setTimeout(blockUsers_now, 2000);
  579. window.addEventListener('urlchange', function(){
  580. setTimeout(blockUsers_now, 1000); // 网页 URL 变化后再次执行
  581. })
  582.  
  583. const callback = (mutationsList, observer) => {
  584. if (location.search.indexOf('type=content') === -1) return // 目前只支持搜索页的 [综合]
  585. for (const mutation of mutationsList) {
  586. for (const target of mutation.addedNodes) {
  587. if (target.nodeType != 1) return
  588. let item = target.querySelector('.Card.SearchResult-Card[data-za-detail-view-path-module="AnswerItem"] .RichText.ztext.CopyrightRichText-richText b, .Card.SearchResult-Card[data-za-detail-view-path-module="PostItem"] .RichText.ztext.CopyrightRichText-richText b');
  589. if (item) {
  590. for (const keyword of menu_value('menu_customBlockUsers')) { // 遍历用户名黑名单
  591. if (keyword != '' && item.textContent === keyword) { // 找到就删除该信息流
  592. console.log(item.textContent);
  593. target.hidden = true;
  594. break;
  595. }
  596. }
  597. }
  598. }
  599. }
  600. };
  601. const observer = new MutationObserver(callback);
  602. observer.observe(document, { childList: true, subtree: true });
  603. }
  604.  
  605. function blockUsers_comment() {
  606. const callback = (mutationsList, observer) => {
  607. for (const mutation of mutationsList) {
  608. for (const target of mutation.addedNodes) {
  609. if (target.nodeType != 1) return
  610. let item = target.querySelector('img.Avatar[width="24"]')
  611. if (item) {
  612. //console.log(item)
  613. menu_value('menu_customBlockUsers').forEach(function(item1){ // 遍历用户黑名单
  614. //console.log(item.alt,item1)
  615. if (item.alt === item1) { // 找到就删除该搜索结果
  616. item.parentElement.parentElement.style.display = "none";
  617. }
  618. })
  619.  
  620. // 添加屏蔽用户按钮(点赞、回复等按钮后面)
  621. /*if (item) {
  622. let footer = findParentElement(item, 'CommentItemV2-meta', true).parentElement.querySelector('.CommentItemV2-metaSibling > .CommentItemV2-footer'),
  623. userid = item.parentElement;
  624. if (userid && footer && !footer.lastElementChild.dataset.name) {
  625. userid = userid.href.split('/')[4];
  626. footer.insertAdjacentHTML('beforeend',`<button type="button" data-name="${item.alt}" data-userid="${userid}" class="Button CommentItemV2-hoverBtn Button--plain"><span style="display: inline-flex; align-items: center;">&#8203;<svg class="Zi Zi--Like" fill="currentColor" viewBox="0 0 24 24" width="16" height="16" style="transform: rotate(180deg); margin-right: 5px;"><path d="M18.376 5.624c-3.498-3.499-9.254-3.499-12.752 0-3.499 3.498-3.499 9.254 0 12.752 3.498 3.499 9.254 3.499 12.752 0 3.499-3.498 3.499-9.14 0-12.752zm-1.693 1.693c2.37 2.37 2.596 6.094.678 8.69l-9.367-9.48c2.708-1.919 6.32-1.58 8.69.79zm-9.48 9.48c-2.37-2.37-2.595-6.095-.676-8.69l9.48 9.48c-2.822 1.918-6.433 1.58-8.803-.79z" fill-rule="evenodd"></path></svg></span>屏蔽用户</button>`);
  627. footer.lastElementChild.onclick = function(){blockUsers_button_add(this.dataset.name, this.dataset.userid, false)}
  628. }
  629. }*/
  630. }
  631. }
  632. }
  633. };
  634. const observer = new MutationObserver(callback);
  635. observer.observe(document, { childList: true, subtree: true });
  636. }
  637.  
  638.  
  639. // 添加屏蔽用户按钮(用户信息悬浮框中)
  640. function blockUsers_button() {
  641. const callback = (mutationsList, observer) => {
  642. for (const mutation of mutationsList) {
  643. for (const target of mutation.addedNodes) {
  644. if (target.nodeType != 1) return
  645. //console.log(target, target.className)
  646. if (target.className && (target.className.indexOf('Popover-content Popover-content--top HoverCard-popoverTarget') > -1 || target.className.indexOf('Popover-content Popover-content--bottom HoverCard-popoverTarget') > -1) || target.querySelector('.Popover-content.Popover-content--top.HoverCard-popoverTarget') || target.querySelector('.Popover-content.Popover-content--bottom.HoverCard-popoverTarget')) {
  647. let item = target.querySelector('.MemberButtonGroup.ProfileButtonGroup.HoverCard-buttons'),
  648. item1 = target.querySelector('a.UserLink-link'),
  649. name = item1.textContent,
  650. userid = item1.href.split('/')[4];
  651. if (item && !target.querySelector('button[data-name][data-userid]')) {
  652. item.insertAdjacentHTML('beforeend', `<button type="button" data-name="${name}" data-userid="${userid}" class="Button FollowButton Button--primary Button--red" style="width: 100%;margin: 7px 0 0 0;"><span style="display: inline-flex; align-items: center;">​<svg class="Zi Zi--Plus FollowButton-icon" fill="currentColor" viewBox="0 0 24 24" width="1.2em" height="1.2em"><path d="M18.376 5.624c-3.498-3.499-9.254-3.499-12.752 0-3.499 3.498-3.499 9.254 0 12.752 3.498 3.499 9.254 3.499 12.752 0 3.499-3.498 3.499-9.14 0-12.752zm-1.693 1.693c2.37 2.37 2.596 6.094.678 8.69l-9.367-9.48c2.708-1.919 6.32-1.58 8.69.79zm-9.48 9.48c-2.37-2.37-2.595-6.095-.676-8.69l9.48 9.48c-2.822 1.918-6.433 1.58-8.803-.79z" fill-rule="evenodd"></path></svg></span>屏蔽用户</button>`);
  653. item.lastElementChild.onclick = function(){blockUsers_button_add(this.dataset.name, this.dataset.userid, false)}
  654. }
  655. }
  656. }
  657. }
  658. };
  659. const observer = new MutationObserver(callback);
  660. observer.observe(document, { childList: true, subtree: true });
  661. }
  662.  
  663. // 添加屏蔽用户按钮(用户主页)
  664. function blockUsers_button_people() {
  665. let item = document.querySelector('.MemberButtonGroup.ProfileButtonGroup.ProfileHeader-buttons'), // 获取按钮元素位置
  666. name = document.querySelector('.ProfileHeader-name').firstChild.textContent, // 获取用户名
  667. users = menu_value('menu_customBlockUsers'), // 读取屏蔽列表
  668. userid = location.href.split('/')[4];
  669. for (let num = 0;num<users.length;num++) { // 判断是否已存在
  670. if (users[num] === name) { // 已存在
  671. document.querySelectorAll('.Button.Button--primary.Button--red').forEach(function(item){item.style.display = 'none';}) // 隐藏知乎自带的已屏蔽按钮
  672. item.insertAdjacentHTML('beforeend', `<button type="button" data-name="${name}" data-userid="${userid}" class="Button FollowButton Button--primary Button--red" style="margin: 0 0 0 12px;"><span style="display: inline-flex; align-items: center;">​<svg class="Zi Zi--Plus FollowButton-icon" fill="currentColor" viewBox="0 0 24 24" width="1.2em" height="1.2em"><path d="M18.376 5.624c-3.498-3.499-9.254-3.499-12.752 0-3.499 3.498-3.499 9.254 0 12.752 3.498 3.499 9.254 3.499 12.752 0 3.499-3.498 3.499-9.14 0-12.752zm-1.693 1.693c2.37 2.37 2.596 6.094.678 8.69l-9.367-9.48c2.708-1.919 6.32-1.58 8.69.79zm-9.48 9.48c-2.37-2.37-2.595-6.095-.676-8.69l9.48 9.48c-2.822 1.918-6.433 1.58-8.803-.79z" fill-rule="evenodd"></path></svg></span>取消屏蔽</button>`);
  673. item.lastElementChild.onclick = function(){blockUsers_button_del(this.dataset.name, this.dataset.userid, true)}
  674. return
  675. }
  676. };
  677. if (item) {
  678. item.insertAdjacentHTML('beforeend', `<button type="button" data-name="${name}" data-userid="${userid}" class="Button FollowButton Button--primary Button--red" style="margin: 0 0 0 12px;"><span style="display: inline-flex; align-items: center;">​<svg class="Zi Zi--Plus FollowButton-icon" fill="currentColor" viewBox="0 0 24 24" width="1.2em" height="1.2em"><path d="M18.376 5.624c-3.498-3.499-9.254-3.499-12.752 0-3.499 3.498-3.499 9.254 0 12.752 3.498 3.499 9.254 3.499 12.752 0 3.499-3.498 3.499-9.14 0-12.752zm-1.693 1.693c2.37 2.37 2.596 6.094.678 8.69l-9.367-9.48c2.708-1.919 6.32-1.58 8.69.79zm-9.48 9.48c-2.37-2.37-2.595-6.095-.676-8.69l9.48 9.48c-2.822 1.918-6.433 1.58-8.803-.79z" fill-rule="evenodd"></path></svg></span>屏蔽用户</button>`);
  679. item.lastElementChild.onclick = function(){blockUsers_button_add(this.dataset.name, this.dataset.userid, true)}
  680. }
  681. }
  682.  
  683. // 屏蔽用户按钮绑定事件(添加)
  684. function blockUsers_button_add(name, userid, reload) {
  685. if (!name || !userid) return
  686. let users = menu_value('menu_customBlockUsers'), // 读取屏蔽列表
  687. index = users.indexOf(name);
  688. if (index === -1) {
  689. users.push(name); // 追加用户名
  690. GM_setValue('menu_customBlockUsers', users); // 写入屏蔽列表
  691. // 加入知乎自带的黑名单(和本脚本互补~
  692. GM_xmlhttpRequest({url: `https://www.zhihu.com/api/v4/members/${userid}/actions/block`,method: 'POST',timeout: 2000});
  693. // 是否刷新本页
  694. if (reload) {
  695. setTimeout(function(){location.reload()}, 200); // 刷新网页,延迟 200 毫秒,避免知乎反应慢~
  696. } else {
  697. GM_notification({text: `该用户已被屏蔽~\n刷新网页后生效~`, timeout: 3000});
  698. }
  699. } else {
  700. GM_notification({text: `该用户已经被屏蔽啦,无需重复屏蔽~`, timeout: 3000});
  701. }
  702. }
  703.  
  704.  
  705. // 屏蔽用户按钮绑定事件(删除)
  706. function blockUsers_button_del(name, userid, reload) {
  707. if (!name || !userid) return
  708. let users = menu_value('menu_customBlockUsers'), // 读取屏蔽列表
  709. index = users.indexOf(name);
  710. if (index > -1) {
  711. users.splice(index, 1); // 移除用户名
  712. GM_setValue('menu_customBlockUsers', users); // 写入屏蔽列表
  713. // 移除知乎自带的黑名单
  714. GM_xmlhttpRequest({url: `https://www.zhihu.com/api/v4/members/${userid}/actions/block`,method: 'DELETE',timeout: 2000});
  715. // 是否刷新本页
  716. if (reload) {
  717. setTimeout(function(){location.reload()}, 200); // 刷新网页,延迟 200 毫秒,避免知乎反应慢~
  718. } else {
  719. GM_notification({text: `该用户已取消屏蔽啦~\n刷新网页后生效~`, timeout: 3000});
  720. }
  721. } else {
  722. GM_notification({text: `没有在屏蔽列表中找到该用户...`, timeout: 3000});
  723. }
  724. }
  725. }
  726.  
  727.  
  728. // 自定义屏蔽关键词(标题)
  729. function customBlockKeywords() {
  730. let nowBlockKeywords = '';
  731. menu_value('menu_customBlockKeywords').forEach(function(item){nowBlockKeywords += '|' + item})
  732. let newBlockKeywords = prompt('编辑 [自定义屏蔽关键词]\n(不同关键词之间使用 "|" 分隔,例如:关键词A|关键词B|关键词C \n(关键词不区分大小写,支持表情如:[捂脸]|[飙泪笑]', nowBlockKeywords.replace('|',''));
  733. if (newBlockKeywords === '') {
  734. GM_setValue('menu_customBlockKeywords', []);
  735. registerMenuCommand(); // 重新注册脚本菜单
  736. } else if (newBlockKeywords != null) {
  737. GM_setValue('menu_customBlockKeywords', newBlockKeywords.split('|'));
  738. registerMenuCommand(); // 重新注册脚本菜单
  739. }
  740. };
  741.  
  742.  
  743. // 屏蔽指定关键词
  744. function blockKeywords(type) {
  745. if (!menu_value('menu_blockKeywords')) return
  746. if (!menu_value('menu_customBlockKeywords') || menu_value('menu_customBlockKeywords').length < 1) return
  747. switch(type) {
  748. case 'index':
  749. blockKeywords_('.Card.TopstoryItem.TopstoryItem-isRecommend', 'Card TopstoryItem TopstoryItem-isRecommend');
  750. break;
  751. case 'topic':
  752. blockKeywords_('.List-item.TopicFeedItem', 'List-item TopicFeedItem');
  753. break;
  754. case 'people':
  755. blockKeywords_('.List-item', 'List-item');
  756. break;
  757. case 'collection':
  758. blockKeywords_('.Card.CollectionDetailPageItem', 'Card CollectionDetailPageItem');
  759. break;
  760. case 'search':
  761. blockKeywords_search();
  762. break;
  763. case 'comment':
  764. blockKeywords_comment();
  765. break;
  766. }
  767.  
  768.  
  769. function blockKeywords_(className1, className2) {
  770. // 前几条因为是直接加载的,而不是动态插入网页的,所以需要单独判断
  771. function blockKeywords_now() {
  772. if (location.pathname === '/hot') {
  773. console.log('222222222222')
  774. document.querySelectorAll('.HotItem').forEach(function(item1){blockKeywords_1(item1, 'h2.HotItem-title');})
  775. } else {
  776. document.querySelectorAll(className1).forEach(function(item1){blockKeywords_1(item1, 'h2.ContentItem-title meta[itemprop="name"], meta[itemprop="headline"]');})
  777. }
  778. }
  779.  
  780. blockKeywords_now();
  781. window.addEventListener('urlchange', function(){
  782. setTimeout(blockKeywords_now, 1000); // 网页 URL 变化后再次执行
  783. })
  784.  
  785. // 这个是监听网页插入事件,用来判断后续网页动态插入的元素
  786. const callback = (mutationsList, observer) => {
  787. for (const mutation of mutationsList) {
  788. for (const target of mutation.addedNodes) {
  789. if (target.nodeType != 1) return
  790. if (target.className === className2) {blockKeywords_1(target, 'h2.ContentItem-title meta[itemprop="name"], meta[itemprop="headline"]');}
  791. }
  792. }
  793. };
  794. const observer = new MutationObserver(callback);
  795. observer.observe(document, { childList: true, subtree: true });
  796. }
  797.  
  798.  
  799. function blockKeywords_search() {
  800. function blockKeywords_now() {
  801. if (location.search.indexOf('type=content') === -1) return // 目前只支持搜索页的 [综合]
  802. document.querySelectorAll('.HotLanding-contentItem, .Card.SearchResult-Card[data-za-detail-view-path-module="AnswerItem"], .Card.SearchResult-Card[data-za-detail-view-path-module="PostItem"]').forEach(function(item1){blockKeywords_1(item1, 'a[data-za-detail-view-id]');})
  803. }
  804.  
  805. setTimeout(blockKeywords_now, 2000);
  806. window.addEventListener('urlchange', function(){
  807. setTimeout(blockKeywords_now, 1000); // 网页 URL 变化后再次执行
  808. })
  809.  
  810. const callback = (mutationsList, observer) => {
  811. if (location.search.indexOf('type=content') === -1) return // 目前只支持搜索页的 [综合]
  812. for (const mutation of mutationsList) {
  813. for (const target of mutation.addedNodes) {
  814. if (target.nodeType != 1) return
  815. if (target.className === 'Card SearchResult-Card' && (target.dataset.zaDetailViewPathModule === 'AnswerItem' || target.dataset.zaDetailViewPathModule === 'PostItem')) {blockKeywords_1(target, 'a[data-za-detail-view-id]');}
  816. }
  817. }
  818. };
  819. const observer = new MutationObserver(callback);
  820. observer.observe(document, { childList: true, subtree: true });
  821. }
  822.  
  823.  
  824. function blockKeywords_comment() {
  825. function filterComment(comment) {
  826. let content = comment.querySelector('.RichText'); // 寻找评论文字所在元素
  827. let texts = [content.textContent.toLowerCase()]; // 因为要针对评论中的表情,所以需要整个数组并全部转为小写(用来不区分大小写)
  828. for (let i = 0; i < content.children.length; i++) { // 该条针对的是评论中的表情
  829. let emoticonValue = content.children[i].getAttribute('data-zhihu-emoticon'); // 确定是表情就将其添加到稍后遍历的数组中
  830. if (emoticonValue) {
  831. texts.push(emoticonValue)
  832. }
  833. }
  834.  
  835. let keywords = menu_value('menu_customBlockKeywords');
  836. for (const text of texts) {
  837. for (const keyword of keywords) { // 遍历关键词黑名单
  838. if (keyword != '' && text.indexOf(keyword.toLowerCase()) > -1) { // 找到就删除该评论
  839. console.log('已屏蔽评论:' + text);
  840. content.textContent = '[该评论已屏蔽]';
  841. break;
  842. }
  843. }
  844. }
  845. }
  846.  
  847. const callback = (mutationsList, observer) => {
  848. for (const mutation of mutationsList) {
  849. for (const target of mutation.addedNodes) {
  850. if (target.nodeType != 1) return
  851. for (const node of target.querySelectorAll('*')) {
  852. if (node.className === 'CommentItemV2-metaSibling') filterComment(node);
  853. }
  854. }
  855. }
  856. };
  857. const observer = new MutationObserver(callback);
  858. observer.observe(document, { childList: true, subtree: true });
  859. }
  860.  
  861. function blockKeywords_1(item1, css) {
  862. let item = item1.querySelector(css); // 标题所在元素
  863. console.log(item)
  864. if (item) {
  865. for (const keyword of menu_value('menu_customBlockKeywords')) { // 遍历关键词黑名单
  866. let text = item.content || item.textContent;
  867. if (keyword != '' && text.toLowerCase().indexOf(keyword.toLowerCase()) > -1) { // 找到就删除该信息流
  868. console.log(text);
  869. item1.hidden = true;
  870. item1.style.display = 'none';
  871. break;
  872. }
  873. }
  874. }
  875. }
  876. }
  877.  
  878.  
  879. // 屏蔽指定类别(视频/文章等)
  880. function blockType(type) {
  881. let name;
  882. // 一开始加载的信息流 + 添加标签样式
  883. if (type === 'search') { // 搜索页
  884. if (!menu_value('menu_blockTypeVideo') && !menu_value('menu_blockTypeArticle') && !menu_value('menu_blockTypeTopic') && !menu_value('menu_blockTypeSearch')) return
  885. if (menu_value('menu_blockTypeSearch') && location.pathname === '/search') setTimeout(function(){document.querySelector('.RelevantQuery').parentElement.parentElement.hidden = true;}, 2000)
  886. name = 'h2.ContentItem-title a:not(.zhihu_e_toQuestion), a.KfeCollection-PcCollegeCard-link, h2.SearchTopicHeader-Title a'
  887. addSetInterval_(name);
  888. } else if (type === 'question') { // 问题页
  889. if (!menu_value('menu_blockTypeVideo')) return
  890. document.lastChild.appendChild(document.createElement('style')).textContent = `.VideoAnswerPlayer, .VideoAnswerPlayer-video, .VideoAnswerPlayer-iframe {display: none !important;}`;
  891. name = '.VideoAnswerPlayer'
  892. document.querySelectorAll(name).forEach(function(item){blockType_(item);})
  893. } else { // 首页
  894. if (!menu_value('menu_blockTypeVideo') && !menu_value('menu_blockTypeArticle')) return
  895. if (menu_value('menu_blockTypeVideo')) document.lastChild.appendChild(document.createElement('style')).textContent = `.Card .ZVideoItem-video, nav.TopstoryTabs > a[aria-controls="Topstory-zvideo"] {display: none !important;}`;
  896. name = 'h2.ContentItem-title a:not(.zhihu_e_toQuestion)'
  897. document.querySelectorAll(name).forEach(function(item){blockType_(item);})
  898. }
  899.  
  900. // 后续加载的信息流
  901. const observer = new MutationObserver(mutationsList => {
  902. for (const mutation of mutationsList) {
  903. for (const target of mutation.addedNodes) {
  904. if (target.nodeType != 1) return
  905. blockType_(target.querySelector(name));
  906. }
  907. }
  908. });
  909. observer.observe(document, { childList: true, subtree: true });
  910.  
  911. window.addEventListener('urlchange', function(){
  912. addSetInterval_(name);
  913. // 移除相关搜索
  914. if (menu_value('menu_blockTypeSearch') && location.pathname === '/search' && location.search.indexOf('type=content') > -1) setTimeout(function(){document.querySelector('.RelevantQuery').parentElement.parentElement.hidden = true;}, 1500)
  915. })
  916.  
  917. function blockType_(titleA) {
  918. if (!titleA) return // 判断是否为真
  919. //console.log(titleA.href)
  920. if (location.pathname === '/search') { // 搜索页
  921. if (location.search.indexOf('type=content') === -1) return // 仅限搜索页的 [综合]
  922. if (titleA.href.indexOf('/zvideo/') > -1 || titleA.href.indexOf('video.zhihu.com') > -1) { // 如果是视频
  923. if (menu_value('menu_blockTypeVideo')) findParentElement(titleA, 'Card').hidden = true;
  924. } else if (titleA.href.indexOf('zhuanlan.zhihu.com') > -1) { // 如果是文章
  925. if (menu_value('menu_blockTypeArticle')) findParentElement(titleA, 'Card SearchResult-Card').hidden = true;
  926. } else if (titleA.href.indexOf('/topic/') > -1) { // 如果是话题
  927. if (menu_value('menu_blockTypeTopic')) findParentElement(titleA, 'Card SearchResult-Card').hidden = true;
  928. } else if (titleA.href.indexOf('/market/') > -1) { // 如果是杂志文章等乱七八糟的
  929. if (menu_value('menu_blockTypeSearch')) findParentElement(titleA, 'Card SearchResult-Card').hidden = true;
  930. }
  931. } else if (location.pathname.indexOf('/question/') > -1) { // 问题页
  932. if (menu_value('menu_blockTypeVideo')) findParentElement(titleA, 'List-item').hidden = true;
  933. } else { // 首页
  934. if (titleA.href.indexOf('/zvideo/') > -1 || titleA.href.indexOf('video.zhihu.com') > -1) { // 如果是视频
  935. if (menu_value('menu_blockTypeVideo')) findParentElement(titleA, 'Card TopstoryItem TopstoryItem-isRecommend').hidden = true;
  936. } else if (titleA.href.indexOf('/answer/') > -1) { // 如果是问题(视频回答)
  937. if (findParentElement(titleA, 'ContentItem AnswerItem').querySelector('.VideoAnswerPlayer')) {
  938. if (menu_value('menu_blockTypeVideo')) findParentElement(titleA, 'Card TopstoryItem TopstoryItem-isRecommend').hidden = true;
  939. }
  940. } else if (titleA.href.indexOf('/education/video-course/') > -1) { // 如果是視頻課程
  941. if (menu_value('menu_blockTypeVideo')) findParentElement(titleA, 'Card TopstoryItem TopstoryItem-isRecommend').hidden = true;
  942. } else if (titleA.href.indexOf('zhuanlan.zhihu.com') > -1) { // 如果是文章
  943. if (menu_value('menu_blockTypeArticle')) findParentElement(titleA, 'Card TopstoryItem TopstoryItem-isRecommend').hidden = true;
  944. }
  945. }
  946. }
  947.  
  948. function addSetInterval_(A) {
  949. let timer = setInterval(function(){
  950. let aTag = document.querySelectorAll(A);
  951. if (aTag.length > 0) {
  952. clearInterval(timer);
  953. aTag.forEach(function(item){blockType_(item);})
  954. }
  955. });
  956. }
  957. }
  958.  
  959.  
  960. // 寻找父元素
  961. function findParentElement(item, className, type = false) {
  962. if (item.parentElement) {
  963. //console.log(item.parentElement)
  964. if (type) { // true = 完全一致,false = 包含即可
  965. if (item.parentElement.className && item.parentElement.className === className) {
  966. //console.log(item.parentElement.className)
  967. return item.parentElement;
  968. } else {
  969. let temp = findParentElement(item.parentElement, className, true)
  970. if (temp) return temp
  971. }
  972. } else {
  973. if (item.parentElement.className && item.parentElement.className.indexOf(className) > -1) {
  974. return item.parentElement;
  975. } else {
  976. let temp = findParentElement(item.parentElement, className)
  977. if (temp) return temp
  978. }
  979. }
  980. }
  981. return
  982. }
  983.  
  984.  
  985. // 移除高亮链接
  986. function removeHighlightLink() {
  987. const callback = (mutationsList, observer) => {
  988. for (const mutation of mutationsList) {
  989. for (const target of mutation.addedNodes) {
  990. if (target.nodeType != 1 || target.tagName != 'A') break
  991. if (target.dataset.zaNotTrackLink && target.href.indexOf('https://www.zhihu.com/search?q=') > -1) {
  992. target.parentElement.replaceWith(target.textContent);
  993. }
  994. }
  995. }
  996. };
  997. const observer = new MutationObserver(callback);
  998. observer.observe(document, { childList: true, subtree: true });
  999.  
  1000. // 针对的是打开网页后直接加载的前面几个回答(上面哪些是针对动态加载的回答)
  1001. document.querySelectorAll('span > a[data-za-not-track-link][href^="https://www.zhihu.com/search?q="]').forEach(e => e.parentElement.replaceWith(e.textContent))
  1002. }
  1003.  
  1004.  
  1005. // 屏蔽盐选内容
  1006. function blockYanXuan() {
  1007. if (!menu_value('menu_blockYanXuan')) return
  1008. const blockYanXuan_question = (mutationsList, observer) => {
  1009. for (const mutation of mutationsList) {
  1010. for (const target of mutation.addedNodes) {
  1011. if (target.nodeType != 1) return
  1012. if (target.className === 'List-item' || target.className === 'Card AnswerCard') {
  1013. if (target.querySelector('.KfeCollection-AnswerTopCard-Container, .KfeCollection-PurchaseBtn')) {
  1014. target.hidden = true;
  1015. }
  1016. }
  1017. }
  1018. }
  1019. };
  1020.  
  1021. const blockYanXuan_question_answer = (mutationsList, observer) => {
  1022. for (const mutation of mutationsList) {
  1023. for (const target of mutation.addedNodes) {
  1024. if (target.nodeType != 1) return
  1025. target.querySelectorAll('.List-item, .Card.AnswerCard').forEach(function(item){
  1026. if (item.querySelector('.KfeCollection-AnswerTopCard-Container, .KfeCollection-PurchaseBtn')) {
  1027. item.hidden = true;
  1028. }
  1029. })
  1030. }
  1031. }
  1032. };
  1033.  
  1034. if (location.pathname.indexOf('/answer/') > -1) { // 回答页(就是只有三个回答的页面)
  1035. const observer = new MutationObserver(blockYanXuan_question_answer);
  1036. observer.observe(document, { childList: true, subtree: true });
  1037. } else { // 问题页(可以显示所有回答的页面)
  1038. const observer = new MutationObserver(blockYanXuan_question);
  1039. observer.observe(document, { childList: true, subtree: true });
  1040. }
  1041.  
  1042. // 针对的是打开网页后直接加载的前面几个回答(上面哪些是针对动态加载的回答)
  1043. document.querySelectorAll('.List-item, .Card.AnswerCard').forEach(function(item){
  1044. if (item.querySelector('.KfeCollection-AnswerTopCard-Container, .KfeCollection-PurchaseBtn')) {
  1045. item.hidden = true;
  1046. }
  1047. })
  1048. }
  1049.  
  1050.  
  1051. // 区分问题文章
  1052. function addTypeTips() {
  1053. if (!menu_value('menu_typeTips')) return
  1054. let style = `font-weight: bold;font-size: 13px;padding: 1px 4px 0;border-radius: 2px;display: inline-block;vertical-align: top;margin: ${(location.pathname === '/search') ? '2' : '4'}px 4px 0 0;`
  1055. document.body.appendChild(document.createElement('style')).textContent = `/* 区分问题文章 */
  1056. .AnswerItem .ContentItem-title a:not(.zhihu_e_toQuestion)::before {content:'问题';color: #f68b83;background-color: #f68b8333;${style}}
  1057. .TopstoryQuestionAskItem .ContentItem-title a:not(.zhihu_e_toQuestion)::before {content:'问题';color: #ff5a4e;background-color: #ff5a4e33;${style}}
  1058. .ZVideoItem .ContentItem-title a::before, .ZvideoItem .ContentItem-title a::before {content:'视频';color: #00BCD4;background-color: #00BCD433;${style}}
  1059. .ArticleItem .ContentItem-title a::before {content:'文章';color: #2196F3;background-color: #2196F333;${style}}`;
  1060. }
  1061.  
  1062.  
  1063. // 直达问题按钮
  1064. function addToQuestion() {
  1065. if (!menu_value('menu_toQuestion')) return
  1066.  
  1067. // 一开始加载的信息流 + 添加按钮样式
  1068. if (location.pathname === '/search') {
  1069. document.lastChild.appendChild(document.createElement('style')).textContent = `a.zhihu_e_toQuestion {font-size: 13px !important;font-weight: normal !important;padding: 1px 6px 0 !important;border-radius: 2px !important;display: inline-block !important;vertical-align: top !important;height: 20.67px !important;line-height: 20.67px !important;margin-top: 2px !important;}`;
  1070. addSetInterval_('h2.ContentItem-title a:not(.zhihu_e_tips)');
  1071. } else {
  1072. document.lastChild.appendChild(document.createElement('style')).textContent = `a.zhihu_e_toQuestion {font-size: 13px !important;font-weight: normal !important;padding: 1px 6px 0 !important;border-radius: 2px !important;display: inline-block !important;vertical-align: top !important;margin-top: 4px !important;}`;
  1073. document.querySelectorAll('h2.ContentItem-title a:not(.zhihu_e_tips)').forEach(function(item){addTypeTips_(item);})
  1074. }
  1075.  
  1076. // 后续加载的信息流
  1077. const observer = new MutationObserver(mutationsList => {
  1078. for (const mutation of mutationsList) {
  1079. for (const target of mutation.addedNodes) {
  1080. if (target.nodeType != 1) return
  1081. addTypeTips_(target.querySelector('h2.ContentItem-title a:not(.zhihu_e_tips)'));
  1082. }
  1083. }
  1084. });
  1085. observer.observe(document, { childList: true, subtree: true });
  1086.  
  1087. window.addEventListener('urlchange', function(){
  1088. addSetInterval_('h2.ContentItem-title a:not(.zhihu_e_tips)');
  1089. })
  1090.  
  1091. function addTypeTips_(titleA) {
  1092. if (!titleA) return // 判断是否为真
  1093. if (titleA.parentElement.querySelector('a.zhihu_e_toQuestion')) return // 判断是否已添加
  1094. if (titleA.textContent.indexOf('?') != -1) { // 把问题末尾英文问好 [?] 的替换为中文问好 [?],这样按钮与标题之间的间距就刚刚好~
  1095. titleA.innerHTML = titleA.innerHTML.replace('?', "?")
  1096. }
  1097. if (/answer\/\d+/.test(titleA.href)) { // 如果是指向回答的问题(而非指向纯问题的链接)
  1098. titleA.insertAdjacentHTML('afterend', `<a class="zhihu_e_toQuestion VoteButton" href="${titleA.parentElement.querySelector('meta[itemprop="url"]').content}" target="_blank">直达问题</a>`);
  1099. }
  1100. }
  1101.  
  1102. function addSetInterval_(A) {
  1103. let timer = setInterval(function(){
  1104. let aTag = document.querySelectorAll(A);
  1105. if (aTag.length > 0) {
  1106. clearInterval(timer);
  1107. aTag.forEach(function(item){addTypeTips_(item);})
  1108. }
  1109. });
  1110. }
  1111. }
  1112.  
  1113.  
  1114. // 展开问题描述
  1115. function questionRichTextMore() {
  1116. if (!menu_value('menu_questionRichTextMore')) return
  1117. let button = document.querySelector('button.QuestionRichText-more');
  1118. if (button) button.click()
  1119. }
  1120.  
  1121.  
  1122. // 知乎免登录
  1123. function removeLogin() {
  1124. const removeLoginModal = (mutationsList, observer) => {
  1125. for (const mutation of mutationsList) {
  1126. for (const target of mutation.addedNodes) {
  1127. if (target.nodeType != 1) return
  1128. if (target.querySelector('.signFlowModal')) {
  1129. let button = target.querySelector('.Button.Modal-closeButton.Button--plain');
  1130. if (button) button.click();
  1131. } else if (getXpath('//button[text()="立即登录/注册"]',target)) {
  1132. target.remove();
  1133. }
  1134. }
  1135. }
  1136. };
  1137.  
  1138. // 未登录时才会监听并移除登录弹窗
  1139. if(location.hostname === 'zhuanlan.zhihu.com') { // 如果是文章页
  1140. if (!document.querySelector('.ColumnPageHeader-profile>.AppHeader-menu')) { // 未登录
  1141. const observer = new MutationObserver(removeLoginModal);
  1142. observer.observe(document, { childList: true, subtree: true });
  1143. if (getXpath('//button[text()="登录/注册"]')) getXpath('//button[text()="登录/注册"]').outerHTML = '<a class="Button AppHeader-login Button--blue" href="https://www.zhihu.com/signin" target="_blank">登录/注册</a>'; // [登录] 按钮跳转至登录页面
  1144. }
  1145. } else { // 不是文章页
  1146. if (!document.querySelector('.AppHeader-profile>.AppHeader-menu')) { // 未登录
  1147. const observer = new MutationObserver(removeLoginModal);
  1148. observer.observe(document, { childList: true, subtree: true });
  1149. document.lastElementChild.appendChild(document.createElement('style')).textContent = '.Question-mainColumnLogin, button.AppHeader-login {display: none !important;}'; // 屏蔽问题页中间的登录提示
  1150. if (getXpath('//button[text()="登录/注册"]')) getXpath('//button[text()="登录/注册"]').outerHTML = '<a class="Button AppHeader-login Button--blue" href="https://www.zhihu.com/signin" target="_blank">登录/注册</a>'; // [登录] 按钮跳转至登录页面
  1151. }
  1152. }
  1153. }
  1154.  
  1155.  
  1156. // 净化标题消息
  1157. function cleanTitles() {
  1158. if (!menu_value('menu_cleanTitles')) return
  1159.  
  1160. // 方案一
  1161. const elTitle = document.head.querySelector('title');
  1162. console.log(elTitle)
  1163. const original = elTitle.textContent;
  1164. const observer = new MutationObserver(function() {
  1165. if (elTitle.textContent != original) { // 避免重复执行
  1166. elTitle.textContent = original;
  1167. }
  1168. });
  1169. observer.observe(elTitle, { childList: true });
  1170.  
  1171. // 方案二
  1172. // if (Reflect.getOwnPropertyDescriptor(document, 'title')) {
  1173. // const elTitle = document.head.querySelector('title');
  1174. // const original = elTitle.textContent;
  1175. // const observer = new MutationObserver(function() {
  1176. // if (elTitle.textContent != original) { // 避免重复执行
  1177. // elTitle.textContent = original;
  1178. // }
  1179. // });
  1180. // observer.observe(elTitle, { childList: true });
  1181. // } else {
  1182. // const title = document.title;
  1183. // Reflect.defineProperty(document, 'title', {
  1184. // set: () => {},
  1185. // get: () => title,
  1186. // });
  1187. // }
  1188. }
  1189.  
  1190.  
  1191. // 净化搜索热门
  1192. function cleanSearch() {
  1193. if (!menu_value('menu_cleanSearch')) return
  1194.  
  1195. const el = document.querySelector('.SearchBar-input > input');
  1196. const observer = new MutationObserver((mutationsList, observer) => {
  1197. if (mutationsList[0].attributeName === 'placeholder' && mutationsList[0].target.placeholder != '') mutationsList[0].target.placeholder = '';
  1198. });
  1199. el.placeholder = '';
  1200. observer.observe(el, { attributes: true });
  1201. document.documentElement.appendChild(document.createElement('style')).textContent = '.AutoComplete-group > .SearchBar-label:not(.SearchBar-label--history), .AutoComplete-group > [id^="AutoComplete2-topSearch-"] {display: none !important;}';
  1202. }
  1203.  
  1204.  
  1205. // 快捷关闭悬浮评论(监听点击事件,点击网页两侧空白处)
  1206. function closeFloatingComments() {
  1207. const closeFloatingCommentsModal = (mutationsList, observer) => {
  1208. for (const mutation of mutationsList) {
  1209. for (const target of mutation.addedNodes) {
  1210. if (target.nodeType != 1) return
  1211. let button = document.querySelector('button[aria-label="关闭"]');
  1212. if (button) {button.parentElement.parentElement.onclick = function(event){if (event.target.parentElement == this) {button.click();}}}
  1213. }
  1214. }
  1215. };
  1216. const observer = new MutationObserver(closeFloatingCommentsModal);
  1217. observer.observe(document, { childList: true, subtree: true });
  1218. }
  1219.  
  1220.  
  1221. // 监听 XMLHttpRequest 事件
  1222. /*function EventXMLHttpRequest() {
  1223. var _send = window.XMLHttpRequest.prototype.send
  1224. function sendReplacement(data) {
  1225. addTypeTips();
  1226. return _send.apply(this, arguments);
  1227. }
  1228. window.XMLHttpRequest.prototype.send = sendReplacement;
  1229. }*/
  1230.  
  1231.  
  1232. // 自定义 urlchange 事件(用来监听 URL 变化)
  1233. function addUrlChangeEvent() {
  1234. history.pushState = ( f => function pushState(){
  1235. var ret = f.apply(this, arguments);
  1236. window.dispatchEvent(new Event('pushstate'));
  1237. window.dispatchEvent(new Event('urlchange'));
  1238. return ret;
  1239. })(history.pushState);
  1240.  
  1241. history.replaceState = ( f => function replaceState(){
  1242. var ret = f.apply(this, arguments);
  1243. window.dispatchEvent(new Event('replacestate'));
  1244. window.dispatchEvent(new Event('urlchange'));
  1245. return ret;
  1246. })(history.replaceState);
  1247.  
  1248. window.addEventListener('popstate',()=>{
  1249. window.dispatchEvent(new Event('urlchange'))
  1250. });
  1251. }
  1252.  
  1253.  
  1254. function getXpath(xpath, contextNode, doc = document) {
  1255. contextNode = contextNode || doc;
  1256. try {
  1257. const result = doc.evaluate(xpath, contextNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
  1258. // 应该总是返回一个元素节点
  1259. return result.singleNodeValue && result.singleNodeValue.nodeType === 1 && result.singleNodeValue;
  1260. } catch (err) {
  1261. throw new Error(`无效 Xpath: ${xpath}`);
  1262. }
  1263. }
  1264.  
  1265.  
  1266. // 显示问题作者
  1267. function question_author() {
  1268. if (document.querySelector('.BrandQuestionSymbol, .QuestionAuthor')) return
  1269. let qJson = JSON.parse(document.querySelector('#js-initialData').textContent).initialState.entities.questions[/\d+/.exec(location.pathname)[0]].author,
  1270. html = `<div class="BrandQuestionSymbol"><a class="BrandQuestionSymbol-brandLink" href="/people/${qJson.urlToken}"><img role="presentation" src="${qJson.avatarUrl}" class="BrandQuestionSymbol-logo" alt=""><span class="BrandQuestionSymbol-name">${qJson.name}</span></a><div class="BrandQuestionSymbol-divider" style="margin-left: 5px;margin-right: 10px;"></div></div>`;
  1271. //html = `<div class="QuestionAuthor"><div class="AuthorInfo AuthorInfo--plain" itemprop="author" itemscope="" itemtype="http://schema.org/Person"><div class="AuthorInfo"><span class="UserLink AuthorInfo-avatarWrapper"><div class="Popover"><div id="Popover18-toggle" aria-haspopup="true" aria-expanded="false" aria-owns="Popover18-content"><a class="UserLink-link" data-za-detail-view-element_name="User" target="_blank" href="${qJson.urlToken}"><img class="Avatar AuthorInfo-avatar" width="24" height="24" src="${qJson.avatarUrl}"></a></div></div></span><div class="AuthorInfo-content"><div class="AuthorInfo-head"><span class="UserLink AuthorInfo-name"><div class="Popover"><div id="Popover19-toggle" aria-haspopup="true" aria-expanded="false" aria-owns="Popover19-content"><a class="UserLink-link" data-za-detail-view-element_name="User" target="_blank" href="${qJson.urlToken}">${qJson.name}</a></div></div></span></div></div></div></div></div>`
  1272. document.querySelector('.QuestionHeader-topics').insertAdjacentHTML('beforebegin', html);
  1273. //document.querySelector('.QuestionPage h1.QuestionHeader-title').insertAdjacentHTML('afterend', html);
  1274. }
  1275.  
  1276.  
  1277. // [完整显示时间 + 置顶显示时间] 功能修改自:https://greasyfork.org/scripts/402808(从 JQuery 改为原生 JavaScript,且精简、优化了代码)
  1278. // 完整显示时间 + 置顶显示时间
  1279. function topTime_(css, classs) {
  1280. document.querySelectorAll(css).forEach(function(_this) {
  1281. let t = _this.querySelector('.ContentItem-time'); if (!t) return
  1282. if (!(t.classList.contains('full')) && t.querySelector('span') && t.querySelector('span').textContent != null) {
  1283. // 完整显示时间
  1284. topTime_allTime(t)
  1285. // 发布时间置顶
  1286. topTime_publishTop(t, _this, classs)
  1287. }
  1288. });
  1289. }
  1290.  
  1291.  
  1292. // 完整显示时间 + 置顶显示时间 - 文章
  1293. function topTime_post() {
  1294. let t = document.querySelector('.ContentItem-time:not(.xiu-time)'); if (!t) return
  1295. // 完整显示时间
  1296. if (t.textContent.indexOf('编辑于') > -1 && !(t.classList.contains('xiu-time'))) {
  1297. let tt = t.textContent;
  1298. t.click();
  1299. t.textContent = (t.textContent + ' ,' + tt)
  1300. t.classList.add('xiu-time');
  1301. }
  1302.  
  1303. //发布时间置顶
  1304. if (menu_value('menu_publishTop') && !(document.querySelector('.Post-Header > .ContentItem-time')) && !(document.querySelector('.ContentItem-meta > .ContentItem-time'))) {
  1305. let temp_time = t.cloneNode(true);
  1306. temp_time.style.padding = '0px';
  1307. document.querySelector('.Post-Header').insertAdjacentElement('beforeEnd', temp_time);
  1308. }
  1309. }
  1310.  
  1311.  
  1312. // 完整显示时间
  1313. function topTime_allTime(t) {
  1314. if (t.textContent.indexOf('发布于') > -1 && t.textContent.indexOf('编辑于') == -1) {
  1315. t.querySelector('span').textContent = (t.querySelector('span').dataset.tooltip);
  1316. t.classList.add('full');
  1317. } else if (t.textContent.indexOf('发布于') == -1 && t.textContent.indexOf('编辑于') > -1) {
  1318. t.querySelector('span').textContent = (t.querySelector('span').dataset.tooltip) + ' ,' + (t.querySelector('span').textContent);
  1319. t.classList.add('full');
  1320. }
  1321. }
  1322.  
  1323.  
  1324. // 发布时间置顶
  1325. function topTime_publishTop(t, _this, _class) {
  1326. if (!menu_value('menu_publishTop')) return
  1327. if (!t.parentNode.classList.contains(_class)) {
  1328. let temp_time = t.cloneNode(true);
  1329. temp_time.style.padding = '0px';
  1330. // 对于较短的回答,隐藏回答底部的时间
  1331. if (_this.offsetHeight < 400) t.style.display = 'none';
  1332. _this.querySelector('.' + _class).insertAdjacentElement('beforeEnd', temp_time);
  1333. }
  1334. }
  1335.  
  1336.  
  1337. // 问题创建时间
  1338. function question_time() {
  1339. if (!(document.querySelector('.QuestionPage .QuestionHeader-side .QuestionTime-xiu'))) {
  1340. document.querySelector('.QuestionPage .QuestionHeader-side').insertAdjacentHTML('beforeEnd', '<div class="QuestionTime-xiu" style="color: #9098ac; margin-top: 5px; font-size: 13px; font-style: italic;"><p>创建时间:' + getUTC8(new Date(document.querySelector('.QuestionPage > meta[itemprop=dateCreated]').content)) + '</p><p>最后编辑:' + getUTC8(new Date(document.querySelector('.QuestionPage > meta[itemprop=dateModified]').content)) + '</p></div>');
  1341. }
  1342. }
  1343.  
  1344.  
  1345. // UTC 标准时转 UTC+8 北京时间,修改自:https://greasyfork.org/zh-CN/scripts/402808(精简)
  1346. function getUTC8(t) {
  1347. return (t.getFullYear() + '-' + (((t.getMonth() + 1) < 10) ? ('0' + (t.getMonth() + 1)) : (t.getMonth() + 1)) + '-' + ((t.getDate() < 10) ? ('0' + t.getDate()) : t.getDate()) + '\xa0\xa0' + ((t.getHours() < 10) ? ('0' + t.getHours()) : t.getHours()) + ':' + ((t.getMinutes() < 10) ? ('0' + t.getMinutes()) : t.getMinutes()) + ':' + ((t.getSeconds() < 10) ? ('0' + t.getSeconds()) : t.getSeconds()));
  1348. }
  1349.  
  1350.  
  1351. // 默认站外直链,修改自:https://greasyfork.org/scripts/402808(从 JQuery 改为原生 JavaScript,且精简、优化了代码)
  1352. function directLink () {
  1353. document.querySelectorAll('a.external[href*="link.zhihu.com/?target="], a.LinkCard[href*="link.zhihu.com/?target="]:not(.MCNLinkCard):not(.ZVideoLinkCard):not(.ADLinkCardContainer)').forEach(function (_this) {_this.href = decodeURIComponent(_this.href.substring(_this.href.indexOf('link.zhihu.com/?target=') + 23));});
  1354. }
  1355.  
  1356.  
  1357. // 默认高清原图,修改自:https://greasyfork.org/scripts/402808(从 JQuery 改为原生 JavaScript,且精简、优化了代码)
  1358. function originalPic(){
  1359. document.querySelectorAll('img[data-original]:not(.comment_sticker):not(.Avatar)').forEach(function(one){if (one.src != one.dataset.original) {one.src = one.dataset.original}});
  1360. }
  1361.  
  1362.  
  1363. // 默认折叠邀请,修改自:https://greasyfork.org/scripts/402808(从 JQuery 改为原生 JavaScript,且精简、优化了代码)
  1364. function questionInvitation(){
  1365. let time = setInterval(function(){
  1366. let q = document.querySelector('.QuestionInvitation-content'); if (!q) return
  1367. clearInterval(time);
  1368. q.style.display = 'none';
  1369. document.querySelector('.QuestionInvitation-title').innerHTML = document.querySelector('.QuestionInvitation-title').innerText + '<span style="cursor: pointer; font-size: 14px; color: #919aae;"> 展开/折叠</span>'
  1370. // 点击事件(展开/折叠)
  1371. document.querySelector('.Topbar').onclick = function(){
  1372. let q = document.querySelector('.QuestionInvitation-content')
  1373. if (q.style.display == 'none') {
  1374. q.style.display = ''
  1375. } else {
  1376. q.style.display = 'none'
  1377. }
  1378. }
  1379. });
  1380. }
  1381.  
  1382.  
  1383. (function() {
  1384. if (window.onurlchange === undefined) {addUrlChangeEvent();} // Tampermonkey v4.11 版本添加的 onurlchange 事件 grant,可以监控 pjax 等网页的 URL 变化
  1385. window.addEventListener('urlchange', function(){ // 针对的是从单个回答页跳转到完整回答页时
  1386. //console.log(location.pathname)
  1387. if (location.pathname.indexOf('question') > -1 && location.pathname.indexOf('waiting') === -1 && location.pathname.indexOf('/answer/') === -1) { // 回答页 //
  1388. setTimeout(function(){
  1389. collapsedNowAnswer('.QuestionPage'); // 收起当前回答 + 快捷返回顶部
  1390. collapsedNowAnswer('.Question-main'); // 收起当前回答 + 快捷返回顶部
  1391. questionRichTextMore(); // 展开问题描述
  1392. blockUsers('question'); // 屏蔽指定用户
  1393. blockYanXuan(); // 屏蔽盐选内容
  1394. }, 300);
  1395. } else if (location.pathname == '/') {
  1396. setTimeout(()=>{
  1397. blockUsers('index'); // 屏蔽指定用户
  1398. blockKeywords('index'); // 屏蔽指定关键词
  1399. blockType(); // 屏蔽指定类别(视频/文章等)
  1400. }, 500);
  1401. } else if (location.pathname == '/hot') {
  1402. setTimeout(()=>{blockKeywords('index');}, 500);// 屏蔽指定关键词
  1403. }
  1404. })
  1405.  
  1406. removeLogin(); // 移除登录弹窗,Violentmonkey 不能延迟执行这个
  1407. cleanTitles(); // 净化标题消息,不能延迟执行
  1408. // Violentmonkey 比 Tampermonkey 加载更早,会导致一些元素还没加载,因此需要延迟一会儿
  1409. // Tampermonkey 4.18.0 版本可能需要延迟一会执行
  1410. if (GM_info.scriptHandler === 'Violentmonkey' || (GM_info.scriptHandler === 'Tampermonkey' && parseFloat(GM_info.version.slice(0,4)) >= 4.18)) {
  1411. setTimeout(start, 300);
  1412. } else {
  1413. start();
  1414. }
  1415.  
  1416. function start(){
  1417. removeHighlightLink(); // 移除高亮链接
  1418. setInterval(originalPic,100); // 默认高清原图
  1419. setInterval(directLink, 100); // 默认站外直链
  1420. if (location.hostname != 'zhuanlan.zhihu.com') {
  1421. if (location.pathname.indexOf('/column/') === -1) cleanSearch(); //净化搜索热门
  1422. collapsedAnswer(); // 一键收起回答
  1423. }
  1424. closeFloatingComments(); // 快捷关闭悬浮评论(监听点击事件,点击网页两侧空白处)
  1425. blockKeywords('comment'); // 屏蔽指定关键词(评论)
  1426.  
  1427.  
  1428. if (location.pathname.indexOf('question') > -1 && location.href.indexOf('/log') == -1) { // 回答页 //
  1429. if (location.pathname.indexOf('waiting') == -1) {
  1430. collapsedNowAnswer('.QuestionPage'); // 收起当前回答 + 快捷返回顶部
  1431. collapsedNowAnswer('.Question-main'); // 收起当前回答 + 快捷返回顶部
  1432. questionRichTextMore(); // 展开问题描述
  1433. blockUsers('question'); // 屏蔽指定用户
  1434. blockYanXuan(); // 屏蔽盐选内容
  1435. blockType('question'); // 屏蔽指定类别(视频/文章等)
  1436. defaultCollapsedAnswer(); // 默认收起回答
  1437. }
  1438. setInterval(function(){topTime_('.ContentItem.AnswerItem', 'ContentItem-meta')}, 300); // 置顶显示时间
  1439. setTimeout(function(){question_time(); question_author()}, 100); //问题创建时间 + 显示问题作者
  1440. questionInvitation(); // 默认折叠邀请
  1441.  
  1442. } else if (location.pathname === '/search') { // 搜索结果页 //
  1443. collapsedNowAnswer('main div'); // 收起当前回答 + 快捷返回顶部
  1444. collapsedNowAnswer('.Search-container'); // 收起当前回答 + 快捷返回顶部
  1445. setInterval(function(){topTime_('.ContentItem.AnswerItem, .ContentItem.ArticleItem', 'SearchItem-meta')}, 300); // 置顶显示时间
  1446. addTypeTips(); // 区分问题文章
  1447. addToQuestion(); // 直达问题按钮
  1448. blockUsers('search'); // 屏蔽指定用户
  1449. blockKeywords('search'); // 屏蔽指定关键词
  1450. blockType('search'); // 屏蔽指定类别(视频/文章等)
  1451.  
  1452.  
  1453. } else if (location.pathname.indexOf('/topic/') > -1) { // 话题页 //
  1454. if (location.pathname.indexOf('/hot') > -1 || location.href.indexOf('/top-answers') > -1) { // 仅限 [讨论] [精华]
  1455. collapsedNowAnswer('main.App-main'); // 收起当前回答 + 快捷返回顶部
  1456. setInterval(function(){topTime_('.ContentItem.AnswerItem, .ContentItem.ArticleItem', 'ContentItem-meta')}, 300); // 置顶显示时间
  1457. addTypeTips(); // 区分问题文章
  1458. addToQuestion(); // 直达问题按钮
  1459. blockUsers('topic'); // 屏蔽指定用户
  1460. blockKeywords('topic'); // 屏蔽指定关键词
  1461. }
  1462.  
  1463. } else if (location.hostname === 'zhuanlan.zhihu.com'){ // 文章 //
  1464. backToTop('article.Post-Main.Post-NormalMain'); // 快捷返回顶部
  1465. backToTop('div.Post-Sub.Post-NormalSub'); // 快捷返回顶部
  1466. setTimeout(topTime_post, 300); // 置顶显示时间
  1467. blockUsers(); // 屏蔽指定用户
  1468.  
  1469.  
  1470. } else if (location.pathname.indexOf('/column/') > -1) { // 专栏 //
  1471. setTimeout(function(){
  1472. collapsedAnswer(); // 一键收起回答
  1473. collapsedNowAnswer('main div'); // 收起当前回答 + 快捷返回顶部
  1474. setInterval(function(){topTime_('.ContentItem.AnswerItem, .ContentItem.ArticleItem', 'ContentItem-meta')}, 300); // 置顶显示时间
  1475. blockUsers(); // 屏蔽指定用户
  1476. }, 300);
  1477.  
  1478.  
  1479. } else if (location.pathname.indexOf('/people/') > -1 || location.href.indexOf('org') > -1) { // 用户主页 //
  1480. if (location.pathname.split('/').length === 3) addTypeTips();addToQuestion(); // 区分问题文章、直达问题按钮
  1481. collapsedNowAnswer('main div'); // 收起当前回答 + 快捷返回顶部
  1482. collapsedNowAnswer('.Profile-main'); // 收起当前回答 + 快捷返回顶部
  1483. setInterval(function(){topTime_('.ContentItem.AnswerItem, .ContentItem.ArticleItem', 'ContentItem-meta')}, 300); // 置顶显示时间
  1484. blockUsers('people'); // 屏蔽指定用户
  1485. blockKeywords('people'); // 屏蔽指定关键词
  1486.  
  1487.  
  1488. } else if (location.pathname.indexOf('/collection/') > -1) { // 收藏夹 //
  1489. addTypeTips(); // 区分问题文章
  1490. addToQuestion(); // 直达问题按钮
  1491. collapsedNowAnswer('main'); // 收起当前回答 + 快捷返回顶部
  1492. collapsedNowAnswer('.CollectionsDetailPage'); // 收起当前回答 + 快捷返回顶部
  1493. setInterval(function(){topTime_('.ContentItem.AnswerItem, .ContentItem.ArticleItem', 'ContentItem-meta')}, 300); // 置顶显示时间
  1494. blockKeywords('collection'); // 屏蔽指定关键词
  1495.  
  1496.  
  1497. } else { // 首页 //
  1498. // 解决屏蔽类别后,因为首页信息流太少而没有滚动条导致无法加载更多内容的问题
  1499. document.lastElementChild.appendChild(document.createElement('style')).textContent = '.Topstory-container {min-height: 1500px;}';
  1500. if (menu_value('menu_blockTypeVideo')) document.lastChild.appendChild(document.createElement('style')).textContent = `.Card .ZVideoItem-video, nav.TopstoryTabs > a[aria-controls="Topstory-zvideo"] {display: none !important;}`;
  1501.  
  1502. collapsedNowAnswer('main div'); // 收起当前回答 + 快捷返回顶部
  1503. collapsedNowAnswer('.Topstory-container'); // 收起当前回答 + 快捷返回顶部
  1504. setInterval(function(){topTime_('.TopstoryItem', 'ContentItem-meta')}, 300); // 置顶显示时间
  1505. addTypeTips(); // 区分问题文章
  1506. addToQuestion(); // 直达问题按钮
  1507. if (location.pathname == '/') {
  1508. blockUsers('index'); // 屏蔽指定用户
  1509. blockKeywords('index'); // 屏蔽指定关键词
  1510. blockType(); // 屏蔽指定类别(视频/文章等)
  1511. } else if (location.pathname == '/hot') {
  1512. blockKeywords('index'); // 屏蔽指定关键词
  1513. } else {
  1514. blockUsers();
  1515. }
  1516. }
  1517. }
  1518. })();