简书优化

支持手机端和PC端、屏蔽广告、优化浏览体验、重定向链接、全文居中、自动展开全文、允许复制文字、劫持唤醒/跳转App、自定义屏蔽元素等

  1. // ==UserScript==
  2. // @name 简书优化
  3. // @namespace https://github.com/WhiteSevs/TamperMonkeyScript
  4. // @version 2025.2.12
  5. // @author WhiteSevs
  6. // @description 支持手机端和PC端、屏蔽广告、优化浏览体验、重定向链接、全文居中、自动展开全文、允许复制文字、劫持唤醒/跳转App、自定义屏蔽元素等
  7. // @license GPL-3.0-only
  8. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAAXNSR0IArs4c6QAAEK1JREFUeF7tXQt0VMUZ/ububgIJjwDJDWqpj6L4LEJbpT0C1r7UFmu1WLJBq7XUHjXZDWDVUytgpYq2ujdUW/FxrMfcCFSPrdTa1laqbVGhalF8AfVZQjbkAQSSze7eKbMQIHt378y9e/eVzJyTg+795///+ea78/937twZAlmGNAJkSLdeNh6SAEOcBJIAkgD5RYAuXqxsaX/JN7K00lsa8/g8nog3Evf4okbE5/H4vIqh+BQl7o1T4iPE8BqJfz3eOI158uv5QOseg8QMqsQ8hEbZv17QqOGLxQzqiXmJETUMbyzqiUcj8dLYcG88ujuyIzZx3JlRsnixkc925GQEoLNne1qqPVN8XmWSYeB4ABMVQiYaoMcTYGw+Aci3bQp0KCCbDUq3KMBmKNjSh/hbR4VWvpIL37JCgLbg3KkGjU8jlEwhCk6nFFMAFNQdmwtwM7FBQSMEWEcI2WAY2KgoyrNVoUdbMtGZqq6rBNhxbc2Jhle5mlB6NZUd7mpfEWAbKFZAUVa4SQRXCNA+f86EeNxzNUCvATDS1ZZLZQMQYEQwKO5XFOU+N4iQMQFaA/7LCMgygI6XfZU7BBIjAugtVVrzfZlYzYgAbcGa+ZSSX2TigKybIQKELFdDTfVOtTgmQGvQfx+h+IFTw7Keqwg8p2r6OU40OiJAOODfCuA4JwZlnSwhQBBWQ3q1Xe22CRAO+ltBodo1JOVzgQB9Q9WaT7NjyRYBwgH/3wB80Y4BKZtjBAi9SQ01LxW1KkyAcLC2EZTWiSqWcnlF4BJV01eLeCBEgLZAzVUU5NciCqVMQSDQo4DMrNSa1vO84RLgwCTPy/I5nwdlwV1/WtX0r/O84hIgHKi9DaA38BTJ6wWJADcUWBIgMbfvIS/L6d2C7FwBp+haVWu2TNotCSATPwGMC12E4Ao1pD+czs20BGCvdEGNl+VbvULvYa5/61VNP8M2AcL1/gAIQlz1UqDgEaCEnFUdavpnKkfTjwABfxMF/AXfOumgCAI3qJq+zBYBWgP+7QSwPbcs4o2VTPm5FwmpoAD2PvOEkGwmQqL+9PzjLzC6d2diKnt1KZ5SG/ULhAnQGqj5NAH5T/Y8Sq953KJGKGPGcU13LrsBsZaPuHKZCPiOPxkV1/yYqyLy2kvY9XAjVy5fAgToqNL0lKCmDAH5iv/eo47GmOt+xsWp783XsHPFnVy5TAVGzpmHYdPO5qrpXvUQev71V65cXgUMY6q6/LFXk31IQ4CaRhCS83n/8vO/jbKvfouL0+7H7kfvi2u5cpkIEJ8Plbc/CHj4a1k7ftqAeHs4E3NZr0sJLq4O6aaYmZoAAf8qALOz7lWSgbE33gFP9VGWZo1dXWi/mS09zG4pnfp5jLrsWq6R6Ja3sOeZx7lydgWYXjcLBb2mWmu+V2wECNY+D0qnu+kAT1fZOd9A+QU1PDHXr7cFa1PqHP3D61Fy4qddtyeqMJ1fovVTyN2qavpPxAgQ8G9mH29kYMxWVY96BCoCi6CU535BcSqgh50xAyP9V9lqg9vCrhOA4gG1UZ8nSgD2PDPC7Ual0zey5gcYdubMXJkbYCcZaOIrQUXDEniP/GRe/Ok36joBgDWqps/iEoB9q9fW+W48V60vnXwGRl0RyJU5k51koMu+fAHKv/GdvPmTNQJQ+oLa2DyDS4DNdeeVjlbG9OYCARZjR13ZAHbX5ascToCSU6di9PcaAEXJlzsH7WZhBFinavoXuATYvvDSciUa7842Ar5jJmLU9xdAGTGKayo5I/ZNPMmyjp0MuuuXtyZ0KRVjMfb620GGl9v2h1XwTjgWpHRY2rp2fGJK+v3iOiMusEHV9M9xCdAZvLwiSvs6xfXal/R+4pjEsO8Zx19cHH3vXXRpSwYYqQo1WRNg85voukd4XWRC17hFGpQxldzGRP/7DroabzHJVVx7E6yI2fPc0+j+nbXfXOOZCbymajr7SHdAMc0D7FpQU9kbI22Z2Upfm82tDz9nFkgJf9g3utrRvtj80cuo79ahdMo0Sxc7f/5jxD5+X6gZFcElYCMSr7DJHjbpk6qM+GYthn/x/LQqjD3d6FhSB9rXxzOTlesU2FSt6adyCdAWnHsEpcY2t70omXQqymfVgN39oiVdHCw5ZQpGz1toqWbvn5/EnqetF8ayO5bduSKFxmLYsfC7aUVFfNq98kH0rmMr6/NS3lE1/UQuAbbVz/6kl/g+cNNFO0AzuyIvV3hhIN76P3Tc9qO0zSg/fzbKvnqhUDP7Nr2CnffzP4GsXPagdR6w9W10Lf+pkM0sCP1X1fRPcQnQ2uA/jhhgn365VuwQgMVJFi95ZfRVP0LJSZMtxbpCixF9n81pDSx2/Nm98gH0rnuO507i+uh5C1ByylRL2Z2/Xoa+tzcK6XNTiIB8WKU1Hc0lQFtg7iQK4203jYsAHvtgK/b+/RlEXvmXkOkR374cw8/6iqVs95OPomftHx0RIPbRe4mkzU72zvKbsnMvtvSJhSUWnvJQWlRNP5JLgPaGmlPiBnnDTQetCND37ib0vvR3RP6dcsVSWjfKvjQL5bPmWLqZLpRY+cM6nPnTu/4F2xCwxJQlqFYl8vq/sevBu2zrzrQCBXZUa3oVlwDhoP90UJjeG2fiQDLgLJuObn0bfa9vAAPESRn2uekYWftDy6pGVwfaF5s7JBUB+ja9up+IG7kf06S16Z1wHMYssI7xxs5OtC/iv2V0ggmnTpeq6WO4BGhrmPNZaijOUUjhBQOcJVys0/v/QNmirkOFxXMW160Kuzv7J0hKJp0G37FswzHrsifFsrF+AsT+9yH6Xl+PyMYNiG37kKeKe52UlaNsxte4cql84lbKXGCPqumm9zumeYDWYM3nCSVigVjQKU/VeMTbtnOlxyxcyn1M3Pmr29D3TmYRKjFhQxREN2/i+jSIBCKqppumKk0ECDfMnQ7DeD4fDWdvBNmbQdFRIB8+FrFNQ9V00/ImEwFagnPO9lBF7LnHZTSI14exN92VmJe3Krt+sxyRV188KCK6ctdld11V17flLVtPHE6Mq5pu6u+CIgBrlMijVPL7Ad48vBOwcl2H5TZ2Hjmd+FcUBGD5wtgblgEer/Uo8Mg9B+cMJAHE6FAUBGBNGXV5PUpPP9OyVeyRbXfzioSMJMAgI8Dws8/DiAvnWrbK6GxHx9L5YC9pJAEGGQF8x01CRf3N3Faxj0PYRyKSAFyoEgJFEwLYypqxN97JfRroWfs0up9skgQQ6//iIUAiD7giALZg1Kqw2bvOO260XIkjiE1WxcrPvZjro3wKSOoCNnXM3tnzCluexZZpFXIRCVGSAEk9yFbY+CYcK9SveZpbF/JN9ClFEkAYzuITlCNAUp+JzPbl4o7IFZUkASQBZBJ4OAfcGgHYa132x93tMsWtzlYjOKmXatTg5SByBMjSCCCy1jDbw/zeZx6HJIBNlN0cAUTX9dt0UVhcEkAYqkOCkgBm0HKR9BbMVLAkgCQAd/28yB0hcwB7w++QHAEyXWXD+xRd5gD2SJiQzmUIOHwpuV1XRfyUBLCLqiRASsREQp4DqAdUGbIhwOluG3IEyJRyaeqLACtyR4gkgTIEHOoEOQLYJLQIUWUOYBNUmQSmBkxkxHMAtcwBZAiQIQDRLW86vnl4Gz7IEOAAWpHYKjIkiiSBDtyzVUUSwBZc+4UlAeS7APkuIIkDIiOeg3tNJoGZgsarL0MAD6EU12UIkCEgZyGAPQbyngL61wf271rE1gr2/yafAhzc4bwquRwB5DyAnAdwvPW6CFEHXQ4Qrq85C4TY3yWRd9sfdl0EWJGsWGQeQI4AB4GPq5pu2nbFtDR+e13tNEWh62z0p21RSYC8JIG9+84PHp5s2USAtnr/ZyjBBtu9aqNCsRBA5OTQ7t8+DHZusFUpkA9DulVNNx3LZt4osm7uZKIYr9noT9uixUKA0VffiJITTGcsDGgv2/eXt91tIRCAAJ1Vmm7af89EgPa62pPjCs3qFprFQACR/IIxoevumxH9wHp3/UIgwL6TQ9v2nRxqOqPHHALmf+cEGvdkdceFQiUAKRsBT2V14viYERddJjSydSwJIN65oxhCwDZV003n8ppDQBYOjEhGJ5cEEOpFh0LGzg6039IAxGMFTwAKfFit6fwDI9rnz5kQjyuZb51tAclgIcCeNY9h77NPcelTCCEAwFZV000nY5lDQJYOjTocpcFAgNj2jxPxn0YixUIAwUOjFtRUerN4bBxDazAQoHv1Q+j551+5nc8Exiy4NXGwpFURmfgSMpZWiLyhak2ncecB3gteXlGe5YMji5kAbGZxzx9WgW1YLVrG3XIPlFEVeSYAxA6OzMXRscVGAGNXF9jxM72vrAPbnNJOYXc+GwF4JdsjAAHWV2m6aeNFUw6Qi8Oj3SQAD9hMrrPzfdgf7ePH+XR2Rl5yJYZ94RyuG9kmAACxw6NzcXy8WwTgopplAd6Xw8Onfw2lk03nNaf0asf13xNKKJ02iQDPV2n6TG4OwATCAf9uAKYDhpwaT643WAjAO71UFK9UB2SL1rUht0bV9FmiBGDHbfJPU7Zh/XBRSYCBwO1ZsxJ7n/29QzQFq1E8oDbq88QIEKx9HpROF1RtW0wSYCBkHbddh3ir6+d1J/fLraqm/0SMAAH/KgD8nZptd/3+CpIAh4Drfvxh9Lxg/TrZIcwDqu17GXTNvpdB94oRoL6mEYRYn4GagVeSAEB8R2viXGPeWoIMYB5IAIKLq0P6E2IECPjr901gaW4Zl0ngIQRoLLq/49f+EUb3rmxBbNZLMEUN6aZ1Hil3S832moDBMgLwNqmkkV4Yu3cO+GMnqMY+fj93Hc8sEYTVkF6dymja7XLDAf97AI7JhqeMALySi4MUeT4MmuuU/lZtbE6Z01kR4AEAVw4aEIZyQyitVxubl9sdAVjnMxLIUuQIUCN+evXylf+xRYC24NwjQI0NFDiyyNs/pN0nBE9VhfQL0oFguWV+a71/MSFYNKQRLPLGE5BZVVrTGkcEkKNAcfc+7+7f/4DAKXIU4CFUuNd5d78QAeQoULgdbOWZyN0vRAAmFA74rwWQ8jGiOOEZ9F73KCAzK7Wm9byWckNAv4LWev+jhKCWp1BeLwgELlE1fbWIJ8IEODASZHWdgIjDUoaDAKE3qaHmpaI42SJAS93sKo/iC4sql3K5RoC+oWrNpqXflrmCXRd3BOacaUB50W49KZ9lBCxe+LhKAKZsZ8PssRHq/RsomZzlZkn1Ygg8p2o6f+lxCl22QkBy/dZAzQoCYlpnJuazlHIFAUKWq6Emtn7DUcmIAAcSQ5ZwXAfA58gDWckRAoTgI1C6tEprvs+RggOVMiYA09NSV3uyR8GlAGUf1cuXR5n0CK8uxatEoY+UkNgjo+9e3cET5113hQD9RthTgtfjvZQCl8n8gAe9zesUfyYKHqkK6U02a1qKu0qAwy2FA/4ZAGZQipkgmEGAEjcdHwK6WgD8CcA/PAZZN255k/NDDyzAyhoBkm22BOec7TWU8VDIeMMwjiCEjAfBeFCUAWD71/lAqJdQ4qWU/feB3w5cI4CX7s8z9ssCSoGRIA4gCiBGgKgBGiMgUXrg/9nvAIki8TsO/k6BXaDYDoLtFLQFhGwHpVuqteaNuWhfzgiQi8ZIG/YRkASwj9mgqiEJMKi6035jJAHsYzaoakgCDKrutN+Y/wNhP/X5lGDapQAAAABJRU5ErkJggg==
  9. // @supportURL https://github.com/WhiteSevs/TamperMonkeyScript/issues
  10. // @match *://*.jianshu.com/*
  11. // @match *://*.jianshu.io/*
  12. // @require https://fastly.jsdelivr.net/gh/WhiteSevs/TamperMonkeyScript@86be74b83fca4fa47521cded28377b35e1d7d2ac/lib/CoverUMD/index.js
  13. // @require https://fastly.jsdelivr.net/npm/@whitesev/utils@2.6.1/dist/index.umd.js
  14. // @require https://fastly.jsdelivr.net/npm/@whitesev/domutils@1.4.8/dist/index.umd.js
  15. // @require https://fastly.jsdelivr.net/npm/@whitesev/pops@1.9.7/dist/index.umd.js
  16. // @require https://fastly.jsdelivr.net/npm/qmsg@1.2.8/dist/index.umd.js
  17. // @connect *
  18. // @grant GM_deleteValue
  19. // @grant GM_getResourceText
  20. // @grant GM_getValue
  21. // @grant GM_info
  22. // @grant GM_registerMenuCommand
  23. // @grant GM_setValue
  24. // @grant GM_unregisterMenuCommand
  25. // @grant GM_xmlhttpRequest
  26. // @grant unsafeWindow
  27. // @run-at document-start
  28. // ==/UserScript==
  29.  
  30. (function (Qmsg, DOMUtils, Utils, pops) {
  31. 'use strict';
  32.  
  33. var _a;
  34. var _GM_deleteValue = /* @__PURE__ */ (() => typeof GM_deleteValue != "undefined" ? GM_deleteValue : void 0)();
  35. var _GM_getResourceText = /* @__PURE__ */ (() => typeof GM_getResourceText != "undefined" ? GM_getResourceText : void 0)();
  36. var _GM_getValue = /* @__PURE__ */ (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)();
  37. var _GM_info = /* @__PURE__ */ (() => typeof GM_info != "undefined" ? GM_info : void 0)();
  38. var _GM_registerMenuCommand = /* @__PURE__ */ (() => typeof GM_registerMenuCommand != "undefined" ? GM_registerMenuCommand : void 0)();
  39. var _GM_setValue = /* @__PURE__ */ (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)();
  40. var _GM_unregisterMenuCommand = /* @__PURE__ */ (() => typeof GM_unregisterMenuCommand != "undefined" ? GM_unregisterMenuCommand : void 0)();
  41. var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0)();
  42. var _unsafeWindow = /* @__PURE__ */ (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)();
  43. var _monkeyWindow = /* @__PURE__ */ (() => window)();
  44. const CommonUtil = {
  45. /**
  46. * 添加屏蔽CSS
  47. * @param args
  48. * @example
  49. * addBlockCSS("")
  50. * addBlockCSS("","")
  51. * addBlockCSS(["",""])
  52. */
  53. addBlockCSS(...args) {
  54. let selectorList = [];
  55. if (args.length === 0) {
  56. return;
  57. }
  58. if (args.length === 1 && typeof args[0] === "string" && args[0].trim() === "") {
  59. return;
  60. }
  61. args.forEach((selector) => {
  62. if (Array.isArray(selector)) {
  63. selectorList = selectorList.concat(selector);
  64. } else {
  65. selectorList.push(selector);
  66. }
  67. });
  68. return addStyle(`${selectorList.join(",\n")}{display: none !important;}`);
  69. },
  70. /**
  71. * 设置GM_getResourceText的style内容
  72. * @param resourceMapData 资源数据
  73. * @example
  74. * setGMResourceCSS({
  75. * keyName: "ViewerCSS",
  76. * url: "https://example.com/example.css",
  77. * })
  78. */
  79. setGMResourceCSS(resourceMapData) {
  80. let cssText = typeof _GM_getResourceText === "function" ? _GM_getResourceText(resourceMapData.keyName) : "";
  81. if (typeof cssText === "string" && cssText) {
  82. addStyle(cssText);
  83. } else {
  84. CommonUtil.loadStyleLink(resourceMapData.url);
  85. }
  86. },
  87. /**
  88. * 添加<link>标签
  89. * @param url
  90. * @example
  91. * loadStyleLink("https://example.com/example.css")
  92. */
  93. async loadStyleLink(url) {
  94. let $link = document.createElement("link");
  95. $link.rel = "stylesheet";
  96. $link.type = "text/css";
  97. $link.href = url;
  98. domUtils.ready(() => {
  99. document.head.appendChild($link);
  100. });
  101. },
  102. /**
  103. * 添加<script>标签
  104. * @param url
  105. * @example
  106. * loadStyleLink("https://example.com/example.js")
  107. */
  108. async loadScript(url) {
  109. let $script = document.createElement("script");
  110. $script.src = url;
  111. return new Promise((resolve) => {
  112. $script.onload = () => {
  113. resolve(null);
  114. };
  115. (document.head || document.documentElement).appendChild($script);
  116. });
  117. },
  118. /**
  119. * 将url修复,例如只有search的链接修复为完整的链接
  120. *
  121. * 注意:不包括http转https
  122. * @param url 需要修复的链接
  123. * @example
  124. * 修复前:`/xxx/xxx?ss=ssss`
  125. * 修复后:`https://xxx.xxx.xxx/xxx/xxx?ss=ssss`
  126. * @example
  127. * 修复前:`//xxx/xxx?ss=ssss`
  128. * 修复后:`https://xxx.xxx.xxx/xxx/xxx?ss=ssss`
  129. * @example
  130. * 修复前:`https://xxx.xxx.xxx/xxx/xxx?ss=ssss`
  131. * 修复后:`https://xxx.xxx.xxx/xxx/xxx?ss=ssss`
  132. * @example
  133. * 修复前:`xxx/xxx?ss=ssss`
  134. * 修复后:`https://xxx.xxx.xxx/xxx/xxx?ss=ssss`
  135. */
  136. fixUrl(url) {
  137. url = url.trim();
  138. if (url.match(/^http(s|):\/\//i)) {
  139. return url;
  140. } else {
  141. if (!url.startsWith("/")) {
  142. url += "/";
  143. }
  144. url = window.location.origin + url;
  145. return url;
  146. }
  147. },
  148. /**
  149. * http转https
  150. * @param url 需要修复的链接
  151. * @example
  152. * 修复前:
  153. * 修复后:
  154. * @example
  155. * 修复前:
  156. * 修复后:
  157. */
  158. fixHttps(url) {
  159. if (url.startsWith("https://")) {
  160. return url;
  161. }
  162. if (!url.startsWith("http://")) {
  163. return url;
  164. }
  165. let urlObj = new URL(url);
  166. urlObj.protocol = "https:";
  167. return urlObj.toString();
  168. }
  169. };
  170. const _SCRIPT_NAME_ = "简书优化";
  171. const utils = Utils.noConflict();
  172. const domUtils = DOMUtils.noConflict();
  173. const log = new utils.Log(
  174. _GM_info,
  175. _unsafeWindow.console || _monkeyWindow.console
  176. );
  177. const SCRIPT_NAME = ((_a = _GM_info == null ? void 0 : _GM_info.script) == null ? void 0 : _a.name) || _SCRIPT_NAME_;
  178. const DEBUG = false;
  179. log.config({
  180. debug: DEBUG,
  181. logMaxCount: 1e3,
  182. autoClearConsole: true,
  183. tag: true
  184. });
  185. Qmsg.config(
  186. Object.defineProperties(
  187. {
  188. html: true,
  189. autoClose: true,
  190. showClose: false
  191. },
  192. {
  193. position: {
  194. get() {
  195. return PopsPanel.getValue("qmsg-config-position", "bottom");
  196. }
  197. },
  198. maxNums: {
  199. get() {
  200. return PopsPanel.getValue("qmsg-config-maxnums", 5);
  201. }
  202. },
  203. showReverse: {
  204. get() {
  205. return PopsPanel.getValue("qmsg-config-showreverse", true);
  206. }
  207. },
  208. zIndex: {
  209. get() {
  210. let maxZIndex = Utils.getMaxZIndex();
  211. let popsMaxZIndex = pops.config.InstanceUtils.getPopsMaxZIndex().zIndex;
  212. return Utils.getMaxValue(maxZIndex, popsMaxZIndex) + 100;
  213. }
  214. }
  215. }
  216. )
  217. );
  218. const GM_Menu = new utils.GM_Menu({
  219. GM_getValue: _GM_getValue,
  220. GM_setValue: _GM_setValue,
  221. GM_registerMenuCommand: _GM_registerMenuCommand,
  222. GM_unregisterMenuCommand: _GM_unregisterMenuCommand
  223. });
  224. const httpx = new utils.Httpx(_GM_xmlhttpRequest);
  225. httpx.interceptors.response.use(void 0, (data) => {
  226. log.error("拦截器-请求错误", data);
  227. if (data.type === "onabort") {
  228. Qmsg.warning("请求取消");
  229. } else if (data.type === "onerror") {
  230. Qmsg.error("请求异常");
  231. } else if (data.type === "ontimeout") {
  232. Qmsg.error("请求超时");
  233. } else {
  234. Qmsg.error("其它错误");
  235. }
  236. return data;
  237. });
  238. httpx.config({
  239. logDetails: DEBUG
  240. });
  241. ({
  242. Object: {
  243. defineProperty: _unsafeWindow.Object.defineProperty
  244. },
  245. Function: {
  246. apply: _unsafeWindow.Function.prototype.apply,
  247. call: _unsafeWindow.Function.prototype.call
  248. },
  249. Element: {
  250. appendChild: _unsafeWindow.Element.prototype.appendChild
  251. },
  252. setTimeout: _unsafeWindow.setTimeout
  253. });
  254. const addStyle = utils.addStyle.bind(utils);
  255. document.querySelector.bind(document);
  256. document.querySelectorAll.bind(document);
  257. const KEY = "GM_Panel";
  258. const ATTRIBUTE_INIT = "data-init";
  259. const ATTRIBUTE_KEY = "data-key";
  260. const ATTRIBUTE_DEFAULT_VALUE = "data-default-value";
  261. const ATTRIBUTE_INIT_MORE_VALUE = "data-init-more-value";
  262. const PROPS_STORAGE_API = "data-storage-api";
  263. const UISwitch = function(text, key, defaultValue, clickCallBack, description, afterAddToUListCallBack) {
  264. let result = {
  265. text,
  266. type: "switch",
  267. description,
  268. attributes: {},
  269. props: {},
  270. getValue() {
  271. return Boolean(
  272. this.props[PROPS_STORAGE_API].get(key, defaultValue)
  273. );
  274. },
  275. callback(event, __value) {
  276. let value = Boolean(__value);
  277. log.success(`${value ? "开启" : "关闭"} ${text}`);
  278. this.props[PROPS_STORAGE_API].set(key, value);
  279. },
  280. afterAddToUListCallBack
  281. };
  282. Reflect.set(result.attributes, ATTRIBUTE_KEY, key);
  283. Reflect.set(result.attributes, ATTRIBUTE_DEFAULT_VALUE, defaultValue);
  284. Reflect.set(result.props, PROPS_STORAGE_API, {
  285. get(key2, defaultValue2) {
  286. return PopsPanel.getValue(key2, defaultValue2);
  287. },
  288. set(key2, value) {
  289. PopsPanel.setValue(key2, value);
  290. }
  291. });
  292. return result;
  293. };
  294. const SettingUIPC = {
  295. id: "jianshu-panel-config-pc",
  296. title: "桌面端",
  297. forms: [
  298. {
  299. text: "",
  300. type: "forms",
  301. forms: [
  302. {
  303. text: "功能",
  304. type: "deepMenu",
  305. forms: [
  306. {
  307. text: "",
  308. type: "forms",
  309. forms: [
  310. UISwitch("全文居中", "JianShuArticleCenter", true),
  311. UISwitch("自动展开全文", "JianShuAutoExpandFullText", true),
  312. UISwitch(
  313. "重定向链接",
  314. "JianShuAutoJumpRedirect_PC",
  315. true,
  316. void 0,
  317. "自动跳转简书拦截的Url链接"
  318. )
  319. ]
  320. }
  321. ]
  322. },
  323. {
  324. text: "屏蔽",
  325. type: "deepMenu",
  326. forms: [
  327. {
  328. text: "",
  329. type: "forms",
  330. forms: [
  331. UISwitch(
  332. "【屏蔽】底部推荐阅读",
  333. "JianShuShieldRecommendedReading",
  334. false
  335. ),
  336. UISwitch("【屏蔽】评论区", "JianShuShieldUserComments", false),
  337. UISwitch(
  338. "【屏蔽】相关文章",
  339. "JianShuShieldRelatedArticles",
  340. false
  341. ),
  342. UISwitch(
  343. "【屏蔽】客户端弹窗",
  344. "jianshu-shieldClientDialog",
  345. true,
  346. void 0,
  347. "弹出的【扫码安装简书客户端 畅享全文阅读体验】"
  348. ),
  349. UISwitch("【屏蔽】顶部导航栏", "jianshu-shieldTopNav", false),
  350. UISwitch(
  351. "【屏蔽】底部工具栏",
  352. "jianshu-shieldBottomToolbar",
  353. false,
  354. void 0,
  355. "屏蔽掉底部悬浮的评论输入框、评论、点赞..."
  356. )
  357. ]
  358. }
  359. ]
  360. },
  361. {
  362. text: "劫持/拦截",
  363. type: "deepMenu",
  364. forms: [
  365. {
  366. text: "",
  367. type: "forms",
  368. forms: [
  369. UISwitch(
  370. "拦截-剪贴板",
  371. "JianShuRemoveClipboardHijacking",
  372. true,
  373. void 0,
  374. "去除禁止复制"
  375. )
  376. ]
  377. }
  378. ]
  379. }
  380. ]
  381. }
  382. ]
  383. };
  384. const SettingUIMobile = {
  385. id: "jianshu-panel-config-mobile",
  386. title: "移动端",
  387. forms: [
  388. {
  389. text: "",
  390. type: "forms",
  391. forms: [
  392. {
  393. text: "功能",
  394. type: "deepMenu",
  395. forms: [
  396. {
  397. text: "",
  398. type: "forms",
  399. forms: [
  400. UISwitch(
  401. "自动展开全文",
  402. "JianShuAutoExpandFullText_Mobile",
  403. true
  404. ),
  405. UISwitch(
  406. "重定向链接",
  407. "JianShuAutoJumpRedirect_Mobile",
  408. true,
  409. void 0,
  410. "自动跳转简书拦截的Url链接"
  411. )
  412. ]
  413. }
  414. ]
  415. },
  416. {
  417. text: "屏蔽",
  418. type: "deepMenu",
  419. forms: [
  420. {
  421. text: "",
  422. type: "forms",
  423. forms: [
  424. UISwitch(
  425. "【屏蔽】底部推荐阅读",
  426. "JianShuremoveFooterRecommendRead",
  427. false
  428. ),
  429. UISwitch(
  430. "【屏蔽】评论区",
  431. "JianShuShieldUserCommentsMobile",
  432. false
  433. )
  434. ]
  435. }
  436. ]
  437. },
  438. {
  439. text: "劫持/拦截",
  440. type: "deepMenu",
  441. forms: [
  442. {
  443. text: "",
  444. type: "forms",
  445. forms: [
  446. UISwitch(
  447. "拦截-剪贴板",
  448. "JianShuRemoveClipboardHijacking_Mobile",
  449. true,
  450. void 0,
  451. "去除禁止复制"
  452. ),
  453. UISwitch(
  454. "劫持-唤醒/跳转App",
  455. "JianShuHijackSchemeScriptLabel_Mobile",
  456. true,
  457. void 0,
  458. "去除简书唤醒调用App"
  459. )
  460. ]
  461. }
  462. ]
  463. }
  464. ]
  465. }
  466. ]
  467. };
  468. const UISelect = function(text, key, defaultValue, data, callback, description) {
  469. let selectData = [];
  470. if (typeof data === "function") {
  471. selectData = data();
  472. } else {
  473. selectData = data;
  474. }
  475. let result = {
  476. text,
  477. type: "select",
  478. description,
  479. attributes: {},
  480. props: {},
  481. getValue() {
  482. return this.props[PROPS_STORAGE_API].get(key, defaultValue);
  483. },
  484. callback(event, isSelectedValue, isSelectedText) {
  485. let value = isSelectedValue;
  486. log.info(`选择:${isSelectedText}`);
  487. this.props[PROPS_STORAGE_API].set(key, value);
  488. if (typeof callback === "function") {
  489. callback(event, value, isSelectedText);
  490. }
  491. },
  492. data: selectData
  493. };
  494. Reflect.set(result.attributes, ATTRIBUTE_KEY, key);
  495. Reflect.set(result.attributes, ATTRIBUTE_DEFAULT_VALUE, defaultValue);
  496. Reflect.set(result.props, PROPS_STORAGE_API, {
  497. get(key2, defaultValue2) {
  498. return PopsPanel.getValue(key2, defaultValue2);
  499. },
  500. set(key2, value) {
  501. PopsPanel.setValue(key2, value);
  502. }
  503. });
  504. return result;
  505. };
  506. const SettingUICommon = {
  507. id: "jianshu-panel-common",
  508. title: "通用",
  509. forms: [
  510. {
  511. text: "Toast配置",
  512. type: "forms",
  513. forms: [
  514. UISelect(
  515. "Toast位置",
  516. "qmsg-config-position",
  517. "bottom",
  518. [
  519. {
  520. value: "topleft",
  521. text: "左上角"
  522. },
  523. {
  524. value: "top",
  525. text: "顶部"
  526. },
  527. {
  528. value: "topright",
  529. text: "右上角"
  530. },
  531. {
  532. value: "left",
  533. text: "左边"
  534. },
  535. {
  536. value: "center",
  537. text: "中间"
  538. },
  539. {
  540. value: "right",
  541. text: "右边"
  542. },
  543. {
  544. value: "bottomleft",
  545. text: "左下角"
  546. },
  547. {
  548. value: "bottom",
  549. text: "底部"
  550. },
  551. {
  552. value: "bottomright",
  553. text: "右下角"
  554. }
  555. ],
  556. (event, isSelectValue, isSelectText) => {
  557. log.info("设置当前Qmsg弹出位置" + isSelectText);
  558. },
  559. "Toast显示在页面九宫格的位置"
  560. ),
  561. UISelect(
  562. "最多显示的数量",
  563. "qmsg-config-maxnums",
  564. 3,
  565. [
  566. {
  567. value: 1,
  568. text: "1"
  569. },
  570. {
  571. value: 2,
  572. text: "2"
  573. },
  574. {
  575. value: 3,
  576. text: "3"
  577. },
  578. {
  579. value: 4,
  580. text: "4"
  581. },
  582. {
  583. value: 5,
  584. text: "5"
  585. }
  586. ],
  587. void 0,
  588. "限制Toast显示的数量"
  589. ),
  590. UISwitch(
  591. "逆序弹出",
  592. "qmsg-config-showreverse",
  593. false,
  594. void 0,
  595. "修改Toast弹出的顺序"
  596. )
  597. ]
  598. }
  599. ]
  600. };
  601. const PanelUISize = {
  602. /**
  603. * 一般设置界面的尺寸
  604. */
  605. setting: {
  606. get width() {
  607. return window.innerWidth < 550 ? "88vw" : "550px";
  608. },
  609. get height() {
  610. return window.innerHeight < 450 ? "70vh" : "450px";
  611. }
  612. }
  613. };
  614. const PopsPanel = {
  615. /** 数据 */
  616. $data: {
  617. __data: null,
  618. __oneSuccessExecMenu: null,
  619. __onceExec: null,
  620. __listenData: null,
  621. /**
  622. * 菜单项的默认值
  623. */
  624. get data() {
  625. if (PopsPanel.$data.__data == null) {
  626. PopsPanel.$data.__data = new utils.Dictionary();
  627. }
  628. return PopsPanel.$data.__data;
  629. },
  630. /**
  631. * 成功只执行了一次的项
  632. */
  633. get oneSuccessExecMenu() {
  634. if (PopsPanel.$data.__oneSuccessExecMenu == null) {
  635. PopsPanel.$data.__oneSuccessExecMenu = new utils.Dictionary();
  636. }
  637. return PopsPanel.$data.__oneSuccessExecMenu;
  638. },
  639. /**
  640. * 成功只执行了一次的项
  641. */
  642. get onceExec() {
  643. if (PopsPanel.$data.__onceExec == null) {
  644. PopsPanel.$data.__onceExec = new utils.Dictionary();
  645. }
  646. return PopsPanel.$data.__onceExec;
  647. },
  648. /** 脚本名,一般用在设置的标题上 */
  649. get scriptName() {
  650. return SCRIPT_NAME;
  651. },
  652. /** 菜单项的总值在本地数据配置的键名 */
  653. key: KEY,
  654. /** 菜单项在attributes上配置的菜单键 */
  655. attributeKeyName: ATTRIBUTE_KEY,
  656. /** 菜单项在attributes上配置的菜单默认值 */
  657. attributeDefaultValueName: ATTRIBUTE_DEFAULT_VALUE
  658. },
  659. /** 监听器 */
  660. $listener: {
  661. /**
  662. * 值改变的监听器
  663. */
  664. get listenData() {
  665. if (PopsPanel.$data.__listenData == null) {
  666. PopsPanel.$data.__listenData = new utils.Dictionary();
  667. }
  668. return PopsPanel.$data.__listenData;
  669. }
  670. },
  671. init() {
  672. this.initPanelDefaultValue();
  673. this.initExtensionsMenu();
  674. },
  675. /** 判断是否是顶层窗口 */
  676. isTopWindow() {
  677. return _unsafeWindow.top === _unsafeWindow.self;
  678. },
  679. initExtensionsMenu() {
  680. if (!this.isTopWindow()) {
  681. return;
  682. }
  683. GM_Menu.add([
  684. {
  685. key: "show_pops_panel_setting",
  686. text: "⚙ 设置",
  687. autoReload: false,
  688. isStoreValue: false,
  689. showText(text) {
  690. return text;
  691. },
  692. callback: () => {
  693. this.showPanel();
  694. }
  695. }
  696. ]);
  697. },
  698. /** 初始化菜单项的默认值保存到本地数据中 */
  699. initPanelDefaultValue() {
  700. let that = this;
  701. function initDefaultValue(config) {
  702. if (!config.attributes) {
  703. return;
  704. }
  705. let needInitConfig = {};
  706. let key = config.attributes[ATTRIBUTE_KEY];
  707. if (key != null) {
  708. needInitConfig[key] = config.attributes[ATTRIBUTE_DEFAULT_VALUE];
  709. }
  710. let __attr_init__ = config.attributes[ATTRIBUTE_INIT];
  711. if (typeof __attr_init__ === "function") {
  712. let __attr_result__ = __attr_init__();
  713. if (typeof __attr_result__ === "boolean" && !__attr_result__) {
  714. return;
  715. }
  716. }
  717. let initMoreValue = config.attributes[ATTRIBUTE_INIT_MORE_VALUE];
  718. if (initMoreValue && typeof initMoreValue === "object") {
  719. Object.assign(needInitConfig, initMoreValue);
  720. }
  721. let needInitConfigList = Object.keys(needInitConfig);
  722. if (!needInitConfigList.length) {
  723. log.warn(["请先配置键", config]);
  724. return;
  725. }
  726. needInitConfigList.forEach((__key) => {
  727. let __defaultValue = needInitConfig[__key];
  728. if (that.$data.data.has(__key)) {
  729. log.warn("请检查该key(已存在): " + __key);
  730. }
  731. that.$data.data.set(__key, __defaultValue);
  732. });
  733. }
  734. function loopInitDefaultValue(configList) {
  735. for (let index = 0; index < configList.length; index++) {
  736. let configItem = configList[index];
  737. initDefaultValue(configItem);
  738. let childForms = configItem.forms;
  739. if (childForms && Array.isArray(childForms)) {
  740. loopInitDefaultValue(childForms);
  741. }
  742. }
  743. }
  744. let contentConfigList = this.getPanelContentConfig();
  745. for (let index = 0; index < contentConfigList.length; index++) {
  746. let leftContentConfigItem = contentConfigList[index];
  747. if (!leftContentConfigItem.forms) {
  748. continue;
  749. }
  750. let rightContentConfigList = leftContentConfigItem.forms;
  751. if (rightContentConfigList && Array.isArray(rightContentConfigList)) {
  752. loopInitDefaultValue(rightContentConfigList);
  753. }
  754. }
  755. },
  756. /**
  757. * 设置值
  758. * @param key 键
  759. * @param value 值
  760. */
  761. setValue(key, value) {
  762. let locaData = _GM_getValue(KEY, {});
  763. let oldValue = locaData[key];
  764. locaData[key] = value;
  765. _GM_setValue(KEY, locaData);
  766. if (this.$listener.listenData.has(key)) {
  767. this.$listener.listenData.get(key).callback(key, oldValue, value);
  768. }
  769. },
  770. /**
  771. * 获取值
  772. * @param key 键
  773. * @param defaultValue 默认值
  774. */
  775. getValue(key, defaultValue) {
  776. let locaData = _GM_getValue(KEY, {});
  777. let localValue = locaData[key];
  778. if (localValue == null) {
  779. if (this.$data.data.has(key)) {
  780. return this.$data.data.get(key);
  781. }
  782. return defaultValue;
  783. }
  784. return localValue;
  785. },
  786. /**
  787. * 删除值
  788. * @param key 键
  789. */
  790. deleteValue(key) {
  791. let locaData = _GM_getValue(KEY, {});
  792. let oldValue = locaData[key];
  793. Reflect.deleteProperty(locaData, key);
  794. _GM_setValue(KEY, locaData);
  795. if (this.$listener.listenData.has(key)) {
  796. this.$listener.listenData.get(key).callback(key, oldValue, void 0);
  797. }
  798. },
  799. /**
  800. * 监听调用setValue、deleteValue
  801. * @param key 需要监听的键
  802. * @param callback
  803. */
  804. addValueChangeListener(key, callback) {
  805. let listenerId = Math.random();
  806. this.$listener.listenData.set(key, {
  807. id: listenerId,
  808. key,
  809. callback
  810. });
  811. return listenerId;
  812. },
  813. /**
  814. * 移除监听
  815. * @param listenerId 监听的id
  816. */
  817. removeValueChangeListener(listenerId) {
  818. let deleteKey = null;
  819. for (const [key, value] of this.$listener.listenData.entries()) {
  820. if (value.id === listenerId) {
  821. deleteKey = key;
  822. break;
  823. }
  824. }
  825. if (typeof deleteKey === "string") {
  826. this.$listener.listenData.delete(deleteKey);
  827. } else {
  828. console.warn("没有找到对应的监听器");
  829. }
  830. },
  831. /**
  832. * 主动触发菜单值改变的回调
  833. * @param key 菜单键
  834. * @param newValue 想要触发的新值,默认使用当前值
  835. * @param oldValue 想要触发的旧值,默认使用当前值
  836. */
  837. triggerMenuValueChange(key, newValue, oldValue) {
  838. if (this.$listener.listenData.has(key)) {
  839. let listenData = this.$listener.listenData.get(key);
  840. if (typeof listenData.callback === "function") {
  841. let value = this.getValue(key);
  842. let __newValue = value;
  843. let __oldValue = value;
  844. if (typeof newValue !== "undefined" && arguments.length > 1) {
  845. __newValue = newValue;
  846. }
  847. if (typeof oldValue !== "undefined" && arguments.length > 2) {
  848. __oldValue = oldValue;
  849. }
  850. listenData.callback(key, __oldValue, __newValue);
  851. }
  852. }
  853. },
  854. /**
  855. * 判断该键是否存在
  856. * @param key 键
  857. */
  858. hasKey(key) {
  859. let locaData = _GM_getValue(KEY, {});
  860. return key in locaData;
  861. },
  862. /**
  863. * 自动判断菜单是否启用,然后执行回调
  864. * @param key
  865. * @param callback 回调
  866. * @param [isReverse=false] 逆反判断菜单启用
  867. */
  868. execMenu(key, callback, isReverse = false) {
  869. if (!(typeof key === "string" || typeof key === "object" && Array.isArray(key))) {
  870. throw new TypeError("key 必须是字符串或者字符串数组");
  871. }
  872. let runKeyList = [];
  873. if (typeof key === "object" && Array.isArray(key)) {
  874. runKeyList = [...key];
  875. } else {
  876. runKeyList.push(key);
  877. }
  878. let value = void 0;
  879. for (let index = 0; index < runKeyList.length; index++) {
  880. const runKey = runKeyList[index];
  881. if (!this.$data.data.has(runKey)) {
  882. log.warn(`${key} 键不存在`);
  883. return;
  884. }
  885. let runValue = PopsPanel.getValue(runKey);
  886. if (isReverse) {
  887. runValue = !runValue;
  888. }
  889. if (!runValue) {
  890. break;
  891. }
  892. value = runValue;
  893. }
  894. if (value) {
  895. callback(value);
  896. }
  897. },
  898. /**
  899. * 自动判断菜单是否启用,然后执行回调,只会执行一次
  900. * @param key
  901. * @param callback 回调
  902. * @param getValueFn 自定义处理获取当前值,值true是启用并执行回调,值false是不执行回调
  903. * @param handleValueChangeFn 自定义处理值改变时的回调,值true是启用并执行回调,值false是不执行回调
  904. */
  905. execMenuOnce(key, callback, getValueFn, handleValueChangeFn) {
  906. if (typeof key !== "string") {
  907. throw new TypeError("key 必须是字符串");
  908. }
  909. if (!this.$data.data.has(key)) {
  910. log.warn(`${key} 键不存在`);
  911. return;
  912. }
  913. if (this.$data.oneSuccessExecMenu.has(key)) {
  914. return;
  915. }
  916. this.$data.oneSuccessExecMenu.set(key, 1);
  917. let __getValue = () => {
  918. let localValue = PopsPanel.getValue(key);
  919. return typeof getValueFn === "function" ? getValueFn(key, localValue) : localValue;
  920. };
  921. let resultStyleList = [];
  922. let dynamicPushStyleNode = ($style) => {
  923. let __value = __getValue();
  924. let dynamicResultList = [];
  925. if ($style instanceof HTMLStyleElement) {
  926. dynamicResultList = [$style];
  927. } else if (Array.isArray($style)) {
  928. dynamicResultList = [
  929. ...$style.filter(
  930. (item) => item != null && item instanceof HTMLStyleElement
  931. )
  932. ];
  933. }
  934. if (__value) {
  935. resultStyleList = resultStyleList.concat(dynamicResultList);
  936. } else {
  937. for (let index = 0; index < dynamicResultList.length; index++) {
  938. let $css = dynamicResultList[index];
  939. $css.remove();
  940. dynamicResultList.splice(index, 1);
  941. index--;
  942. }
  943. }
  944. };
  945. let changeCallBack = (currentValue) => {
  946. let resultList = [];
  947. if (currentValue) {
  948. let result = callback(currentValue, dynamicPushStyleNode);
  949. if (result instanceof HTMLStyleElement) {
  950. resultList = [result];
  951. } else if (Array.isArray(result)) {
  952. resultList = [
  953. ...result.filter(
  954. (item) => item != null && item instanceof HTMLStyleElement
  955. )
  956. ];
  957. }
  958. }
  959. for (let index = 0; index < resultStyleList.length; index++) {
  960. let $css = resultStyleList[index];
  961. $css.remove();
  962. resultStyleList.splice(index, 1);
  963. index--;
  964. }
  965. resultStyleList = [...resultList];
  966. };
  967. this.addValueChangeListener(
  968. key,
  969. (__key, oldValue, newValue) => {
  970. let __newValue = newValue;
  971. if (typeof handleValueChangeFn === "function") {
  972. __newValue = handleValueChangeFn(__key, newValue, oldValue);
  973. }
  974. changeCallBack(__newValue);
  975. }
  976. );
  977. let value = __getValue();
  978. if (value) {
  979. changeCallBack(value);
  980. }
  981. },
  982. /**
  983. * 父子菜单联动,自动判断菜单是否启用,然后执行回调,只会执行一次
  984. * @param key 菜单键
  985. * @param childKey 子菜单键
  986. * @param callback 回调
  987. * @param replaceValueFn 用于修改mainValue,返回undefined则不做处理
  988. */
  989. execInheritMenuOnce(key, childKey, callback, replaceValueFn) {
  990. let that = this;
  991. const handleInheritValue = (key2, childKey2) => {
  992. let mainValue = that.getValue(key2);
  993. let childValue = that.getValue(childKey2);
  994. if (typeof replaceValueFn === "function") {
  995. let changedMainValue = replaceValueFn(mainValue, childValue);
  996. if (changedMainValue !== void 0) {
  997. return changedMainValue;
  998. }
  999. }
  1000. return mainValue;
  1001. };
  1002. this.execMenuOnce(
  1003. key,
  1004. callback,
  1005. () => {
  1006. return handleInheritValue(key, childKey);
  1007. },
  1008. () => {
  1009. return handleInheritValue(key, childKey);
  1010. }
  1011. );
  1012. this.execMenuOnce(
  1013. childKey,
  1014. () => {
  1015. },
  1016. () => false,
  1017. () => {
  1018. this.triggerMenuValueChange(key);
  1019. return false;
  1020. }
  1021. );
  1022. },
  1023. /**
  1024. * 根据key执行一次
  1025. * @param key
  1026. */
  1027. onceExec(key, callback) {
  1028. if (typeof key !== "string") {
  1029. throw new TypeError("key 必须是字符串");
  1030. }
  1031. if (this.$data.onceExec.has(key)) {
  1032. return;
  1033. }
  1034. callback();
  1035. this.$data.onceExec.set(key, 1);
  1036. },
  1037. /**
  1038. * 显示设置面板
  1039. */
  1040. showPanel() {
  1041. pops.panel({
  1042. title: {
  1043. text: `${SCRIPT_NAME}-设置`,
  1044. position: "center",
  1045. html: false,
  1046. style: ""
  1047. },
  1048. content: this.getPanelContentConfig(),
  1049. mask: {
  1050. enable: true,
  1051. clickEvent: {
  1052. toClose: true,
  1053. toHide: false
  1054. }
  1055. },
  1056. width: PanelUISize.setting.width,
  1057. height: PanelUISize.setting.height,
  1058. drag: true,
  1059. only: true
  1060. });
  1061. },
  1062. /**
  1063. * 获取配置内容
  1064. */
  1065. getPanelContentConfig() {
  1066. let configList = [
  1067. SettingUICommon,
  1068. SettingUIPC,
  1069. SettingUIMobile
  1070. ];
  1071. return configList;
  1072. }
  1073. };
  1074. const blockCSS = `.download-app-guidance,\r
  1075. .call-app-btn,\r
  1076. .collapse-tips,\r
  1077. .note-graceful-button,\r
  1078. .app-open,\r
  1079. .header-wrap,\r
  1080. .recommend-wrap.recommend-ad,\r
  1081. .call-app-Ad-bottom,\r
  1082. #recommended-notes p.top-title span.more,\r
  1083. #homepage .modal,\r
  1084. button.index_call-app-btn,\r
  1085. span.note__flow__download,\r
  1086. .download-guide,\r
  1087. #footer,\r
  1088. .comment-open-app-btn-wrap,\r
  1089. .nav.navbar-nav + div,\r
  1090. .self-flow-ad,\r
  1091. #free-reward-panel,\r
  1092. div[id*='AdFive'],\r
  1093. #index-aside-download-qrbox,\r
  1094. .baidu-app-download-2eIkf_1,\r
  1095. /* 底部的"小礼物走一走,来简书关注我"、赞赏支持和更多精彩内容,就在简书APP */\r
  1096. div[role="main"] > div > section:first-child > div:nth-last-child(2),\r
  1097. /* 它的内部是script标签,可能影响部分评论之间的高度问题 */\r
  1098. div.adad_container ,\r
  1099. /* 顶部导航栏的【下载App】 */\r
  1100. #__next nav a[href*="navbar-app"] {\r
  1101. display: none !important;\r
  1102. }\r
  1103. body.reader-day-mode.normal-size {\r
  1104. overflow: auto !important;\r
  1105. }\r
  1106. .collapse-free-content {\r
  1107. height: auto !important;\r
  1108. }\r
  1109. .copyright {\r
  1110. color: #000 !important;\r
  1111. }\r
  1112. #note-show .content .show-content-free .collapse-free-content:after {\r
  1113. background-image: none !important;\r
  1114. }\r
  1115. footer > div > div {\r
  1116. justify-content: center;\r
  1117. }\r
  1118. /* 修复底部最后编辑于:。。。在某些套壳浏览器上的错位问题 */\r
  1119. #note-show .content .show-content-free .note-meta-time {\r
  1120. margin-top: 0px !important;\r
  1121. }\r
  1122. `;
  1123. const JianshuRouter = {
  1124. /**
  1125. * 简书拦截跳转的网址
  1126. */
  1127. isGoWild() {
  1128. return window.location.pathname === "/go-wild";
  1129. }
  1130. };
  1131. const waitForElementToRemove = function(selectorText = "") {
  1132. utils.waitNodeList(selectorText).then((nodeList) => {
  1133. nodeList.forEach((item) => item.remove());
  1134. });
  1135. };
  1136. const Jianshu = {
  1137. init() {
  1138. this.addCSS();
  1139. PopsPanel.execMenu("JianShuAutoJumpRedirect_PC", () => {
  1140. this.jumpRedirect();
  1141. });
  1142. PopsPanel.execMenu("JianShuRemoveClipboardHijacking", () => {
  1143. this.removeClipboardHijacking();
  1144. });
  1145. PopsPanel.execMenu("JianShuAutoExpandFullText", () => {
  1146. this.autoExpandFullText();
  1147. });
  1148. PopsPanel.execMenu("JianShuArticleCenter", () => {
  1149. return this.articleCenter();
  1150. });
  1151. PopsPanel.execMenu("JianShuShieldRelatedArticles", () => {
  1152. return this.blockRelatedArticles();
  1153. });
  1154. PopsPanel.execMenu("jianshu-shieldClientDialog", () => {
  1155. this.blockClientDialog();
  1156. });
  1157. PopsPanel.execMenuOnce("JianShuShieldUserComments", () => {
  1158. return this.blockUserComments();
  1159. });
  1160. PopsPanel.execMenuOnce("JianShuShieldRecommendedReading", () => {
  1161. return this.blockRecommendedReading();
  1162. });
  1163. PopsPanel.execMenuOnce("jianshu-shieldTopNav", () => {
  1164. return this.blockTopNav();
  1165. });
  1166. PopsPanel.execMenuOnce("jianshu-shieldBottomToolbar", () => {
  1167. return this.blockBottomToolbar();
  1168. });
  1169. },
  1170. /**
  1171. * 添加屏蔽CSS
  1172. */
  1173. addCSS() {
  1174. log.info("添加屏蔽CSS");
  1175. return addStyle(blockCSS);
  1176. },
  1177. /**
  1178. * 全文居中
  1179. */
  1180. articleCenter() {
  1181. log.info("全文居中");
  1182. let result = [];
  1183. result.push(
  1184. CommonUtil.addBlockCSS("div[role=main] aside", "div._3Pnjry"),
  1185. addStyle(
  1186. /*css*/
  1187. `
  1188. div[role=main] aside,
  1189. div._3Pnjry{
  1190. display: none !important;
  1191. }
  1192. div._gp-ck{
  1193. width: 100% !important;
  1194. }`
  1195. )
  1196. );
  1197. waitForElementToRemove("div[role=main] aside");
  1198. waitForElementToRemove("div._3Pnjry");
  1199. utils.waitNodeList("div._gp-ck").then((nodeList) => {
  1200. nodeList.forEach((item) => {
  1201. item.style["width"] = "100%";
  1202. });
  1203. });
  1204. return result;
  1205. },
  1206. /**
  1207. * 去除剪贴板劫持
  1208. */
  1209. removeClipboardHijacking() {
  1210. log.info("去除剪贴板劫持");
  1211. const stopNativePropagation = (event) => {
  1212. event.stopPropagation();
  1213. };
  1214. window.addEventListener("copy", stopNativePropagation, true);
  1215. document.addEventListener("copy", stopNativePropagation, true);
  1216. },
  1217. /**
  1218. * 自动展开全文
  1219. */
  1220. autoExpandFullText() {
  1221. utils.waitNode(`div#homepage div[class*="dialog-"]`).then((element) => {
  1222. element.style["visibility"] = "hidden";
  1223. log.info("自动展开全文");
  1224. utils.mutationObserver(element, {
  1225. callback: (mutations) => {
  1226. if (mutations.length == 0) {
  1227. return;
  1228. }
  1229. mutations.forEach((mutationItem) => {
  1230. var _a2;
  1231. if (mutationItem.target.style["display"] != "none") {
  1232. log.success("自动展开全文-自动点击");
  1233. (_a2 = document.querySelector(
  1234. 'div#homepage div[class*="dialog-"] .cancel'
  1235. )) == null ? void 0 : _a2.click();
  1236. }
  1237. });
  1238. },
  1239. config: {
  1240. /* 子节点的变动(新增、删除或者更改) */
  1241. childList: false,
  1242. /* 属性的变动 */
  1243. attributes: true,
  1244. /* 节点内容或节点文本的变动 */
  1245. characterData: true,
  1246. /* 是否将观察器应用于该节点的所有后代节点 */
  1247. subtree: true
  1248. }
  1249. });
  1250. });
  1251. },
  1252. /**
  1253. * 去除简书拦截其它网址的url并自动跳转
  1254. */
  1255. jumpRedirect() {
  1256. if (JianshuRouter.isGoWild()) {
  1257. log.success("去除简书拦截其它网址的url并自动跳转");
  1258. window.stop();
  1259. let search = window.location.href.replace(
  1260. window.location.origin + "/",
  1261. ""
  1262. );
  1263. search = decodeURIComponent(search);
  1264. let newURL = search.replace(/^go-wild\?ac=2&url=/gi, "").replace(/^https:\/\/link.zhihu.com\/\?target\=/gi, "");
  1265. window.location.href = newURL;
  1266. }
  1267. },
  1268. /**
  1269. * 屏蔽相关文章
  1270. */
  1271. blockRelatedArticles() {
  1272. log.info("屏蔽相关文章");
  1273. return CommonUtil.addBlockCSS(
  1274. 'div[role="main"] > div > section:nth-child(2)'
  1275. );
  1276. },
  1277. /**
  1278. * 【屏蔽】客户端弹窗
  1279. */
  1280. blockClientDialog() {
  1281. log.info("【屏蔽】客户端弹窗");
  1282. CommonUtil.addBlockCSS(
  1283. 'div:has(>div[class*="-mask"]:not([class*="-mask-hidden"]) + div[tabindex="-1"][role="dialog"])'
  1284. );
  1285. utils.waitNode(
  1286. `div[class*="-mask"]:not([class*="-mask-hidden"]) + div[tabindex="-1"][role="dialog"]`
  1287. ).then((element) => {
  1288. log.success("弹窗出现");
  1289. utils.waitPropertyByInterval(
  1290. element,
  1291. () => {
  1292. var _a2, _b, _c, _d;
  1293. let react = utils.getReactObj(element);
  1294. return (_d = (_c = (_b = (_a2 = react == null ? void 0 : react.reactInternalInstance) == null ? void 0 : _a2.return) == null ? void 0 : _b.return) == null ? void 0 : _c.memoizedProps) == null ? void 0 : _d.onClose;
  1295. },
  1296. 250,
  1297. 1e4
  1298. ).then(() => {
  1299. let react = utils.getReactObj(element);
  1300. react.reactInternalInstance.return.return.memoizedProps.onClose(
  1301. new Event("click")
  1302. );
  1303. log.success("调用函数关闭弹窗");
  1304. });
  1305. });
  1306. },
  1307. /**
  1308. * 屏蔽评论区
  1309. */
  1310. blockUserComments() {
  1311. log.info("屏蔽评论区");
  1312. return CommonUtil.addBlockCSS("div#note-page-comment");
  1313. },
  1314. /**
  1315. * 屏蔽底部推荐阅读
  1316. */
  1317. blockRecommendedReading() {
  1318. log.info("屏蔽底部推荐阅读");
  1319. return CommonUtil.addBlockCSS(
  1320. 'div[role="main"] > div > section:last-child'
  1321. );
  1322. },
  1323. /**
  1324. * 【屏蔽】顶部导航栏
  1325. */
  1326. blockTopNav() {
  1327. log.info("【屏蔽】顶部导航栏");
  1328. return CommonUtil.addBlockCSS("header");
  1329. },
  1330. /**
  1331. * 【屏蔽】底部工具栏
  1332. */
  1333. blockBottomToolbar() {
  1334. log.info("【屏蔽】底部工具栏");
  1335. return CommonUtil.addBlockCSS("footer");
  1336. }
  1337. };
  1338. const M_Jianshu = {
  1339. init() {
  1340. this.addCSS();
  1341. PopsPanel.execMenu("JianShuAutoJumpRedirect_Mobile", () => {
  1342. Jianshu.jumpRedirect();
  1343. });
  1344. PopsPanel.execMenu("JianShuHijackSchemeScriptLabel_Mobile", () => {
  1345. this.handlePrototype();
  1346. });
  1347. PopsPanel.execMenu("JianShuRemoveClipboardHijacking_Mobile", () => {
  1348. Jianshu.removeClipboardHijacking();
  1349. });
  1350. PopsPanel.execMenu("JianShuAutoExpandFullText_Mobile", () => {
  1351. Jianshu.autoExpandFullText();
  1352. });
  1353. PopsPanel.execMenuOnce("JianShuremoveFooterRecommendRead", () => {
  1354. return this.blockeFooterRecommendRead();
  1355. });
  1356. PopsPanel.execMenu("JianShuShieldUserCommentsMobile", () => {
  1357. return this.blockUserComments();
  1358. });
  1359. },
  1360. /**
  1361. * 添加屏蔽CSS
  1362. */
  1363. addCSS() {
  1364. Jianshu.addCSS();
  1365. },
  1366. /**
  1367. * 手机-屏蔽底部推荐阅读
  1368. */
  1369. blockeFooterRecommendRead() {
  1370. log.info("屏蔽底部推荐阅读");
  1371. return CommonUtil.addBlockCSS("#recommended-notes");
  1372. },
  1373. /**
  1374. * 处理原型
  1375. */
  1376. handlePrototype() {
  1377. log.info("处理原型添加script标签");
  1378. let originalAppendChild = Node.prototype.appendChild;
  1379. _unsafeWindow.Node.prototype.appendChild = function(element) {
  1380. let allowElementLocalNameList = ["img"];
  1381. if (element.src && !element.src.includes("jianshu.io") && !allowElementLocalNameList.includes(element.localName)) {
  1382. log.success(["禁止添加的元素", element]);
  1383. return null;
  1384. } else {
  1385. return originalAppendChild.call(this, element);
  1386. }
  1387. };
  1388. },
  1389. /**
  1390. * 屏蔽评论区
  1391. */
  1392. blockUserComments() {
  1393. log.info("屏蔽评论区");
  1394. return CommonUtil.addBlockCSS("#comment-main");
  1395. }
  1396. };
  1397. PopsPanel.init();
  1398. let isMobile = utils.isPhone();
  1399. let CHANGE_ENV_SET_KEY = "change_env_set";
  1400. let chooseMode = _GM_getValue(CHANGE_ENV_SET_KEY);
  1401. GM_Menu.add({
  1402. key: CHANGE_ENV_SET_KEY,
  1403. text: `⚙ 自动: ${isMobile ? "移动端" : "PC端"}`,
  1404. autoReload: false,
  1405. isStoreValue: false,
  1406. showText(text) {
  1407. if (chooseMode == null) {
  1408. return text;
  1409. }
  1410. return text + ` 手动: ${chooseMode == 1 ? "移动端" : chooseMode == 2 ? "PC端" : "未知"}`;
  1411. },
  1412. callback: () => {
  1413. let allowValue = [0, 1, 2];
  1414. let chooseText = window.prompt(
  1415. "请输入当前脚本环境判定\n\n自动判断: 0\n移动端: 1\nPC端: 2",
  1416. "0"
  1417. );
  1418. if (!chooseText) {
  1419. return;
  1420. }
  1421. let chooseMode2 = parseInt(chooseText);
  1422. if (isNaN(chooseMode2)) {
  1423. Qmsg.error("输入的不是规范的数字");
  1424. return;
  1425. }
  1426. if (!allowValue.includes(chooseMode2)) {
  1427. Qmsg.error("输入的值必须是0或1或2");
  1428. return;
  1429. }
  1430. if (chooseMode2 == 0) {
  1431. _GM_deleteValue(CHANGE_ENV_SET_KEY);
  1432. } else {
  1433. _GM_setValue(CHANGE_ENV_SET_KEY, chooseMode2);
  1434. }
  1435. }
  1436. });
  1437. if (chooseMode != null) {
  1438. log.info(`手动判定为${chooseMode === 1 ? "移动端" : "PC端"}`);
  1439. if (chooseMode == 1) {
  1440. M_Jianshu.init();
  1441. } else if (chooseMode == 2) {
  1442. Jianshu.init();
  1443. } else {
  1444. Qmsg.error("意外,手动判定的值不在范围内");
  1445. _GM_deleteValue(CHANGE_ENV_SET_KEY);
  1446. }
  1447. } else {
  1448. if (isMobile) {
  1449. log.info("自动判定为移动端");
  1450. M_Jianshu.init();
  1451. } else {
  1452. log.info("自动判定为PC端");
  1453. Jianshu.init();
  1454. }
  1455. }
  1456.  
  1457. })(Qmsg, DOMUtils, Utils, pops);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址