简书优化

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

目前为 2024-07-24 提交的版本。查看 最新版本

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

QingJ © 2025

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