Greasy Fork is available in English.

小红书优化

屏蔽登录弹窗、屏蔽广告、优化评论浏览、优化图片浏览、允许复制、禁止唤醒App、禁止唤醒弹窗、修复正确跳转等

  1. // ==UserScript==
  2. // @name 小红书优化
  3. // @namespace https://github.com/WhiteSevs/TamperMonkeyScript
  4. // @version 2024.12.17
  5. // @author WhiteSevs
  6. // @description 屏蔽登录弹窗、屏蔽广告、优化评论浏览、优化图片浏览、允许复制、禁止唤醒App、禁止唤醒弹窗、修复正确跳转等
  7. // @license GPL-3.0-only
  8. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAx9JREFUWEfNl09MU0EQxn/beFTDDRI41FAMcNGbBw62oPEGid6UULxg1EhEEzExgdBEEzRqlKDxZCHgDZJ6U8TWAyaQGIsHMQLSA0S8VYQT2NXp9tnX0vKnpi2TNH1vd3bmm5lv9+0o0kQ73SXsc7QCx1EcjU9rnOl6O3pXRNAqCjqCIsB6LKQioYh9rbK/6MMnWojFHgElO3KwWyUBBD1q9q3fWvoPgHY1dIHu2a3N3PRVt5ob98naOABdVd+K5nluxnJc5dBe9TU4qHS128lvRzDnOufoH4iyETukihJ9EnSH0i5PAFRj7oH8z0r9UmlXw0fQZrsVWhQRKcFCEepvQo0DcNXrQgeechDtbQAVpbCyBiurqUmqqYSD+2FyOnPyZE50ln7A4vKWCc5egvIyCA3DzV4YeZ00UlEGQ/eN88670HsjOTczZ8bbvXCiDqbC8HkeBkahuhLE5sBICqDdAzh9yjh1n4OlZZgdTxqcDEPfIAw9SI1aMjg1DVrDpe5tAIRewOJ36LyXzIAgv+IFz1ljXN5FJAOjrwwIcd583YwfO2L0JHvW2qqGjKXYnAExJkYfDyYBaGWibmyDGhe0t/z9bikDSMQO4NZlEO5YJTggfHCBf8SUIo0TqQCEPB8C0Ddg6m5xQIj4xAcXu+DLPASHjY5/1BDUDkAyWF6amXjCkcYLW5Sg1gWBZ3C7H6Y+mWdJ48y35LiQ0HvGGLHzIFsJLAJLSSQzssYmmzMg0TVfM9vMqqMYkcwIejEiv59rhliy3URP2H6n3/zXJsbsO+ipz+huCUCQSb2E3eJQRNL+ZsIQS/a1ALQIKDtCxu0i4EUs8GPvk7YEXFPbNrvAmj5ZJ3dB49wSYbTlUIgqANJFzoFfq4aE8izBiC0h49iEmctagszUyevoHvgYFf1zXEwA6PBeuJLVXwUe5pVp2Yyr2HmVaMUW8tYNZXWuI6xrT6IxcbeiHYVtTCT62ZDf1pp5ekB1FaYU2qfmgvGLQWpzKi0adOfxlhxF0ZGxObUiT7RqbjRNoJ0oVZIzINMNy5Eehtg7NvCrSChqz/IfgUZkW/BhLsQAAAAASUVORK5CYII=
  9. // @supportURL https://github.com/WhiteSevs/TamperMonkeyScript/issues
  10. // @match *://www.xiaohongshu.com/*
  11. // @require https://update.greasyfork.org/scripts/494167/1413255/CoverUMD.js
  12. // @require https://fastly.jsdelivr.net/npm/@whitesev/utils@2.5.5/dist/index.umd.js
  13. // @require https://fastly.jsdelivr.net/npm/@whitesev/domutils@1.4.8/dist/index.umd.js
  14. // @require https://fastly.jsdelivr.net/npm/@whitesev/pops@1.9.5/dist/index.umd.js
  15. // @require https://fastly.jsdelivr.net/npm/qmsg@1.2.8/dist/index.umd.js
  16. // @require https://fastly.jsdelivr.net/npm/viewerjs@1.11.7/dist/viewer.min.js
  17. // @resource ViewerCSS https://fastly.jsdelivr.net/npm/viewerjs@1.11.7/dist/viewer.min.css
  18. // @connect edith.xiaohongshu.com
  19. // @grant GM_deleteValue
  20. // @grant GM_getResourceText
  21. // @grant GM_getValue
  22. // @grant GM_info
  23. // @grant GM_registerMenuCommand
  24. // @grant GM_setValue
  25. // @grant GM_unregisterMenuCommand
  26. // @grant GM_xmlhttpRequest
  27. // @grant unsafeWindow
  28. // @run-at document-start
  29. // ==/UserScript==
  30.  
  31. (function (Qmsg, Utils, DOMUtils, pops, Viewer) {
  32. 'use strict';
  33.  
  34. var _a;
  35. var _GM_deleteValue = /* @__PURE__ */ (() => typeof GM_deleteValue != "undefined" ? GM_deleteValue : void 0)();
  36. var _GM_getResourceText = /* @__PURE__ */ (() => typeof GM_getResourceText != "undefined" ? GM_getResourceText : void 0)();
  37. var _GM_getValue = /* @__PURE__ */ (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)();
  38. var _GM_info = /* @__PURE__ */ (() => typeof GM_info != "undefined" ? GM_info : void 0)();
  39. var _GM_registerMenuCommand = /* @__PURE__ */ (() => typeof GM_registerMenuCommand != "undefined" ? GM_registerMenuCommand : void 0)();
  40. var _GM_setValue = /* @__PURE__ */ (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)();
  41. var _GM_unregisterMenuCommand = /* @__PURE__ */ (() => typeof GM_unregisterMenuCommand != "undefined" ? GM_unregisterMenuCommand : void 0)();
  42. var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0)();
  43. var _unsafeWindow = /* @__PURE__ */ (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)();
  44. var _monkeyWindow = /* @__PURE__ */ (() => window)();
  45. const GM_RESOURCE_MAPPING = {
  46. ElementPlus: {
  47. keyName: "ElementPlusResourceCSS",
  48. url: "https://fastly.jsdelivr.net/npm/element-plus@latest/dist/index.min.css"
  49. },
  50. Viewer: {
  51. keyName: "ViewerCSS",
  52. url: "https://fastly.jsdelivr.net/npm/viewerjs@latest/dist/viewer.min.css"
  53. },
  54. Hljs: {
  55. keyName: "HljsCSS",
  56. url: "https://fastly.jsdelivr.net/npm/highlight.js@latest/styles/github-dark.min.css"
  57. }
  58. };
  59. const CommonUtil = {
  60. /**
  61. * 添加屏蔽CSS
  62. * @param args
  63. * @example
  64. * addBlockCSS("")
  65. * addBlockCSS("","")
  66. * addBlockCSS(["",""])
  67. */
  68. addBlockCSS(...args) {
  69. let selectorList = [];
  70. if (args.length === 0) {
  71. return;
  72. }
  73. if (args.length === 1 && typeof args[0] === "string" && args[0].trim() === "") {
  74. return;
  75. }
  76. args.forEach((selector) => {
  77. if (Array.isArray(selector)) {
  78. selectorList = selectorList.concat(selector);
  79. } else {
  80. selectorList.push(selector);
  81. }
  82. });
  83. return addStyle(`${selectorList.join(",\n")}{display: none !important;}`);
  84. },
  85. /**
  86. * 设置GM_getResourceText的style内容
  87. * @param resourceMapData 资源数据
  88. * @example
  89. * setGMResourceCSS({
  90. * keyName: "ViewerCSS",
  91. * url: "https://example.com/example.css",
  92. * })
  93. */
  94. setGMResourceCSS(resourceMapData) {
  95. let cssText = typeof _GM_getResourceText === "function" ? _GM_getResourceText(resourceMapData.keyName) : "";
  96. if (typeof cssText === "string" && cssText) {
  97. addStyle(cssText);
  98. } else {
  99. CommonUtil.loadStyleLink(resourceMapData.url);
  100. }
  101. },
  102. /**
  103. * 添加<link>标签
  104. * @param url
  105. * @example
  106. * loadStyleLink("https://example.com/example.css")
  107. */
  108. async loadStyleLink(url) {
  109. let $link = document.createElement("link");
  110. $link.rel = "stylesheet";
  111. $link.type = "text/css";
  112. $link.href = url;
  113. domutils.ready(() => {
  114. document.head.appendChild($link);
  115. });
  116. },
  117. /**
  118. * 添加<script>标签
  119. * @param url
  120. * @example
  121. * loadStyleLink("https://example.com/example.js")
  122. */
  123. async loadScript(url) {
  124. let $script = document.createElement("script");
  125. $script.src = url;
  126. return new Promise((resolve) => {
  127. $script.onload = () => {
  128. resolve(null);
  129. };
  130. (document.head || document.documentElement).appendChild($script);
  131. });
  132. },
  133. /**
  134. * 将url修复,例如只有search的链接修复为完整的链接
  135. *
  136. * 注意:不包括http转https
  137. * @param url 需要修复的链接
  138. * @example
  139. * 修复前:`/xxx/xxx?ss=ssss`
  140. * 修复后:`https://xxx.xxx.xxx/xxx/xxx?ss=ssss`
  141. * @example
  142. * 修复前:`//xxx/xxx?ss=ssss`
  143. * 修复后:`https://xxx.xxx.xxx/xxx/xxx?ss=ssss`
  144. * @example
  145. * 修复前:`https://xxx.xxx.xxx/xxx/xxx?ss=ssss`
  146. * 修复后:`https://xxx.xxx.xxx/xxx/xxx?ss=ssss`
  147. * @example
  148. * 修复前:`xxx/xxx?ss=ssss`
  149. * 修复后:`https://xxx.xxx.xxx/xxx/xxx?ss=ssss`
  150. */
  151. fixUrl(url) {
  152. url = url.trim();
  153. if (url.match(/^http(s|):\/\//i)) {
  154. return url;
  155. } else {
  156. if (!url.startsWith("/")) {
  157. url += "/";
  158. }
  159. url = window.location.origin + url;
  160. return url;
  161. }
  162. },
  163. /**
  164. * http转https
  165. * @param url 需要修复的链接
  166. * @example
  167. * 修复前:
  168. * 修复后:
  169. * @example
  170. * 修复前:
  171. * 修复后:
  172. */
  173. fixHttps(url) {
  174. if (url.startsWith("https://")) {
  175. return url;
  176. }
  177. if (!url.startsWith("http://")) {
  178. return url;
  179. }
  180. let urlObj = new URL(url);
  181. urlObj.protocol = "https:";
  182. return urlObj.toString();
  183. }
  184. };
  185. const _SCRIPT_NAME_ = "小红书优化";
  186. const utils = Utils.noConflict();
  187. const domutils = DOMUtils.noConflict();
  188. const __pops = pops;
  189. const __viewer = Viewer;
  190. const log = new utils.Log(
  191. _GM_info,
  192. _unsafeWindow.console || _monkeyWindow.console
  193. );
  194. const SCRIPT_NAME = ((_a = _GM_info == null ? void 0 : _GM_info.script) == null ? void 0 : _a.name) || _SCRIPT_NAME_;
  195. const DEBUG = false;
  196. log.config({
  197. debug: DEBUG,
  198. logMaxCount: 1e3,
  199. autoClearConsole: true,
  200. tag: true
  201. });
  202. Qmsg.config(
  203. Object.defineProperties(
  204. {
  205. html: true,
  206. autoClose: true,
  207. showClose: false
  208. },
  209. {
  210. position: {
  211. get() {
  212. return PopsPanel.getValue("qmsg-config-position", "bottom");
  213. }
  214. },
  215. maxNums: {
  216. get() {
  217. return PopsPanel.getValue("qmsg-config-maxnums", 5);
  218. }
  219. },
  220. showReverse: {
  221. get() {
  222. return PopsPanel.getValue("qmsg-config-showreverse", true);
  223. }
  224. },
  225. zIndex: {
  226. get() {
  227. let maxZIndex = Utils.getMaxZIndex();
  228. let popsMaxZIndex = pops.config.InstanceUtils.getPopsMaxZIndex().zIndex;
  229. return Utils.getMaxValue(maxZIndex, popsMaxZIndex) + 100;
  230. }
  231. }
  232. }
  233. )
  234. );
  235. const GM_Menu = new utils.GM_Menu({
  236. GM_getValue: _GM_getValue,
  237. GM_setValue: _GM_setValue,
  238. GM_registerMenuCommand: _GM_registerMenuCommand,
  239. GM_unregisterMenuCommand: _GM_unregisterMenuCommand
  240. });
  241. const httpx = new utils.Httpx(_GM_xmlhttpRequest);
  242. httpx.interceptors.response.use(void 0, (data) => {
  243. log.error("拦截器-请求错误", data);
  244. if (data.type === "onabort") {
  245. Qmsg.warning("请求取消");
  246. } else if (data.type === "onerror") {
  247. Qmsg.error("请求异常");
  248. } else if (data.type === "ontimeout") {
  249. Qmsg.error("请求超时");
  250. } else {
  251. Qmsg.error("其它错误");
  252. }
  253. return data;
  254. });
  255. httpx.config({
  256. logDetails: DEBUG
  257. });
  258. ({
  259. Object: {
  260. defineProperty: _unsafeWindow.Object.defineProperty
  261. },
  262. Function: {
  263. apply: _unsafeWindow.Function.prototype.apply,
  264. call: _unsafeWindow.Function.prototype.call
  265. },
  266. Element: {
  267. appendChild: _unsafeWindow.Element.prototype.appendChild
  268. },
  269. setTimeout: _unsafeWindow.setTimeout
  270. });
  271. const addStyle = utils.addStyle.bind(utils);
  272. document.querySelector.bind(document);
  273. const $$ = document.querySelectorAll.bind(document);
  274. const KEY = "GM_Panel";
  275. const ATTRIBUTE_INIT = "data-init";
  276. const ATTRIBUTE_KEY = "data-key";
  277. const ATTRIBUTE_DEFAULT_VALUE = "data-default-value";
  278. const ATTRIBUTE_INIT_MORE_VALUE = "data-init-more-value";
  279. const PROPS_STORAGE_API = "data-storage-api";
  280. const UISwitch = function(text, key, defaultValue, clickCallBack, description, afterAddToUListCallBack) {
  281. let result = {
  282. text,
  283. type: "switch",
  284. description,
  285. attributes: {},
  286. props: {},
  287. getValue() {
  288. return Boolean(
  289. this.props[PROPS_STORAGE_API].get(key, defaultValue)
  290. );
  291. },
  292. callback(event, __value) {
  293. let value = Boolean(__value);
  294. log.success(`${value ? "开启" : "关闭"} ${text}`);
  295. this.props[PROPS_STORAGE_API].set(key, value);
  296. },
  297. afterAddToUListCallBack
  298. };
  299. Reflect.set(result.attributes, ATTRIBUTE_KEY, key);
  300. Reflect.set(result.attributes, ATTRIBUTE_DEFAULT_VALUE, defaultValue);
  301. Reflect.set(result.props, PROPS_STORAGE_API, {
  302. get(key2, defaultValue2) {
  303. return PopsPanel.getValue(key2, defaultValue2);
  304. },
  305. set(key2, value) {
  306. PopsPanel.setValue(key2, value);
  307. }
  308. });
  309. return result;
  310. };
  311. const MSettingUI_Home = {
  312. id: "little-red-book-panel-config-home",
  313. title: "主页",
  314. forms: [
  315. {
  316. text: "",
  317. type: "forms",
  318. forms: [
  319. {
  320. text: "劫持/拦截",
  321. type: "deepMenu",
  322. forms: [
  323. {
  324. text: "",
  325. type: "forms",
  326. forms: [
  327. UISwitch(
  328. "劫持点击事件",
  329. "little-red-book-repariClick",
  330. true,
  331. void 0,
  332. "可阻止点击跳转至下载页面"
  333. )
  334. ]
  335. }
  336. ]
  337. }
  338. ]
  339. }
  340. ]
  341. };
  342. const MSettingUI_Notes = {
  343. id: "little-red-book-panel-config-note",
  344. title: "笔记",
  345. forms: [
  346. {
  347. text: "",
  348. type: "forms",
  349. forms: [
  350. {
  351. text: "视频笔记",
  352. type: "deepMenu",
  353. forms: [
  354. {
  355. text: "",
  356. type: "forms",
  357. forms: [
  358. UISwitch(
  359. "优化视频描述",
  360. "little-red-book-optimizeVideoNoteDesc",
  361. true,
  362. void 0,
  363. "让视频描述可以滚动显示更多"
  364. ),
  365. UISwitch(
  366. "【屏蔽】作者热门笔记",
  367. "little-red-book-shieldAuthorHotNote",
  368. true,
  369. void 0,
  370. "建议开启"
  371. ),
  372. UISwitch(
  373. "【屏蔽】热门推荐",
  374. "little-red-book-shieldHotRecommendNote",
  375. true,
  376. void 0,
  377. "建议开启"
  378. )
  379. ]
  380. }
  381. ]
  382. }
  383. ]
  384. },
  385. {
  386. text: "",
  387. type: "forms",
  388. forms: [
  389. {
  390. text: "功能",
  391. type: "deepMenu",
  392. forms: [
  393. {
  394. text: "",
  395. type: "forms",
  396. forms: [
  397. UISwitch(
  398. "优化评论浏览",
  399. "little-red-book-optimizeCommentBrowsing",
  400. true,
  401. void 0,
  402. "加载评论,未登录最多查看1页评论(注:楼中楼评论已失效,api无法获取楼中楼评论,需要请求头X-T、X-S、X-B3-Traceid)"
  403. ),
  404. UISwitch(
  405. "优化图片浏览",
  406. "little-red-book-optimizeImageBrowsing",
  407. true,
  408. void 0,
  409. "更方便的浏览图片"
  410. ),
  411. UISwitch(
  412. "允许复制",
  413. "little-red-book-allowCopy",
  414. true,
  415. void 0,
  416. "可以复制笔记的内容"
  417. )
  418. ]
  419. }
  420. ]
  421. },
  422. {
  423. text: "劫持/拦截",
  424. type: "deepMenu",
  425. forms: [
  426. {
  427. text: "",
  428. type: "forms",
  429. forms: [
  430. UISwitch(
  431. "劫持webpack-弹窗",
  432. "little-red-book-hijack-webpack-mask",
  433. true,
  434. void 0,
  435. "如:打开App弹窗、登录弹窗"
  436. ),
  437. UISwitch(
  438. "劫持webpack-唤醒App",
  439. "little-red-book-hijack-webpack-scheme",
  440. true,
  441. void 0,
  442. "禁止跳转商店小红书详情页/小红书"
  443. )
  444. ]
  445. }
  446. ]
  447. }
  448. ]
  449. }
  450. ]
  451. };
  452. const UISelect = function(text, key, defaultValue, data, callback, description) {
  453. let selectData = [];
  454. if (typeof data === "function") {
  455. selectData = data();
  456. } else {
  457. selectData = data;
  458. }
  459. let result = {
  460. text,
  461. type: "select",
  462. description,
  463. attributes: {},
  464. props: {},
  465. getValue() {
  466. return this.props[PROPS_STORAGE_API].get(key, defaultValue);
  467. },
  468. callback(event, isSelectedValue, isSelectedText) {
  469. let value = isSelectedValue;
  470. log.info(`选择:${isSelectedText}`);
  471. this.props[PROPS_STORAGE_API].set(key, value);
  472. if (typeof callback === "function") {
  473. callback(event, value, isSelectedText);
  474. }
  475. },
  476. data: selectData
  477. };
  478. Reflect.set(result.attributes, ATTRIBUTE_KEY, key);
  479. Reflect.set(result.attributes, ATTRIBUTE_DEFAULT_VALUE, defaultValue);
  480. Reflect.set(result.props, PROPS_STORAGE_API, {
  481. get(key2, defaultValue2) {
  482. return PopsPanel.getValue(key2, defaultValue2);
  483. },
  484. set(key2, value) {
  485. PopsPanel.setValue(key2, value);
  486. }
  487. });
  488. return result;
  489. };
  490. const SettingUI_Common = {
  491. id: "xhs-panel-config-common",
  492. title: "通用",
  493. forms: [
  494. {
  495. text: "",
  496. type: "forms",
  497. forms: [
  498. {
  499. text: "功能",
  500. type: "deepMenu",
  501. forms: [
  502. {
  503. text: "",
  504. type: "forms",
  505. forms: [
  506. UISwitch(
  507. "允许复制",
  508. "pc-xhs-allowCopy",
  509. true,
  510. void 0,
  511. "可以选择文字并复制"
  512. ),
  513. UISwitch(
  514. "新标签页打开文章",
  515. "pc-xhs-open-blank-article",
  516. false,
  517. void 0,
  518. "点击文章不会在本页展开,会打开新标签页"
  519. )
  520. ]
  521. }
  522. ]
  523. },
  524. {
  525. text: "搜索",
  526. type: "deepMenu",
  527. forms: [
  528. {
  529. text: "",
  530. type: "forms",
  531. forms: [
  532. UISwitch(
  533. "新标签页打开-搜索按钮",
  534. "pc-xhs-search-open-blank-btn",
  535. false,
  536. void 0,
  537. "点击右边的搜索按钮直接新标签页打开搜索内容"
  538. ),
  539. UISwitch(
  540. "新标签页打开-回车键",
  541. "pc-xhs-search-open-blank-keyboard-enter",
  542. false,
  543. void 0,
  544. "按下回车键直接新标签页打开搜索内容"
  545. )
  546. ]
  547. }
  548. ]
  549. },
  550. {
  551. text: "屏蔽",
  552. type: "deepMenu",
  553. forms: [
  554. {
  555. text: "",
  556. type: "forms",
  557. forms: [
  558. UISwitch(
  559. "【屏蔽】广告",
  560. "pc-xhs-shieldAd",
  561. true,
  562. void 0,
  563. "屏蔽元素"
  564. ),
  565. UISwitch(
  566. "【屏蔽】登录弹窗",
  567. "pc-xhs-shield-login-dialog",
  568. true,
  569. void 0,
  570. "屏蔽会自动弹出的登录弹窗"
  571. ),
  572. UISwitch(
  573. "【屏蔽】选择文字弹出的搜索提示",
  574. "pc-xhs-shield-select-text-search-position",
  575. false,
  576. void 0,
  577. "屏蔽元素"
  578. ),
  579. UISwitch(
  580. "【屏蔽】顶部工具栏",
  581. "pc-xhs-shield-topToolbar",
  582. false,
  583. void 0,
  584. "屏蔽元素"
  585. )
  586. ]
  587. }
  588. ]
  589. },
  590. {
  591. text: "劫持/拦截",
  592. type: "deepMenu",
  593. forms: [
  594. {
  595. text: "",
  596. type: "forms",
  597. forms: [
  598. UISwitch(
  599. "劫持Vue",
  600. "pc-xhs-hook-vue",
  601. true,
  602. void 0,
  603. "恢复__vue__属性"
  604. )
  605. ]
  606. }
  607. ]
  608. },
  609. {
  610. text: "Toast配置",
  611. type: "deepMenu",
  612. forms: [
  613. {
  614. text: "",
  615. type: "forms",
  616. forms: [
  617. UISelect(
  618. "Toast位置",
  619. "qmsg-config-position",
  620. "bottom",
  621. [
  622. {
  623. value: "topleft",
  624. text: "左上角"
  625. },
  626. {
  627. value: "top",
  628. text: "顶部"
  629. },
  630. {
  631. value: "topright",
  632. text: "右上角"
  633. },
  634. {
  635. value: "left",
  636. text: "左边"
  637. },
  638. {
  639. value: "center",
  640. text: "中间"
  641. },
  642. {
  643. value: "right",
  644. text: "右边"
  645. },
  646. {
  647. value: "bottomleft",
  648. text: "左下角"
  649. },
  650. {
  651. value: "bottom",
  652. text: "底部"
  653. },
  654. {
  655. value: "bottomright",
  656. text: "右下角"
  657. }
  658. ],
  659. (event, isSelectValue, isSelectText) => {
  660. log.info("设置当前Qmsg弹出位置" + isSelectText);
  661. },
  662. "Toast显示在页面九宫格的位置"
  663. ),
  664. UISelect(
  665. "最多显示的数量",
  666. "qmsg-config-maxnums",
  667. 3,
  668. [
  669. {
  670. value: 1,
  671. text: "1"
  672. },
  673. {
  674. value: 2,
  675. text: "2"
  676. },
  677. {
  678. value: 3,
  679. text: "3"
  680. },
  681. {
  682. value: 4,
  683. text: "4"
  684. },
  685. {
  686. value: 5,
  687. text: "5"
  688. }
  689. ],
  690. void 0,
  691. "限制Toast显示的数量"
  692. ),
  693. UISwitch(
  694. "逆序弹出",
  695. "qmsg-config-showreverse",
  696. false,
  697. void 0,
  698. "修改Toast弹出的顺序"
  699. )
  700. ]
  701. }
  702. ]
  703. }
  704. ]
  705. }
  706. ]
  707. };
  708. const UISlider = function(text, key, defaultValue, min, max, changeCallBack, getToolTipContent, description, step) {
  709. let result = {
  710. text,
  711. type: "slider",
  712. description,
  713. attributes: {},
  714. props: {},
  715. getValue() {
  716. return this.props[PROPS_STORAGE_API].get(key, defaultValue);
  717. },
  718. getToolTipContent(value) {
  719. if (typeof getToolTipContent === "function") {
  720. return getToolTipContent(value);
  721. } else {
  722. return `${value}`;
  723. }
  724. },
  725. callback(event, value) {
  726. if (typeof changeCallBack === "function") {
  727. if (changeCallBack(event, value)) {
  728. return;
  729. }
  730. }
  731. this.props[PROPS_STORAGE_API].set(key, value);
  732. },
  733. min,
  734. max,
  735. step
  736. };
  737. Reflect.set(result.attributes, ATTRIBUTE_KEY, key);
  738. Reflect.set(result.attributes, ATTRIBUTE_DEFAULT_VALUE, defaultValue);
  739. Reflect.set(result.props, PROPS_STORAGE_API, {
  740. get(key2, defaultValue2) {
  741. return PopsPanel.getValue(key2, defaultValue2);
  742. },
  743. set(key2, value) {
  744. PopsPanel.setValue(key2, value);
  745. }
  746. });
  747. return result;
  748. };
  749. const SettingUI_Article = {
  750. id: "xhs-panel-config-article",
  751. title: "笔记",
  752. forms: [
  753. {
  754. type: "forms",
  755. text: "功能",
  756. forms: [
  757. UISwitch(
  758. "显示发布、修改的绝对时间",
  759. "pc-xhs-article-showPubsliushTime",
  760. false,
  761. void 0,
  762. ""
  763. )
  764. ]
  765. },
  766. {
  767. text: "笔记宽屏",
  768. type: "forms",
  769. forms: [
  770. UISwitch(
  771. "启用",
  772. "pc-xhs-article-fullWidth",
  773. false,
  774. void 0,
  775. `让笔记占据宽屏,当页面可视宽度>=960px时才会触发该功能,当前页面可视宽度: ${window.innerWidth}px`
  776. ),
  777. UISlider(
  778. "占据范围",
  779. "pc-xhs-article-fullWidth-widthSize",
  780. 90,
  781. 30,
  782. 100,
  783. (event, value) => {
  784. let $noteContainer = document.querySelector("#noteContainer");
  785. if (!$noteContainer) {
  786. log.error("未找到笔记容器");
  787. return;
  788. }
  789. $noteContainer.style.width = `${value}vw`;
  790. },
  791. (value) => {
  792. return `${value}%,默认:90%`;
  793. },
  794. "调整笔记页面占据的页面范围"
  795. )
  796. ]
  797. }
  798. ]
  799. };
  800. const MSettingUI_Common = {
  801. id: "little-red-book-panel-config-common",
  802. title: "通用",
  803. forms: [
  804. {
  805. text: "",
  806. type: "forms",
  807. forms: [
  808. {
  809. text: "Toast配置",
  810. type: "deepMenu",
  811. forms: [
  812. {
  813. text: "",
  814. type: "forms",
  815. forms: [
  816. UISelect(
  817. "Toast位置",
  818. "qmsg-config-position",
  819. "bottom",
  820. [
  821. {
  822. value: "topleft",
  823. text: "左上角"
  824. },
  825. {
  826. value: "top",
  827. text: "顶部"
  828. },
  829. {
  830. value: "topright",
  831. text: "右上角"
  832. },
  833. {
  834. value: "left",
  835. text: "左边"
  836. },
  837. {
  838. value: "center",
  839. text: "中间"
  840. },
  841. {
  842. value: "right",
  843. text: "右边"
  844. },
  845. {
  846. value: "bottomleft",
  847. text: "左下角"
  848. },
  849. {
  850. value: "bottom",
  851. text: "底部"
  852. },
  853. {
  854. value: "bottomright",
  855. text: "右下角"
  856. }
  857. ],
  858. (event, isSelectValue, isSelectText) => {
  859. log.info("设置当前Qmsg弹出位置" + isSelectText);
  860. },
  861. "Toast显示在页面九宫格的位置"
  862. ),
  863. UISelect(
  864. "最多显示的数量",
  865. "qmsg-config-maxnums",
  866. 3,
  867. [
  868. {
  869. value: 1,
  870. text: "1"
  871. },
  872. {
  873. value: 2,
  874. text: "2"
  875. },
  876. {
  877. value: 3,
  878. text: "3"
  879. },
  880. {
  881. value: 4,
  882. text: "4"
  883. },
  884. {
  885. value: 5,
  886. text: "5"
  887. }
  888. ],
  889. void 0,
  890. "限制Toast显示的数量"
  891. ),
  892. UISwitch(
  893. "逆序弹出",
  894. "qmsg-config-showreverse",
  895. false,
  896. void 0,
  897. "修改Toast弹出的顺序"
  898. )
  899. ]
  900. }
  901. ]
  902. }
  903. ]
  904. },
  905. {
  906. text: "",
  907. type: "forms",
  908. forms: [
  909. {
  910. text: "屏蔽",
  911. type: "deepMenu",
  912. forms: [
  913. {
  914. text: "",
  915. type: "forms",
  916. forms: [
  917. UISwitch(
  918. "【屏蔽】广告",
  919. "little-red-book-shieldAd",
  920. true,
  921. void 0,
  922. "如:App内打开"
  923. ),
  924. UISwitch(
  925. "【屏蔽】底部搜索发现",
  926. "little-red-book-shieldBottomSearchFind",
  927. true,
  928. void 0,
  929. "建议开启"
  930. ),
  931. UISwitch(
  932. "【屏蔽】底部工具栏",
  933. "little-red-book-shieldBottomToorBar",
  934. true,
  935. void 0,
  936. "建议开启"
  937. )
  938. ]
  939. }
  940. ]
  941. },
  942. {
  943. text: "劫持/拦截",
  944. type: "deepMenu",
  945. forms: [
  946. {
  947. text: "",
  948. type: "forms",
  949. forms: [
  950. UISwitch(
  951. "劫持Vue",
  952. "little-red-book-hijack-vue",
  953. true,
  954. void 0,
  955. "恢复__vue__属性"
  956. )
  957. ]
  958. }
  959. ]
  960. }
  961. ]
  962. }
  963. ]
  964. };
  965. const PanelUISize = {
  966. /**
  967. * 一般设置界面的尺寸
  968. */
  969. setting: {
  970. get width() {
  971. return window.innerWidth < 550 ? "88vw" : "550px";
  972. },
  973. get height() {
  974. return window.innerHeight < 450 ? "70vh" : "450px";
  975. }
  976. },
  977. /**
  978. * 功能丰富,aside铺满了的设置界面,要稍微大一点
  979. */
  980. settingBig: {
  981. get width() {
  982. return window.innerWidth < 800 ? "92vw" : "800px";
  983. },
  984. get height() {
  985. return window.innerHeight < 600 ? "80vh" : "600px";
  986. }
  987. },
  988. /**
  989. * 信息界面,一般用于提示信息之类
  990. */
  991. info: {
  992. get width() {
  993. return window.innerWidth < 350 ? "350px" : "350px";
  994. },
  995. get height() {
  996. return window.innerHeight < 250 ? "250px" : "250px";
  997. }
  998. }
  999. };
  1000. const PopsPanel = {
  1001. /** 数据 */
  1002. $data: {
  1003. __data: null,
  1004. __oneSuccessExecMenu: null,
  1005. __onceExec: null,
  1006. __listenData: null,
  1007. /**
  1008. * 菜单项的默认值
  1009. */
  1010. get data() {
  1011. if (PopsPanel.$data.__data == null) {
  1012. PopsPanel.$data.__data = new utils.Dictionary();
  1013. }
  1014. return PopsPanel.$data.__data;
  1015. },
  1016. /**
  1017. * 成功只执行了一次的项
  1018. */
  1019. get oneSuccessExecMenu() {
  1020. if (PopsPanel.$data.__oneSuccessExecMenu == null) {
  1021. PopsPanel.$data.__oneSuccessExecMenu = new utils.Dictionary();
  1022. }
  1023. return PopsPanel.$data.__oneSuccessExecMenu;
  1024. },
  1025. /**
  1026. * 成功只执行了一次的项
  1027. */
  1028. get onceExec() {
  1029. if (PopsPanel.$data.__onceExec == null) {
  1030. PopsPanel.$data.__onceExec = new utils.Dictionary();
  1031. }
  1032. return PopsPanel.$data.__onceExec;
  1033. },
  1034. /** 脚本名,一般用在设置的标题上 */
  1035. get scriptName() {
  1036. return SCRIPT_NAME;
  1037. },
  1038. /** 菜单项的总值在本地数据配置的键名 */
  1039. key: KEY,
  1040. /** 菜单项在attributes上配置的菜单键 */
  1041. attributeKeyName: ATTRIBUTE_KEY,
  1042. /** 菜单项在attributes上配置的菜单默认值 */
  1043. attributeDefaultValueName: ATTRIBUTE_DEFAULT_VALUE
  1044. },
  1045. /** 监听器 */
  1046. $listener: {
  1047. /**
  1048. * 值改变的监听器
  1049. */
  1050. get listenData() {
  1051. if (PopsPanel.$data.__listenData == null) {
  1052. PopsPanel.$data.__listenData = new utils.Dictionary();
  1053. }
  1054. return PopsPanel.$data.__listenData;
  1055. }
  1056. },
  1057. init() {
  1058. this.initPanelDefaultValue();
  1059. this.initExtensionsMenu();
  1060. },
  1061. /** 判断是否是顶层窗口 */
  1062. isTopWindow() {
  1063. return _unsafeWindow.top === _unsafeWindow.self;
  1064. },
  1065. initExtensionsMenu() {
  1066. if (_unsafeWindow.top !== _unsafeWindow.self) {
  1067. return;
  1068. }
  1069. GM_Menu.add([
  1070. {
  1071. key: "show_pops_panel_setting",
  1072. text: "⚙ 移动端-设置",
  1073. autoReload: false,
  1074. isStoreValue: false,
  1075. showText(text) {
  1076. return text;
  1077. },
  1078. callback: () => {
  1079. this.showPanel();
  1080. }
  1081. },
  1082. {
  1083. key: "show_pops_panel_setting",
  1084. text: "⚙ PC-设置",
  1085. autoReload: false,
  1086. isStoreValue: false,
  1087. showText(text) {
  1088. return text;
  1089. },
  1090. callback: () => {
  1091. this.showPCPanel();
  1092. }
  1093. }
  1094. ]);
  1095. },
  1096. /** 初始化菜单项的默认值保存到本地数据中 */
  1097. initPanelDefaultValue() {
  1098. let that = this;
  1099. function initDefaultValue(config) {
  1100. if (!config.attributes) {
  1101. return;
  1102. }
  1103. let needInitConfig = {};
  1104. let key = config.attributes[ATTRIBUTE_KEY];
  1105. if (key != null) {
  1106. needInitConfig[key] = config.attributes[ATTRIBUTE_DEFAULT_VALUE];
  1107. }
  1108. let __attr_init__ = config.attributes[ATTRIBUTE_INIT];
  1109. if (typeof __attr_init__ === "function") {
  1110. let __attr_result__ = __attr_init__();
  1111. if (typeof __attr_result__ === "boolean" && !__attr_result__) {
  1112. return;
  1113. }
  1114. }
  1115. let initMoreValue = config.attributes[ATTRIBUTE_INIT_MORE_VALUE];
  1116. if (initMoreValue && typeof initMoreValue === "object") {
  1117. Object.assign(needInitConfig, initMoreValue);
  1118. }
  1119. let needInitConfigList = Object.keys(needInitConfig);
  1120. if (!needInitConfigList.length) {
  1121. log.warn(["请先配置键", config]);
  1122. return;
  1123. }
  1124. needInitConfigList.forEach((__key) => {
  1125. let __defaultValue = needInitConfig[__key];
  1126. if (that.$data.data.has(__key)) {
  1127. log.warn("请检查该key(已存在): " + __key);
  1128. }
  1129. that.$data.data.set(__key, __defaultValue);
  1130. });
  1131. }
  1132. function loopInitDefaultValue(configList) {
  1133. for (let index = 0; index < configList.length; index++) {
  1134. let configItem = configList[index];
  1135. initDefaultValue(configItem);
  1136. let childForms = configItem.forms;
  1137. if (childForms && Array.isArray(childForms)) {
  1138. loopInitDefaultValue(childForms);
  1139. }
  1140. }
  1141. }
  1142. let contentConfigList = this.getPanelContentConfig().concat(
  1143. this.getPCPanelContentConfig()
  1144. );
  1145. for (let index = 0; index < contentConfigList.length; index++) {
  1146. let leftContentConfigItem = contentConfigList[index];
  1147. if (!leftContentConfigItem.forms) {
  1148. continue;
  1149. }
  1150. let rightContentConfigList = leftContentConfigItem.forms;
  1151. if (rightContentConfigList && Array.isArray(rightContentConfigList)) {
  1152. loopInitDefaultValue(rightContentConfigList);
  1153. }
  1154. }
  1155. },
  1156. /**
  1157. * 设置值
  1158. * @param key 键
  1159. * @param value 值
  1160. */
  1161. setValue(key, value) {
  1162. let locaData = _GM_getValue(KEY, {});
  1163. let oldValue = locaData[key];
  1164. locaData[key] = value;
  1165. _GM_setValue(KEY, locaData);
  1166. if (this.$listener.listenData.has(key)) {
  1167. this.$listener.listenData.get(key).callback(key, oldValue, value);
  1168. }
  1169. },
  1170. /**
  1171. * 获取值
  1172. * @param key 键
  1173. * @param defaultValue 默认值
  1174. */
  1175. getValue(key, defaultValue) {
  1176. let locaData = _GM_getValue(KEY, {});
  1177. let localValue = locaData[key];
  1178. if (localValue == null) {
  1179. if (this.$data.data.has(key)) {
  1180. return this.$data.data.get(key);
  1181. }
  1182. return defaultValue;
  1183. }
  1184. return localValue;
  1185. },
  1186. /**
  1187. * 删除值
  1188. * @param key 键
  1189. */
  1190. deleteValue(key) {
  1191. let locaData = _GM_getValue(KEY, {});
  1192. let oldValue = locaData[key];
  1193. Reflect.deleteProperty(locaData, key);
  1194. _GM_setValue(KEY, locaData);
  1195. if (this.$listener.listenData.has(key)) {
  1196. this.$listener.listenData.get(key).callback(key, oldValue, void 0);
  1197. }
  1198. },
  1199. /**
  1200. * 监听调用setValue、deleteValue
  1201. * @param key 需要监听的键
  1202. * @param callback
  1203. */
  1204. addValueChangeListener(key, callback) {
  1205. let listenerId = Math.random();
  1206. this.$listener.listenData.set(key, {
  1207. id: listenerId,
  1208. key,
  1209. callback
  1210. });
  1211. return listenerId;
  1212. },
  1213. /**
  1214. * 移除监听
  1215. * @param listenerId 监听的id
  1216. */
  1217. removeValueChangeListener(listenerId) {
  1218. let deleteKey = null;
  1219. for (const [key, value] of this.$listener.listenData.entries()) {
  1220. if (value.id === listenerId) {
  1221. deleteKey = key;
  1222. break;
  1223. }
  1224. }
  1225. if (typeof deleteKey === "string") {
  1226. this.$listener.listenData.delete(deleteKey);
  1227. } else {
  1228. console.warn("没有找到对应的监听器");
  1229. }
  1230. },
  1231. /**
  1232. * 主动触发菜单值改变的回调
  1233. * @param key 菜单键
  1234. * @param newValue 想要触发的新值,默认使用当前值
  1235. * @param oldValue 想要触发的旧值,默认使用当前值
  1236. */
  1237. triggerMenuValueChange(key, newValue, oldValue) {
  1238. if (this.$listener.listenData.has(key)) {
  1239. let listenData = this.$listener.listenData.get(key);
  1240. if (typeof listenData.callback === "function") {
  1241. let value = this.getValue(key);
  1242. let __newValue = value;
  1243. let __oldValue = value;
  1244. if (typeof newValue !== "undefined" && arguments.length > 1) {
  1245. __newValue = newValue;
  1246. }
  1247. if (typeof oldValue !== "undefined" && arguments.length > 2) {
  1248. __oldValue = oldValue;
  1249. }
  1250. listenData.callback(key, __oldValue, __newValue);
  1251. }
  1252. }
  1253. },
  1254. /**
  1255. * 判断该键是否存在
  1256. * @param key 键
  1257. */
  1258. hasKey(key) {
  1259. let locaData = _GM_getValue(KEY, {});
  1260. return key in locaData;
  1261. },
  1262. /**
  1263. * 自动判断菜单是否启用,然后执行回调
  1264. * @param key
  1265. * @param callback 回调
  1266. * @param [isReverse=false] 逆反判断菜单启用
  1267. */
  1268. execMenu(key, callback, isReverse = false) {
  1269. if (!(typeof key === "string" || typeof key === "object" && Array.isArray(key))) {
  1270. throw new TypeError("key 必须是字符串或者字符串数组");
  1271. }
  1272. let runKeyList = [];
  1273. if (typeof key === "object" && Array.isArray(key)) {
  1274. runKeyList = [...key];
  1275. } else {
  1276. runKeyList.push(key);
  1277. }
  1278. let value = void 0;
  1279. for (let index = 0; index < runKeyList.length; index++) {
  1280. const runKey = runKeyList[index];
  1281. if (!this.$data.data.has(runKey)) {
  1282. log.warn(`${key} 键不存在`);
  1283. return;
  1284. }
  1285. let runValue = PopsPanel.getValue(runKey);
  1286. if (isReverse) {
  1287. runValue = !runValue;
  1288. }
  1289. if (!runValue) {
  1290. break;
  1291. }
  1292. value = runValue;
  1293. }
  1294. if (value) {
  1295. callback(value);
  1296. }
  1297. },
  1298. /**
  1299. * 自动判断菜单是否启用,然后执行回调,只会执行一次
  1300. * @param key
  1301. * @param callback 回调
  1302. * @param getValueFn 自定义处理获取当前值,值true是启用并执行回调,值false是不执行回调
  1303. * @param handleValueChangeFn 自定义处理值改变时的回调,值true是启用并执行回调,值false是不执行回调
  1304. */
  1305. execMenuOnce(key, callback, getValueFn, handleValueChangeFn) {
  1306. if (typeof key !== "string") {
  1307. throw new TypeError("key 必须是字符串");
  1308. }
  1309. if (!this.$data.data.has(key)) {
  1310. log.warn(`${key} 键不存在`);
  1311. return;
  1312. }
  1313. if (this.$data.oneSuccessExecMenu.has(key)) {
  1314. return;
  1315. }
  1316. this.$data.oneSuccessExecMenu.set(key, 1);
  1317. let __getValue = () => {
  1318. let localValue = PopsPanel.getValue(key);
  1319. return typeof getValueFn === "function" ? getValueFn(key, localValue) : localValue;
  1320. };
  1321. let resultStyleList = [];
  1322. let dynamicPushStyleNode = ($style) => {
  1323. let __value = __getValue();
  1324. let dynamicResultList = [];
  1325. if ($style instanceof HTMLStyleElement) {
  1326. dynamicResultList = [$style];
  1327. } else if (Array.isArray($style)) {
  1328. dynamicResultList = [
  1329. ...$style.filter(
  1330. (item) => item != null && item instanceof HTMLStyleElement
  1331. )
  1332. ];
  1333. }
  1334. if (__value) {
  1335. resultStyleList = resultStyleList.concat(dynamicResultList);
  1336. } else {
  1337. for (let index = 0; index < dynamicResultList.length; index++) {
  1338. let $css = dynamicResultList[index];
  1339. $css.remove();
  1340. dynamicResultList.splice(index, 1);
  1341. index--;
  1342. }
  1343. }
  1344. };
  1345. let changeCallBack = (currentValue) => {
  1346. let resultList = [];
  1347. if (currentValue) {
  1348. let result = callback(currentValue, dynamicPushStyleNode);
  1349. if (result instanceof HTMLStyleElement) {
  1350. resultList = [result];
  1351. } else if (Array.isArray(result)) {
  1352. resultList = [
  1353. ...result.filter(
  1354. (item) => item != null && item instanceof HTMLStyleElement
  1355. )
  1356. ];
  1357. }
  1358. }
  1359. for (let index = 0; index < resultStyleList.length; index++) {
  1360. let $css = resultStyleList[index];
  1361. $css.remove();
  1362. resultStyleList.splice(index, 1);
  1363. index--;
  1364. }
  1365. resultStyleList = [...resultList];
  1366. };
  1367. this.addValueChangeListener(
  1368. key,
  1369. (__key, oldValue, newValue) => {
  1370. let __newValue = newValue;
  1371. if (typeof handleValueChangeFn === "function") {
  1372. __newValue = handleValueChangeFn(__key, newValue, oldValue);
  1373. }
  1374. changeCallBack(__newValue);
  1375. }
  1376. );
  1377. let value = __getValue();
  1378. if (value) {
  1379. changeCallBack(value);
  1380. }
  1381. },
  1382. /**
  1383. * 父子菜单联动,自动判断菜单是否启用,然后执行回调,只会执行一次
  1384. * @param key 菜单键
  1385. * @param childKey 子菜单键
  1386. * @param callback 回调
  1387. * @param replaceValueFn 用于修改mainValue,返回undefined则不做处理
  1388. */
  1389. execInheritMenuOnce(key, childKey, callback, replaceValueFn) {
  1390. let that = this;
  1391. const handleInheritValue = (key2, childKey2) => {
  1392. let mainValue = that.getValue(key2);
  1393. let childValue = that.getValue(childKey2);
  1394. if (typeof replaceValueFn === "function") {
  1395. let changedMainValue = replaceValueFn(mainValue, childValue);
  1396. if (changedMainValue !== void 0) {
  1397. return changedMainValue;
  1398. }
  1399. }
  1400. return mainValue;
  1401. };
  1402. this.execMenuOnce(
  1403. key,
  1404. callback,
  1405. () => {
  1406. return handleInheritValue(key, childKey);
  1407. },
  1408. () => {
  1409. return handleInheritValue(key, childKey);
  1410. }
  1411. );
  1412. this.execMenuOnce(
  1413. childKey,
  1414. () => {
  1415. },
  1416. () => false,
  1417. () => {
  1418. this.triggerMenuValueChange(key);
  1419. return false;
  1420. }
  1421. );
  1422. },
  1423. /**
  1424. * 根据key执行一次
  1425. * @param key
  1426. */
  1427. onceExec(key, callback) {
  1428. if (typeof key !== "string") {
  1429. throw new TypeError("key 必须是字符串");
  1430. }
  1431. if (this.$data.onceExec.has(key)) {
  1432. return;
  1433. }
  1434. callback();
  1435. this.$data.onceExec.set(key, 1);
  1436. },
  1437. /**
  1438. * 显示设置面板
  1439. */
  1440. showPanel() {
  1441. __pops.panel({
  1442. title: {
  1443. text: `${SCRIPT_NAME}-移动端设置`,
  1444. position: "center",
  1445. html: false,
  1446. style: ""
  1447. },
  1448. content: this.getPanelContentConfig(),
  1449. mask: {
  1450. enable: true,
  1451. clickEvent: {
  1452. toClose: true,
  1453. toHide: false
  1454. }
  1455. },
  1456. width: PanelUISize.setting.width,
  1457. height: PanelUISize.setting.height,
  1458. drag: true,
  1459. only: true
  1460. });
  1461. },
  1462. /**
  1463. * 显示设置面板
  1464. */
  1465. showPCPanel() {
  1466. __pops.panel({
  1467. title: {
  1468. text: `${SCRIPT_NAME}-设置`,
  1469. position: "center",
  1470. html: false,
  1471. style: ""
  1472. },
  1473. content: this.getPCPanelContentConfig(),
  1474. mask: {
  1475. enable: true,
  1476. clickEvent: {
  1477. toClose: true,
  1478. toHide: false
  1479. }
  1480. },
  1481. width: PanelUISize.setting.width,
  1482. height: PanelUISize.setting.height,
  1483. drag: true,
  1484. only: true
  1485. });
  1486. },
  1487. /**
  1488. * 获取配置内容
  1489. */
  1490. getPanelContentConfig() {
  1491. let configList = [
  1492. MSettingUI_Common,
  1493. MSettingUI_Home,
  1494. MSettingUI_Notes
  1495. ];
  1496. return configList;
  1497. },
  1498. /**
  1499. * 获取配置内容
  1500. */
  1501. getPCPanelContentConfig() {
  1502. let configList = [
  1503. SettingUI_Common,
  1504. SettingUI_Article
  1505. ];
  1506. return configList;
  1507. }
  1508. };
  1509. const XHS_Hook = {
  1510. /**
  1511. * 劫持webpack
  1512. * 笔记的
  1513. */
  1514. webpackChunkranchi() {
  1515. let originObject = void 0;
  1516. let webpackName = "webpackChunkranchi";
  1517. Object.defineProperty(_unsafeWindow, webpackName, {
  1518. get() {
  1519. return originObject;
  1520. },
  1521. set(newValue) {
  1522. originObject = newValue;
  1523. const oldPush = originObject.push;
  1524. originObject.push = function(...args) {
  1525. args[0][0];
  1526. if (typeof args[0][1] === "object") {
  1527. Object.keys(args[0][1]).forEach((keyName, index) => {
  1528. if (typeof args[0][1][keyName] === "function" && args[0][1][keyName].toString().includes("是否打开小红书App?") && PopsPanel.getValue("little-red-book-hijack-webpack-mask")) {
  1529. log.success(["成功劫持各种弹窗/遮罩层:" + keyName]);
  1530. args[0][1][keyName] = function() {
  1531. };
  1532. } else if (typeof args[0][1][keyName] === "function" && args[0][1][keyName].toString().startsWith(
  1533. "function(e,n,t){t.d(n,{Z:function(){return y}});"
  1534. ) && args[0][1][keyName].toString().includes("jumpToApp") && PopsPanel.getValue("little-red-book-hijack-webpack-scheme")) {
  1535. let oldFunc = args[0][1][keyName];
  1536. args[0][1][keyName] = function(...args_1) {
  1537. log.success(["成功劫持scheme唤醒", args_1]);
  1538. let oldD = args_1[2].d;
  1539. args_1[2].d = function(...args_2) {
  1540. var _a2;
  1541. if (args_2.length === 2 && typeof ((_a2 = args_2[1]) == null ? void 0 : _a2["Z"]) === "function") {
  1542. let oldZ = args_2[1]["Z"];
  1543. if (oldZ.toString() === "function(){return y}") {
  1544. args_2[1]["Z"] = function(...args_3) {
  1545. let result = oldZ.call(this, ...args_3);
  1546. if (typeof result === "function" && result.toString().includes("jumpToApp")) {
  1547. return function() {
  1548. return {
  1549. jumpToApp(data) {
  1550. var _a3;
  1551. log.success(["拦截唤醒", data]);
  1552. if ((_a3 = data["deeplink"]) == null ? void 0 : _a3.startsWith(
  1553. "xhsdiscover://user/"
  1554. )) {
  1555. let userId = data["deeplink"].replace(
  1556. /^xhsdiscover:\/\/user\//,
  1557. ""
  1558. );
  1559. let userHomeUrl = `https://www.xiaohongshu.com/user/profile/${userId}`;
  1560. window.open(userHomeUrl, "_blank");
  1561. }
  1562. }
  1563. };
  1564. };
  1565. }
  1566. return result;
  1567. };
  1568. }
  1569. }
  1570. oldD.call(this, ...args_2);
  1571. };
  1572. oldFunc.call(this, ...args_1);
  1573. };
  1574. }
  1575. });
  1576. }
  1577. return oldPush.call(this, ...args);
  1578. };
  1579. }
  1580. });
  1581. },
  1582. /**
  1583. * 劫持vue,恢复属性__Ivue__
  1584. */
  1585. webPackVue() {
  1586. let originApply = _unsafeWindow.Function.prototype.apply;
  1587. let isHijack = false;
  1588. _unsafeWindow.Function.prototype.apply = function(...args) {
  1589. var _a2, _b, _c, _d, _e, _f;
  1590. const result = originApply.call(this, ...args);
  1591. if (!isHijack && args.length === 2 && ((_a2 = args[0]) == null ? void 0 : _a2.addRoute) && ((_b = args[0]) == null ? void 0 : _b.currentRoute) && ((_c = args[0]) == null ? void 0 : _c.getRoutes) && ((_d = args[0]) == null ? void 0 : _d.hasRoute) && ((_e = args[0]) == null ? void 0 : _e.install) && ((_f = args[0]) == null ? void 0 : _f.removeRoute)) {
  1592. isHijack = true;
  1593. let __vue__ = args[1][0];
  1594. log.success(["成功劫持vue,version版本:", __vue__.version]);
  1595. __vue__["mixin"]({
  1596. mounted: function() {
  1597. this.$el["__Ivue__"] = this;
  1598. }
  1599. });
  1600. }
  1601. return result;
  1602. };
  1603. }
  1604. };
  1605. const blockCSS$2 = "/* 用户主页 */\r\n/* 底部的-App内打开 */\r\n.launch-app-container.bottom-bar,\r\n/* 顶部的-打开看看 */\r\n.main-container > .scroll-view-container > .launch-app-container:first-child,\r\n/* 底部的-打开小红书看更多精彩内容 */\r\n.bottom-launch-app-tip.show-bottom-bar {\r\n display: none !important;\r\n}\r\n";
  1606. const ScriptRouter = {
  1607. /**
  1608. * 判断是否是笔记页面
  1609. */
  1610. isArticle() {
  1611. return globalThis.location.pathname.startsWith("/discovery/item/") || globalThis.location.pathname.startsWith("/explore/");
  1612. },
  1613. /**
  1614. * 判断是否是用户主页页面
  1615. */
  1616. isUserHome() {
  1617. return globalThis.location.pathname.startsWith("/user/profile/");
  1618. },
  1619. /**
  1620. * 判断是否是主页
  1621. */
  1622. isHome() {
  1623. return globalThis.location.href === "https://www.xiaohongshu.com/" || globalThis.location.href === "https://www.xiaohongshu.com";
  1624. },
  1625. /**
  1626. * 判断是否是搜索页面
  1627. */
  1628. isSearch() {
  1629. return globalThis.location.pathname.startsWith("/search_result/");
  1630. }
  1631. };
  1632. const XHS_BASE_URL = "https://edith.xiaohongshu.com";
  1633. const XHSApi = {
  1634. /**
  1635. * 获取页信息
  1636. */
  1637. async getPageInfo(note_id, cursor = "", top_comment_id = "", image_formats = "jpg,webp") {
  1638. const Api = `/api/sns/web/v2/comment/page`;
  1639. const SearchParamsData = {
  1640. note_id,
  1641. cursor,
  1642. top_comment_id,
  1643. image_formats
  1644. };
  1645. const SearchParams = Api + "?" + utils.toSearchParamsStr(SearchParamsData);
  1646. let getResp = await httpx.get(`${XHS_BASE_URL}${SearchParams}`, {
  1647. headers: {
  1648. Accept: "application/json, text/plain, */*",
  1649. "User-Agent": utils.getRandomPCUA(),
  1650. Origin: "https://www.xiaohongshu.com",
  1651. Referer: "https://www.xiaohongshu.com/"
  1652. // "X-S": signInfo.xs,
  1653. // "X-T": signInfo.xt,
  1654. }
  1655. });
  1656. if (!getResp.status) {
  1657. return;
  1658. }
  1659. let data = utils.toJSON(getResp.data.responseText);
  1660. log.info(["获取页信息", data]);
  1661. if (data["code"] === 0 || data["success"]) {
  1662. return data["data"];
  1663. } else if (data["code"] === -101) {
  1664. return;
  1665. } else {
  1666. Qmsg.error(data["msg"]);
  1667. }
  1668. },
  1669. /**
  1670. * 获取楼中楼页信息
  1671. */
  1672. async getLzlPageInfo(note_id = "", root_comment_id = "", num = 10, cursor = "", image_formats = "jpg,webp,avif", top_comment_id = "") {
  1673. const Api = `/api/sns/web/v2/comment/sub/page`;
  1674. let ApiData = {
  1675. note_id,
  1676. root_comment_id,
  1677. num,
  1678. cursor,
  1679. image_formats,
  1680. top_comment_id
  1681. };
  1682. Api + "?" + utils.toSearchParamsStr(ApiData);
  1683. let url = `${XHS_BASE_URL}${Api}?${utils.toSearchParamsStr(ApiData)}`;
  1684. let getResp = await httpx.get(url, {
  1685. headers: {
  1686. Accept: "application/json, text/plain, */*",
  1687. "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
  1688. Host: "edith.xiaohongshu.com",
  1689. Origin: "https://www.xiaohongshu.com",
  1690. Referer: "https://www.xiaohongshu.com/"
  1691. // "X-S": signInfo.xs,
  1692. // "X-T": signInfo.xt,
  1693. // "X-S-Common": signInfo.xsCommon,
  1694. // "X-B3-Traceid": signInfo.traceId,
  1695. },
  1696. onerror() {
  1697. }
  1698. });
  1699. if (!getResp.status) {
  1700. if (getResp.data.status === 406 && utils.isNotNull(getResp.data.responseText)) {
  1701. let errorData = utils.toJSON(getResp.data.responseText);
  1702. if (errorData["code"] == -1) {
  1703. Qmsg.error("获取楼中楼信息失败,验证x-s、x-t、x-s-common失败");
  1704. } else {
  1705. Qmsg.error("获取楼中楼信息失败");
  1706. }
  1707. } else {
  1708. Qmsg.error("请求异常");
  1709. }
  1710. log.error(["获取楼中楼信息失败", getResp]);
  1711. return;
  1712. }
  1713. let data = utils.toJSON(getResp.data.responseText);
  1714. log.info(["获取楼中楼页信息", data]);
  1715. if (data["code"] === 0 || data["success"]) {
  1716. return data["data"];
  1717. } else {
  1718. Qmsg.error(data["msg"]);
  1719. }
  1720. },
  1721. /**
  1722. * 获取搜索推荐内容
  1723. * @param searchText
  1724. */
  1725. async getSearchRecommend(searchText) {
  1726. let getResp = await httpx.get(
  1727. `https://edith.xiaohongshu.com/api/sns/web/v1/search/recommend?keyword=${searchText}`,
  1728. {
  1729. fetch: true
  1730. }
  1731. );
  1732. if (!getResp.status) {
  1733. return;
  1734. }
  1735. let data = utils.toJSON(getResp.data.responseText);
  1736. if (!(data.success || data.code === 1e3)) {
  1737. return;
  1738. }
  1739. return data.data.sug_items;
  1740. }
  1741. };
  1742. const M_XHSArticleBlock = {
  1743. /**
  1744. * 允许复制
  1745. */
  1746. allowCopy() {
  1747. log.info("允许复制");
  1748. return addStyle(
  1749. /*css*/
  1750. `
  1751. *{
  1752. -webkit-user-select: unset;
  1753. user-select: unset;
  1754. }
  1755. `
  1756. );
  1757. },
  1758. /**
  1759. * 屏蔽底部搜索发现
  1760. */
  1761. blockBottomSearchFind() {
  1762. log.info("屏蔽底部搜索发现");
  1763. return CommonUtil.addBlockCSS(
  1764. ".hotlist-container",
  1765. /* 一大块空白区域 */
  1766. ".safe-area-bottom.margin-placeholder"
  1767. );
  1768. },
  1769. /**
  1770. * 屏蔽底部工具栏
  1771. */
  1772. blockBottomToorBar() {
  1773. log.info("屏蔽底部工具栏");
  1774. return CommonUtil.addBlockCSS(".engage-bar-container");
  1775. },
  1776. /**
  1777. * 屏蔽视频笔记的作者热门笔记
  1778. */
  1779. blockAuthorHotNote() {
  1780. log.info("屏蔽视频笔记的作者热门笔记");
  1781. return CommonUtil.addBlockCSS(
  1782. ".user-notes-box.user-notes-clo-layout-container"
  1783. );
  1784. },
  1785. /**
  1786. * 屏蔽视频笔记的热门推荐
  1787. */
  1788. blockHotRecommendNote() {
  1789. log.info("屏蔽视频笔记的热门推荐");
  1790. return CommonUtil.addBlockCSS("#new-note-view-container .recommend-box");
  1791. }
  1792. };
  1793. const M_XHSArticleVideo = {
  1794. /**
  1795. * 优化视频笔记的描述(可滚动)
  1796. */
  1797. optimizeVideoNoteDesc() {
  1798. log.info("优化视频笔记的描述(可滚动)");
  1799. return addStyle(
  1800. /*css*/
  1801. `
  1802. .author-box .author-desc-wrapper .author-desc{
  1803. max-height: 70px !important;
  1804. overflow: auto !important;
  1805. }
  1806. /* 展开按钮 */
  1807. .author-box .author-desc-wrapper .author-desc .author-desc-trigger{
  1808. display: none !important;
  1809. }`
  1810. );
  1811. }
  1812. };
  1813. const blockCSS$1 = "/* 底部的App内打开 */\r\n.bottom-button-box,\r\n/* 顶部的打开看看 */\r\n.nav-bar-box {\r\n display: none !important;\r\n}\r\n";
  1814. const M_XHSArticle = {
  1815. init() {
  1816. addStyle(blockCSS$1);
  1817. if (PopsPanel.getValue("little-red-book-hijack-webpack-mask") || PopsPanel.getValue("little-red-book-hijack-webpack-scheme")) {
  1818. log.info("劫持webpack");
  1819. XHS_Hook.webpackChunkranchi();
  1820. }
  1821. PopsPanel.execMenuOnce("little-red-book-shieldBottomSearchFind", () => {
  1822. return M_XHSArticleBlock.blockBottomSearchFind();
  1823. });
  1824. PopsPanel.execMenuOnce("little-red-book-shieldBottomToorBar", () => {
  1825. return M_XHSArticleBlock.blockBottomToorBar();
  1826. });
  1827. PopsPanel.execMenuOnce("little-red-book-optimizeImageBrowsing", () => {
  1828. M_XHSArticle.optimizeImageBrowsing();
  1829. });
  1830. PopsPanel.execMenuOnce("little-red-book-optimizeVideoNoteDesc", () => {
  1831. return M_XHSArticleVideo.optimizeVideoNoteDesc();
  1832. });
  1833. PopsPanel.execMenuOnce("little-red-book-shieldAuthorHotNote", () => {
  1834. return M_XHSArticleBlock.blockAuthorHotNote();
  1835. });
  1836. PopsPanel.execMenuOnce("little-red-book-shieldHotRecommendNote", () => {
  1837. return M_XHSArticleBlock.blockHotRecommendNote();
  1838. });
  1839. domutils.ready(function() {
  1840. PopsPanel.execMenu("little-red-book-optimizeCommentBrowsing", () => {
  1841. M_XHSArticle.optimizeCommentBrowsing();
  1842. });
  1843. });
  1844. },
  1845. /**
  1846. * 优化评论浏览
  1847. */
  1848. optimizeCommentBrowsing() {
  1849. log.info("优化评论浏览");
  1850. const Comments = {
  1851. QmsgLoading: void 0,
  1852. scrollFunc: void 0,
  1853. noteData: {},
  1854. commentData: {},
  1855. emojiMap: {},
  1856. emojiNameList: [],
  1857. currentCursor: void 0,
  1858. commentContainer: void 0,
  1859. init() {
  1860. var _a2;
  1861. this.emojiMap = ((_a2 = utils.toJSON(_unsafeWindow.localStorage.getItem("redmoji"))) == null ? void 0 : _a2["redmojiMap"]) || {};
  1862. this.emojiNameList = Object.keys(this.emojiMap);
  1863. this.scrollFunc = new utils.LockFunction(this.scrollEvent, this);
  1864. Comments.noteData = _unsafeWindow["__INITIAL_STATE__"].noteData.data.noteData;
  1865. Comments.commentData = _unsafeWindow["__INITIAL_STATE__"].noteData.data.commentData;
  1866. log.info(["笔记数据", Comments.noteData]);
  1867. log.info(["评论数据", Comments.commentData]);
  1868. },
  1869. /**
  1870. *
  1871. * @param data
  1872. * @returns
  1873. */
  1874. getCommentHTML(data) {
  1875. return (
  1876. /*html*/
  1877. `
  1878. <div class="little-red-book-comments-avatar">
  1879. <a target="_blank" href="/user/profile/${data.user_id}">
  1880. <img src="${data.user_avatar}" crossorigin="anonymous">
  1881. </a>
  1882. </div>
  1883. <div class="little-red-book-comments-content-wrapper">
  1884. <div class="little-red-book-comments-author-wrapper">
  1885. <div class="little-red-book-comments-author">
  1886. <a href="/user/profile/${data.user_id}" class="little-red-book-comments-author-name" target="_blank">
  1887. ${data.user_nickname}
  1888. </a>
  1889. </div>
  1890. <div class="little-red-book-comments-content">
  1891. ${data.content}
  1892. </div>
  1893. <div class="little-red-book-comments-info">
  1894. <div class="little-red-book-comments-info-date">
  1895. <span class="little-red-book-comments-create-time">${utils.formatTime(
  1896. data.create_time
  1897. )}</span>
  1898. <span class="little-red-book-comments-location">${data.ip_location}</span>
  1899. </div>
  1900. </div>
  1901. </div>
  1902. </div>
  1903. `
  1904. );
  1905. },
  1906. /**
  1907. * 获取内容元素
  1908. * @param {object} data
  1909. * @returns
  1910. */
  1911. getCommentElement(data) {
  1912. var _a2, _b;
  1913. let content = data["content"];
  1914. let create_time = data["create_time"] || parseInt(data["time"]);
  1915. let id = data["id"];
  1916. let ip_location = data["ip_location"] || data["ipLocation"];
  1917. let sub_comment_has_more = data["sub_comment_has_more"];
  1918. let sub_comment_count = parseInt(data["sub_comment_count"]) || 0;
  1919. let sub_comment_cursor = data["sub_comment_cursor"];
  1920. let sub_comments = data["sub_comments"] || data["subComments"];
  1921. let user_avatar = (data["user_info"] || data["user"])["image"];
  1922. let user_nickname = (data["user_info"] || data["user"])["nickname"];
  1923. let user_id = ((_a2 = data == null ? void 0 : data["user_info"]) == null ? void 0 : _a2["user_id"]) || ((_b = data == null ? void 0 : data["user"]) == null ? void 0 : _b["userId"]);
  1924. content = Comments.converContent(content);
  1925. let commentItemElement = domutils.createElement("div", {
  1926. className: "little-red-book-comments-item",
  1927. innerHTML: (
  1928. /*html*/
  1929. `
  1930. <div class="little-red-book-comments-parent">
  1931. ${Comments.getCommentHTML({
  1932. user_id,
  1933. user_avatar,
  1934. user_nickname,
  1935. content,
  1936. create_time,
  1937. ip_location
  1938. })}
  1939. </div>
  1940. `
  1941. )
  1942. });
  1943. if (sub_comment_has_more && Array.isArray(sub_comments)) {
  1944. sub_comments.forEach((subCommentInfo) => {
  1945. let subCommentElement = domutils.createElement("div", {
  1946. className: "little-red-book-comments-reply-container",
  1947. innerHTML: Comments.getCommentHTML({
  1948. user_id: subCommentInfo["user_info"]["user_id"],
  1949. user_avatar: subCommentInfo["user_info"]["image"],
  1950. user_nickname: subCommentInfo["user_info"]["nickname"],
  1951. content: Comments.converContent(subCommentInfo["content"]),
  1952. create_time: subCommentInfo["create_time"],
  1953. ip_location: subCommentInfo["ip_location"]
  1954. })
  1955. });
  1956. commentItemElement.appendChild(subCommentElement);
  1957. });
  1958. if (sub_comment_count !== sub_comments.length) {
  1959. let endReplyCount = sub_comment_count - sub_comments.length;
  1960. let lzlCursor = sub_comment_cursor;
  1961. let showMoreElement = domutils.createElement("div", {
  1962. className: "little-red-book-comments-reply-show-more",
  1963. innerText: `展开 ${endReplyCount} 条回复`
  1964. });
  1965. async function showMoreEvent() {
  1966. let QmsgLoading = Qmsg.loading("加载中,请稍后...");
  1967. let pageInfo = await XHSApi.getLzlPageInfo(
  1968. Comments.noteData["id"],
  1969. id,
  1970. 10,
  1971. lzlCursor,
  1972. void 0
  1973. );
  1974. QmsgLoading.close();
  1975. if (!pageInfo) {
  1976. return;
  1977. }
  1978. lzlCursor = pageInfo.cursor;
  1979. endReplyCount = endReplyCount - pageInfo.comments.length;
  1980. showMoreElement.innerText = `展开 ${endReplyCount} 条回复`;
  1981. pageInfo.comments.forEach((subCommentInfo) => {
  1982. let subCommentElement = domutils.createElement("div", {
  1983. className: "little-red-book-comments-reply-container",
  1984. innerHTML: Comments.getCommentHTML({
  1985. user_id: subCommentInfo["user_info"]["user_id"],
  1986. user_avatar: subCommentInfo["user_info"]["image"],
  1987. user_nickname: subCommentInfo["user_info"]["nickname"],
  1988. content: Comments.converContent(subCommentInfo["content"]),
  1989. create_time: subCommentInfo["create_time"],
  1990. ip_location: subCommentInfo["ip_location"]
  1991. })
  1992. });
  1993. domutils.before(showMoreElement, subCommentElement);
  1994. });
  1995. if (!pageInfo.has_more) {
  1996. domutils.off(
  1997. showMoreElement,
  1998. "click",
  1999. void 0,
  2000. showMoreEvent,
  2001. {
  2002. capture: true
  2003. }
  2004. );
  2005. showMoreElement.remove();
  2006. }
  2007. }
  2008. domutils.on(showMoreElement, "click", void 0, showMoreEvent, {
  2009. capture: true
  2010. });
  2011. commentItemElement.appendChild(showMoreElement);
  2012. }
  2013. }
  2014. return commentItemElement;
  2015. },
  2016. /**
  2017. * 转换内容字符串中的emoji
  2018. */
  2019. converContent(content) {
  2020. Comments.emojiNameList.forEach((emojiName) => {
  2021. if (content.includes(emojiName)) {
  2022. content = content.replaceAll(
  2023. emojiName,
  2024. /*html*/
  2025. `<img class="little-red-book-note-content-emoji" crossorigin="anonymous" src="${Comments.emojiMap[emojiName]}">`
  2026. );
  2027. }
  2028. });
  2029. return content;
  2030. },
  2031. /**
  2032. * 滚动事件
  2033. */
  2034. async scrollEvent() {
  2035. if (!utils.isNearBottom(window.innerHeight / 3)) {
  2036. return;
  2037. }
  2038. if (this.QmsgLoading == null) {
  2039. this.QmsgLoading = Qmsg.loading("加载中,请稍后...");
  2040. }
  2041. let pageInfo = await XHSApi.getPageInfo(
  2042. Comments.noteData["id"],
  2043. Comments.currentCursor
  2044. );
  2045. if (this.QmsgLoading) {
  2046. this.QmsgLoading.close();
  2047. this.QmsgLoading = void 0;
  2048. }
  2049. if (!pageInfo) {
  2050. return;
  2051. }
  2052. Comments.currentCursor = pageInfo.cursor;
  2053. pageInfo.comments.forEach((commentItem) => {
  2054. let commentItemElement = Comments.getCommentElement(commentItem);
  2055. Comments.commentContainer.appendChild(commentItemElement);
  2056. });
  2057. if (!pageInfo.has_more) {
  2058. Qmsg.info("已加载全部评论");
  2059. Comments.removeScrollEventListener();
  2060. return;
  2061. }
  2062. },
  2063. /**
  2064. * 添加滚动监听
  2065. */
  2066. addSrollEventListener() {
  2067. log.success("添加滚动监听事件");
  2068. domutils.on(document, "scroll", void 0, Comments.scrollFunc.run, {
  2069. capture: true,
  2070. once: false,
  2071. passive: true
  2072. });
  2073. },
  2074. /**
  2075. * 移除滚动监听
  2076. */
  2077. removeScrollEventListener() {
  2078. log.success("移除滚动监听事件");
  2079. domutils.off(document, "scroll", void 0, Comments.scrollFunc.run, {
  2080. capture: true
  2081. });
  2082. }
  2083. };
  2084. utils.waitNode(".narmal-note-container").then(async () => {
  2085. log.info("优化评论浏览-笔记元素出现");
  2086. let noteViewContainer = document.querySelector(
  2087. ".note-view-container"
  2088. );
  2089. let loading = Qmsg.loading("获取评论中,请稍后...");
  2090. let commentContainer = domutils.createElement("div", {
  2091. className: "little-red-book-comments-container",
  2092. innerHTML: (
  2093. /*html*/
  2094. `
  2095. <style>
  2096. .little-red-book-comments-parent {
  2097. position: relative;
  2098. display: flex;
  2099. padding: 8px;
  2100. width: 100%;
  2101. }
  2102. .little-red-book-comments-reply-container {
  2103. position: relative;
  2104. display: flex;
  2105. padding: 8px;
  2106. width: 100%;
  2107. padding-left: 52px;
  2108. }
  2109. .little-red-book-comments-container {
  2110. background: #fff;
  2111. position: relative;
  2112. padding: 8px 8px;
  2113. }
  2114. .little-red-book-comments-item {
  2115. position: relative;
  2116. }
  2117. .little-red-book-comments-avatar {
  2118. flex: 0 0 auto;
  2119. }
  2120. .little-red-book-comments-avatar img {
  2121. display: flex;
  2122. align-items: center;
  2123. justify-content: center;
  2124. cursor: pointer;
  2125. border-radius: 100%;
  2126. border: 1px solid rgba(0,0,0,0.08);
  2127. object-fit: cover;
  2128. width: 40px;
  2129. height: 40px;
  2130. }
  2131. .little-red-book-comments-content-wrapper {
  2132. margin-left: 12px;
  2133. display: flex;
  2134. flex-direction: column;
  2135. font-size: 14px;
  2136. flex-grow: 1;
  2137. }
  2138. .little-red-book-comments-author {display: flex;justify-content: space-between;align-items: center;}
  2139. a.little-red-book-comments-author-name {
  2140. line-height: 18px;
  2141. color: rgba(51,51,51,0.6);
  2142. }
  2143. .little-red-book-comments-content {
  2144. margin-top: 4px;
  2145. line-height: 140%;
  2146. color: #333;
  2147. }
  2148. .little-red-book-comments-info {
  2149. display: flex;
  2150. flex-direction: column;
  2151. justify-content: space-between;
  2152. font-size: 12px;
  2153. line-height: 16px;
  2154. color: rgba(51,51,51,0.6);
  2155. }
  2156. .little-red-book-comments-info-date {
  2157. margin: 8px 0;
  2158. }
  2159. span.little-red-book-comments-location {
  2160. margin-left: 4px;
  2161. line-height: 120%;
  2162. }
  2163. img.little-red-book-note-content-emoji {
  2164. margin: 0 1px;
  2165. height: 16px;
  2166. transform: translateY(2px);
  2167. position: relative;
  2168. }
  2169. .little-red-book-comments-reply-container .little-red-book-comments-avatar img {
  2170. width: 24px;
  2171. height: 24px;
  2172. }
  2173. .little-red-book-comments-total{
  2174. font-size: 14px;
  2175. color: rgba(51,51,51,0.6);
  2176. margin-left: 8px;
  2177. margin-bottom: 12px;
  2178. }
  2179. .little-red-book-comments-reply-show-more {
  2180. padding-left: calc(52px + 24px + 12px);
  2181. height: 32px;
  2182. line-height: 32px;
  2183. color: #13386c;
  2184. cursor: pointer;
  2185. font-weight: 500;
  2186. font-size: 14px;
  2187. }
  2188. </style>
  2189. `
  2190. )
  2191. });
  2192. Comments.commentContainer = commentContainer;
  2193. Comments.init();
  2194. let totalElement = domutils.createElement("div", {
  2195. className: "little-red-book-comments-total",
  2196. innerHTML: `共 ${Comments.noteData["comments"]} 条评论`
  2197. });
  2198. commentContainer.appendChild(totalElement);
  2199. let pageInfo = await XHSApi.getPageInfo(Comments.noteData["id"]);
  2200. await utils.sleep(800);
  2201. if (pageInfo) {
  2202. Comments.currentCursor = pageInfo.cursor;
  2203. pageInfo.comments.forEach((commentItem) => {
  2204. let commentItemElement = Comments.getCommentElement(commentItem);
  2205. commentContainer.appendChild(commentItemElement);
  2206. });
  2207. if (pageInfo.has_more) {
  2208. Comments.addSrollEventListener();
  2209. }
  2210. } else if (Comments.commentData && Comments.commentData["comments"]) {
  2211. log.info("从固定的评论中加载");
  2212. Comments.commentData["comments"].forEach((commentItem) => {
  2213. let commentItemElement = Comments.getCommentElement(commentItem);
  2214. commentContainer.appendChild(commentItemElement);
  2215. });
  2216. }
  2217. loading.close();
  2218. domutils.append(noteViewContainer, commentContainer);
  2219. });
  2220. },
  2221. /**
  2222. * 优化图片浏览
  2223. */
  2224. optimizeImageBrowsing() {
  2225. log.info("优化图片浏览");
  2226. CommonUtil.setGMResourceCSS(GM_RESOURCE_MAPPING.Viewer);
  2227. function viewIMG(imgSrcList = [], index = 0) {
  2228. let viewerULNodeHTML = "";
  2229. imgSrcList.forEach((item) => {
  2230. viewerULNodeHTML += `<li><img data-src="${item}" loading="lazy"></li>`;
  2231. });
  2232. let viewerULNode = domutils.createElement("ul", {
  2233. innerHTML: viewerULNodeHTML
  2234. });
  2235. let viewer = new __viewer(viewerULNode, {
  2236. inline: false,
  2237. url: "data-src",
  2238. zIndex: utils.getMaxZIndex() + 100,
  2239. hidden: () => {
  2240. viewer.destroy();
  2241. }
  2242. });
  2243. index = index < 0 ? 0 : index;
  2244. viewer.view(index);
  2245. viewer.zoomTo(1);
  2246. viewer.show();
  2247. }
  2248. domutils.on(document, "click", ".note-image-box", function(event) {
  2249. let clickElement = event.target;
  2250. let imgElement = clickElement.querySelector("img");
  2251. let imgList = [];
  2252. let imgBoxList = [];
  2253. if (clickElement.closest(".onix-carousel-item")) {
  2254. imgBoxList = Array.from(
  2255. clickElement.closest(".onix-carousel-item").parentElement.querySelectorAll("img")
  2256. );
  2257. } else {
  2258. imgBoxList = [imgElement];
  2259. }
  2260. let index = imgBoxList.findIndex((value) => {
  2261. return value == imgElement;
  2262. });
  2263. imgBoxList.forEach((element) => {
  2264. let imgSrc = element.getAttribute("src") || element.getAttribute("data-src") || element.getAttribute("alt");
  2265. if (imgSrc) {
  2266. imgList.push(imgSrc);
  2267. }
  2268. });
  2269. log.success(["点击浏览图片👉", imgList[index]]);
  2270. viewIMG(imgList, index);
  2271. });
  2272. }
  2273. };
  2274. const M_XHSHome = {
  2275. init() {
  2276. domutils.ready(() => {
  2277. PopsPanel.execMenuOnce("little-red-book-repariClick", () => {
  2278. M_XHSHome.repariClick();
  2279. });
  2280. });
  2281. },
  2282. /**
  2283. * 修复正确的点击跳转-用户主页
  2284. * 点啥都不好使,都会跳转至下载页面
  2285. */
  2286. repariClick() {
  2287. log.info("修复正确的点击跳转");
  2288. domutils.on(
  2289. document,
  2290. "click",
  2291. void 0,
  2292. function(event) {
  2293. var _a2, _b, _c, _d, _e;
  2294. let clickElement = event.target;
  2295. log.info(["点击的按钮元素", clickElement]);
  2296. if ((_a2 = clickElement == null ? void 0 : clickElement.className) == null ? void 0 : _a2.includes("follow-btn")) {
  2297. log.success("点击-关注按钮");
  2298. } else if (clickElement == null ? void 0 : clickElement.closest("button.reds-button.message-btn")) {
  2299. log.success("点击-私信按钮");
  2300. } else if (clickElement == null ? void 0 : clickElement.closest("div.reds-tab-item")) {
  2301. log.success("点击-笔记/收藏按钮");
  2302. } else if (clickElement == null ? void 0 : clickElement.closest("section.reds-note-card")) {
  2303. log.success("点击-笔记卡片");
  2304. let sectionElement = clickElement == null ? void 0 : clickElement.closest(
  2305. "section.reds-note-card"
  2306. );
  2307. let note_id = sectionElement.getAttribute("id") || ((_d = (_c = (_b = utils.toJSON(sectionElement.getAttribute("impression"))) == null ? void 0 : _b["noteTarget"]) == null ? void 0 : _c["value"]) == null ? void 0 : _d["noteId"]);
  2308. if (note_id) {
  2309. window.open(
  2310. `https://www.xiaohongshu.com/discovery/item/${(_e = clickElement == null ? void 0 : clickElement.closest("section.reds-note-card")) == null ? void 0 : _e.getAttribute("id")}`,
  2311. "_blank"
  2312. );
  2313. } else {
  2314. Qmsg.error("获取笔记note_id失败");
  2315. }
  2316. }
  2317. utils.preventEvent(event);
  2318. return false;
  2319. },
  2320. {
  2321. capture: true
  2322. }
  2323. );
  2324. }
  2325. };
  2326. const M_XHS = {
  2327. init() {
  2328. PopsPanel.execMenu("little-red-book-hijack-vue", () => {
  2329. log.info("劫持页面的Vue");
  2330. XHS_Hook.webPackVue();
  2331. });
  2332. PopsPanel.execMenuOnce("little-red-book-shieldAd", () => {
  2333. log.info("注入默认屏蔽CSS");
  2334. return addStyle(blockCSS$2);
  2335. });
  2336. PopsPanel.execMenuOnce("little-red-book-allowCopy", () => {
  2337. return M_XHS.allowCopy();
  2338. });
  2339. if (ScriptRouter.isArticle()) {
  2340. M_XHSArticle.init();
  2341. } else if (ScriptRouter.isUserHome()) {
  2342. M_XHSHome.init();
  2343. }
  2344. },
  2345. /**
  2346. * 允许复制
  2347. */
  2348. allowCopy() {
  2349. log.info("允许复制文字");
  2350. return addStyle(
  2351. /*css*/
  2352. `
  2353. *{
  2354. -webkit-user-select: unset;
  2355. user-select: unset;
  2356. }
  2357. `
  2358. );
  2359. }
  2360. };
  2361. const blockCSS = "";
  2362. const XHSBlock = {
  2363. init() {
  2364. PopsPanel.execMenuOnce("pc-xhs-shieldAd", () => {
  2365. return addStyle(blockCSS);
  2366. });
  2367. PopsPanel.execMenuOnce("pc-xhs-shield-select-text-search-position", () => {
  2368. return this.blockSelectTextVisibleSearchPosition();
  2369. });
  2370. PopsPanel.execMenuOnce("pc-xhs-shield-topToolbar", () => {
  2371. return this.blockTopToolbar();
  2372. });
  2373. domutils.ready(() => {
  2374. PopsPanel.execMenuOnce("pc-xhs-shield-login-dialog", () => {
  2375. this.blockLoginContainer();
  2376. });
  2377. });
  2378. },
  2379. /**
  2380. * 屏蔽登录弹窗显示
  2381. */
  2382. blockLoginContainer() {
  2383. log.info("添加屏蔽登录弹窗CSS,监听登录弹窗出现");
  2384. CommonUtil.addBlockCSS(".login-container");
  2385. utils.mutationObserver(document.body, {
  2386. config: {
  2387. subtree: true,
  2388. childList: true
  2389. },
  2390. callback: () => {
  2391. let $close = document.querySelector(
  2392. ".login-container .icon-btn-wrapper"
  2393. );
  2394. if ($close) {
  2395. $close.click();
  2396. log.success("登录弹窗出现,关闭");
  2397. }
  2398. }
  2399. });
  2400. },
  2401. /**
  2402. * 屏蔽选择文字弹出的搜索提示
  2403. */
  2404. blockSelectTextVisibleSearchPosition() {
  2405. log.info("屏蔽选择文字弹出的搜索提示");
  2406. return CommonUtil.addBlockCSS(".search-position");
  2407. },
  2408. /**
  2409. * 【屏蔽】顶部工具栏
  2410. */
  2411. blockTopToolbar() {
  2412. log.info("【屏蔽】顶部工具栏");
  2413. return [
  2414. CommonUtil.addBlockCSS("#headerContainer"),
  2415. addStyle(
  2416. /*css*/
  2417. `
  2418. /* 主内容去除padding */
  2419. #mfContainer{
  2420. padding-top: 0 !important;
  2421. }
  2422. .outer-link-container{
  2423. margin-top: 0 !important;
  2424. height: 100vh !important;
  2425. padding: 30px 0;
  2426. }
  2427. #noteContainer{
  2428. height: 100%;
  2429. }
  2430. `
  2431. )
  2432. ];
  2433. }
  2434. };
  2435. const XHSUrlApi = {
  2436. /**
  2437. * 获取搜索链接
  2438. * @param searchText
  2439. * @returns
  2440. */
  2441. getSearchUrl(searchText) {
  2442. return `https://www.xiaohongshu.com/search_result?keyword=${searchText}&source=web_explore_feed`;
  2443. }
  2444. };
  2445. const VueUtils = {
  2446. /**
  2447. * 获取vue2实例
  2448. * @param element
  2449. * @returns
  2450. */
  2451. getVue(element) {
  2452. if (element == null) {
  2453. return;
  2454. }
  2455. return element["__vue__"] || element["__Ivue__"] || element["__IVue__"];
  2456. },
  2457. /**
  2458. * 获取vue3实例
  2459. * @param element
  2460. * @returns
  2461. */
  2462. getVue3(element) {
  2463. if (element == null) {
  2464. return;
  2465. }
  2466. return element["__vueParentComponent"];
  2467. },
  2468. /**
  2469. * 等待vue属性并进行设置
  2470. * @param $target 目标对象
  2471. * @param needSetList 需要设置的配置
  2472. */
  2473. waitVuePropToSet($target, needSetList) {
  2474. if (!Array.isArray(needSetList)) {
  2475. VueUtils.waitVuePropToSet($target, [needSetList]);
  2476. return;
  2477. }
  2478. function getTarget() {
  2479. let __target__ = null;
  2480. if (typeof $target === "string") {
  2481. __target__ = document.querySelector($target);
  2482. } else if (typeof $target === "function") {
  2483. __target__ = $target();
  2484. } else if ($target instanceof HTMLElement) {
  2485. __target__ = $target;
  2486. }
  2487. return __target__;
  2488. }
  2489. needSetList.forEach((needSetOption) => {
  2490. if (typeof needSetOption.msg === "string") {
  2491. log.info(needSetOption.msg);
  2492. }
  2493. function checkVue() {
  2494. let target = getTarget();
  2495. if (target == null) {
  2496. return false;
  2497. }
  2498. let vueInstance = VueUtils.getVue(target);
  2499. if (vueInstance == null) {
  2500. return false;
  2501. }
  2502. let needOwnCheck = needSetOption.check(vueInstance);
  2503. return Boolean(needOwnCheck);
  2504. }
  2505. utils.waitVueByInterval(
  2506. () => {
  2507. return getTarget();
  2508. },
  2509. checkVue,
  2510. 250,
  2511. 1e4
  2512. ).then((result) => {
  2513. if (!result) {
  2514. if (typeof needSetOption.failWait === "function") {
  2515. needSetOption.failWait(true);
  2516. }
  2517. return;
  2518. }
  2519. let target = getTarget();
  2520. let vueInstance = VueUtils.getVue(target);
  2521. if (vueInstance == null) {
  2522. if (typeof needSetOption.failWait === "function") {
  2523. needSetOption.failWait(false);
  2524. }
  2525. return;
  2526. }
  2527. needSetOption.set(vueInstance);
  2528. });
  2529. });
  2530. },
  2531. /**
  2532. * 观察vue属性的变化
  2533. * @param $target 目标对象
  2534. * @param key 需要观察的属性
  2535. * @param callback 监听回调
  2536. * @param watchConfig 监听配置
  2537. * @param failWait 当检测失败/超时触发该回调
  2538. */
  2539. watchVuePropChange($target, key, callback, watchConfig, failWait) {
  2540. let config = utils.assign(
  2541. {
  2542. immediate: true,
  2543. deep: false
  2544. },
  2545. watchConfig || {}
  2546. );
  2547. return new Promise((resolve) => {
  2548. VueUtils.waitVuePropToSet($target, {
  2549. check(vueInstance) {
  2550. return typeof (vueInstance == null ? void 0 : vueInstance.$watch) === "function";
  2551. },
  2552. set(vueInstance) {
  2553. let removeWatch = null;
  2554. if (typeof key === "function") {
  2555. removeWatch = vueInstance.$watch(
  2556. () => {
  2557. return key(vueInstance);
  2558. },
  2559. (newValue, oldValue) => {
  2560. callback(vueInstance, newValue, oldValue);
  2561. },
  2562. config
  2563. );
  2564. } else {
  2565. removeWatch = vueInstance.$watch(
  2566. key,
  2567. (newValue, oldValue) => {
  2568. callback(vueInstance, newValue, oldValue);
  2569. },
  2570. config
  2571. );
  2572. }
  2573. resolve(removeWatch);
  2574. },
  2575. failWait
  2576. });
  2577. });
  2578. },
  2579. /**
  2580. * 前往网址
  2581. * @param $vueNode 包含vue属性的元素
  2582. * @param path 需要跳转的路径
  2583. * @param [useRouter=false] 是否强制使用Vue的Router来进行跳转
  2584. */
  2585. goToUrl($vueNode, path, useRouter = false) {
  2586. if ($vueNode == null) {
  2587. Qmsg.error("跳转Url: $vueNode为空");
  2588. log.error("跳转Url: $vueNode为空:" + path);
  2589. return;
  2590. }
  2591. let vueObj = VueUtils.getVue($vueNode);
  2592. if (vueObj == null) {
  2593. Qmsg.error("获取vue属性失败", { consoleLogContent: true });
  2594. return;
  2595. }
  2596. let $router = vueObj.$router;
  2597. let isBlank = true;
  2598. log.info("即将跳转URL:" + path);
  2599. if (useRouter) {
  2600. isBlank = false;
  2601. }
  2602. if (isBlank) {
  2603. window.open(path, "_blank");
  2604. } else {
  2605. if (path.startsWith("http") || path.startsWith("//")) {
  2606. if (path.startsWith("//")) {
  2607. path = window.location.protocol + path;
  2608. }
  2609. let urlObj = new URL(path);
  2610. if (urlObj.origin === window.location.origin) {
  2611. path = urlObj.pathname + urlObj.search + urlObj.hash;
  2612. } else {
  2613. log.info("不同域名,直接本页打开,不用Router:" + path);
  2614. window.location.href = path;
  2615. return;
  2616. }
  2617. }
  2618. log.info("$router push跳转Url:" + path);
  2619. $router.push(path);
  2620. }
  2621. },
  2622. /**
  2623. * 手势返回
  2624. * @param option 配置
  2625. */
  2626. hookGestureReturnByVueRouter(option) {
  2627. function popstateEvent() {
  2628. log.success("触发popstate事件");
  2629. resumeBack(true);
  2630. }
  2631. function banBack() {
  2632. log.success("监听地址改变");
  2633. option.vueInstance.$router.history.push(option.hash);
  2634. domutils.on(_unsafeWindow, "popstate", popstateEvent);
  2635. }
  2636. async function resumeBack(isFromPopState = false) {
  2637. domutils.off(_unsafeWindow, "popstate", popstateEvent);
  2638. let callbackResult = option.callback(isFromPopState);
  2639. if (callbackResult) {
  2640. return;
  2641. }
  2642. while (1) {
  2643. if (option.vueInstance.$router.history.current.hash === option.hash) {
  2644. log.info("后退!");
  2645. option.vueInstance.$router.back();
  2646. await utils.sleep(250);
  2647. } else {
  2648. return;
  2649. }
  2650. }
  2651. }
  2652. banBack();
  2653. return {
  2654. resumeBack
  2655. };
  2656. }
  2657. };
  2658. const XHS_Article = {
  2659. init() {
  2660. if (PopsPanel.getValue("pc-xhs-search-open-blank-btn") || PopsPanel.getValue("pc-xhs-search-open-blank-keyboard-enter")) {
  2661. this.optimizationSearch();
  2662. }
  2663. PopsPanel.execMenuOnce("pc-xhs-article-fullWidth", () => {
  2664. return this.fullWidth();
  2665. });
  2666. },
  2667. /**
  2668. * 优化搜索
  2669. */
  2670. optimizationSearch() {
  2671. function blankSearchText(searchText, isBlank = true) {
  2672. {
  2673. let $searchText = document.querySelector("#search-input");
  2674. if ($searchText) {
  2675. let searchText2 = $searchText.value;
  2676. let searchUrl = XHSUrlApi.getSearchUrl(searchText2);
  2677. log.info("搜索内容: " + searchText2);
  2678. window.open(searchUrl, isBlank ? "_blank" : "_self");
  2679. } else {
  2680. Qmsg.error("未找到搜索的输入框");
  2681. }
  2682. }
  2683. }
  2684. utils.waitNode("#search-input").then(($searchInput) => {
  2685. $searchInput.placeholder = "搜索小红书";
  2686. PopsPanel.execMenu("pc-xhs-search-open-blank-keyboard-enter", () => {
  2687. domutils.listenKeyboard(
  2688. $searchInput,
  2689. "keydown",
  2690. (keyName, keyValue, otherCodeList, event) => {
  2691. if (keyName === "Enter" && !otherCodeList.length) {
  2692. log.info("按下回车键");
  2693. utils.preventEvent(event);
  2694. $searchInput.blur();
  2695. blankSearchText();
  2696. }
  2697. }
  2698. );
  2699. });
  2700. });
  2701. utils.waitNode("#search-input + .input-button .search-icon").then(($searchIconBtn) => {
  2702. PopsPanel.execMenu("pc-xhs-search-open-blank-btn", () => {
  2703. domutils.on(
  2704. $searchIconBtn,
  2705. "click",
  2706. (event) => {
  2707. utils.preventEvent(event);
  2708. log.info("点击搜索按钮");
  2709. blankSearchText();
  2710. },
  2711. {
  2712. capture: true
  2713. }
  2714. );
  2715. });
  2716. });
  2717. },
  2718. /**
  2719. * 笔记宽屏
  2720. */
  2721. fullWidth() {
  2722. log.info("笔记宽屏");
  2723. let noteContainerWidth = PopsPanel.getValue(
  2724. "pc-xhs-article-fullWidth-widthSize",
  2725. 90
  2726. );
  2727. return addStyle(
  2728. /*css*/
  2729. `
  2730. .main-container .main-content{
  2731. padding-left: 0 !important;
  2732. }
  2733. .outer-link-container{
  2734. width: 100vw !important;
  2735. }
  2736. /* 隐藏左侧工具栏 */
  2737. .main-container .side-bar{
  2738. display: none !important;
  2739. }
  2740. #noteContainer{
  2741. width: ${noteContainerWidth}vw;
  2742. }
  2743. `
  2744. );
  2745. },
  2746. /**
  2747. * 转换笔记发布时间
  2748. */
  2749. transformPublishTime() {
  2750. log.info(`转换笔记发布时间`);
  2751. let lockFn = new utils.LockFunction(() => {
  2752. $$(".note-content:not([data-edit-date])").forEach(
  2753. ($noteContent) => {
  2754. var _a2, _b;
  2755. let vueInstance = VueUtils.getVue($noteContent);
  2756. if (!vueInstance) {
  2757. return;
  2758. }
  2759. let note = (_b = (_a2 = vueInstance == null ? void 0 : vueInstance._) == null ? void 0 : _a2.props) == null ? void 0 : _b.note;
  2760. if (note == null) {
  2761. return;
  2762. }
  2763. let publishTime = note.time;
  2764. let lastUpdateTime = note.lastUpdateTime;
  2765. let ipLocation = note.ipLocation;
  2766. if (typeof publishTime === "number") {
  2767. let detailTimeLocationInfo = [];
  2768. detailTimeLocationInfo.push(
  2769. `发布:${utils.formatTime(publishTime)}`
  2770. );
  2771. if (typeof lastUpdateTime === "number") {
  2772. detailTimeLocationInfo.push(
  2773. `修改:${utils.formatTime(lastUpdateTime)}`
  2774. );
  2775. }
  2776. if (typeof ipLocation === "string" && utils.isNotNull(ipLocation)) {
  2777. detailTimeLocationInfo.push(ipLocation);
  2778. }
  2779. let $date = $noteContent.querySelector(".date");
  2780. domutils.html($date, detailTimeLocationInfo.join("<br>"));
  2781. $noteContent.setAttribute("data-edit-date", "");
  2782. }
  2783. }
  2784. );
  2785. });
  2786. utils.mutationObserver(document, {
  2787. config: {
  2788. subtree: true,
  2789. childList: true
  2790. },
  2791. callback: () => {
  2792. lockFn.run();
  2793. }
  2794. });
  2795. }
  2796. };
  2797. const XHS = {
  2798. init() {
  2799. PopsPanel.execMenuOnce("pc-xhs-hook-vue", () => {
  2800. XHS_Hook.webPackVue();
  2801. });
  2802. PopsPanel.execMenuOnce("pc-xhs-allowCopy", () => {
  2803. XHS.allowPCCopy();
  2804. });
  2805. PopsPanel.execMenuOnce("pc-xhs-open-blank-article", () => {
  2806. XHS.openBlankArticle();
  2807. });
  2808. XHSBlock.init();
  2809. PopsPanel.execMenuOnce("pc-xhs-article-showPubsliushTime", () => {
  2810. XHS_Article.transformPublishTime();
  2811. });
  2812. if (ScriptRouter.isArticle()) {
  2813. log.info("Router: 笔记页面");
  2814. XHS_Article.init();
  2815. }
  2816. },
  2817. /**
  2818. * 允许复制
  2819. */
  2820. allowPCCopy() {
  2821. log.success("允许复制文字");
  2822. domutils.on(
  2823. _unsafeWindow,
  2824. "copy",
  2825. void 0,
  2826. function(event) {
  2827. utils.preventEvent(event);
  2828. let selectText = _unsafeWindow.getSelection();
  2829. if (selectText) {
  2830. utils.setClip(selectText.toString());
  2831. } else {
  2832. log.error("未选中任何内容");
  2833. }
  2834. return false;
  2835. },
  2836. {
  2837. capture: true
  2838. }
  2839. );
  2840. },
  2841. /**
  2842. * 新标签页打开文章
  2843. */
  2844. openBlankArticle() {
  2845. log.success("新标签页打开文章");
  2846. domutils.on(
  2847. document,
  2848. "click",
  2849. ".feeds-container .note-item",
  2850. function(event) {
  2851. utils.preventEvent(event);
  2852. let $click = event.target;
  2853. let $url = $click.querySelector("a.cover[href]");
  2854. if ($url && $url.href) {
  2855. log.info("跳转文章: " + $url.href);
  2856. window.open($url.href, "_blank");
  2857. } else {
  2858. Qmsg.error("未找到文章链接");
  2859. }
  2860. },
  2861. {
  2862. capture: true
  2863. }
  2864. );
  2865. }
  2866. };
  2867. addStyle(
  2868. /*css*/
  2869. `
  2870. .qmsg svg.animate-turn {
  2871. fill: none;
  2872. }
  2873. `
  2874. );
  2875. PopsPanel.init();
  2876. let isMobile = utils.isPhone();
  2877. let CHANGE_ENV_SET_KEY = "change_env_set";
  2878. let chooseMode = _GM_getValue(CHANGE_ENV_SET_KEY);
  2879. GM_Menu.add({
  2880. key: CHANGE_ENV_SET_KEY,
  2881. text: `⚙ 自动: ${isMobile ? "移动端" : "PC端"}`,
  2882. autoReload: false,
  2883. isStoreValue: false,
  2884. showText(text) {
  2885. if (chooseMode == null) {
  2886. return text;
  2887. }
  2888. return text + ` 手动: ${chooseMode == 1 ? "移动端" : chooseMode == 2 ? "PC端" : "未知"}`;
  2889. },
  2890. callback: () => {
  2891. let allowValue = [0, 1, 2];
  2892. let chooseText = window.prompt(
  2893. "请输入当前脚本环境判定\n\n自动判断: 0\n移动端: 1\nPC端: 2",
  2894. "0"
  2895. );
  2896. if (!chooseText) {
  2897. return;
  2898. }
  2899. let chooseMode2 = parseInt(chooseText);
  2900. if (isNaN(chooseMode2)) {
  2901. Qmsg.error("输入的不是规范的数字");
  2902. return;
  2903. }
  2904. if (!allowValue.includes(chooseMode2)) {
  2905. Qmsg.error("输入的值必须是0或1或2");
  2906. return;
  2907. }
  2908. if (chooseMode2 == 0) {
  2909. _GM_deleteValue(CHANGE_ENV_SET_KEY);
  2910. } else {
  2911. _GM_setValue(CHANGE_ENV_SET_KEY, chooseMode2);
  2912. }
  2913. }
  2914. });
  2915. if (chooseMode != null) {
  2916. log.info(`手动判定为${chooseMode === 1 ? "移动端" : "PC端"}`);
  2917. if (chooseMode == 1) {
  2918. M_XHS.init();
  2919. } else if (chooseMode == 2) {
  2920. XHS.init();
  2921. } else {
  2922. Qmsg.error("意外,手动判定的值不在范围内");
  2923. _GM_deleteValue(CHANGE_ENV_SET_KEY);
  2924. }
  2925. } else {
  2926. if (isMobile) {
  2927. log.info("自动判定为移动端");
  2928. M_XHS.init();
  2929. } else {
  2930. log.info("自动判定为PC端");
  2931. XHS.init();
  2932. }
  2933. }
  2934.  
  2935. })(Qmsg, Utils, DOMUtils, pops, Viewer);