NGA Auto Pagerize

简单的自动翻页,以及更多附加功能,如:快捷翻页、抽楼检测、只看楼主、大召唤术

  1. // ==UserScript==
  2. // @name NGA Auto Pagerize
  3. // @namespace https://greasyfork.org/users/263018
  4. // @version 1.7.6
  5. // @author snyssss
  6. // @description 简单的自动翻页,以及更多附加功能,如:快捷翻页、抽楼检测、只看楼主、大召唤术
  7.  
  8. // @match *://bbs.nga.cn/*
  9. // @match *://ngabbs.com/*
  10. // @match *://nga.178.com/*
  11.  
  12. // @grant GM_addStyle
  13. // @grant GM_setValue
  14. // @grant GM_getValue
  15. // @grant GM_registerMenuCommand
  16. // @grant GM_setClipboard
  17.  
  18. // @noframes
  19. // ==/UserScript==
  20.  
  21. ((ui, n = {}, api = {}, uid) => {
  22. if (!ui) return;
  23.  
  24. // KEY
  25. const ATTACHMENT_STYLE_ENABLE_KEY = "ATTACHMENT_STYLE_ENABLE";
  26. const PAGE_BUTTON_STYLE_ENABLE_KEY = "PAGE_BUTTON_STYLE_ENABLE_KEY";
  27. const HOTKEYS_ENABLE_KEY = "HOTKEYS_ENABLE_KEY";
  28. const FORUM_NAME_ENABLE_KEY = "FORUM_NAME_ENABLE_KEY";
  29. const POST_LOSS_DETECTION_KEY = "POSTS_LOSS_DETECTION_KEY";
  30. const AUTHOR_ONLY_KEY = "AUTHOR_ONLY_KEY";
  31. const AUTO_CHECK_IN_ENABLE_KEY = "AUTO_CHECK_IN_ENABLE_KEY";
  32. const AUTO_CHECK_IN_LAST_TIME_KEY = "AUTO_CHECK_IN_LAST_TIME_KEY";
  33.  
  34. // 附件样式
  35. const attachmentStyleEnable =
  36. GM_getValue(ATTACHMENT_STYLE_ENABLE_KEY) || false;
  37.  
  38. // 页码样式
  39. const pageButtonStyleEnable =
  40. GM_getValue(PAGE_BUTTON_STYLE_ENABLE_KEY) || false;
  41.  
  42. // 快捷翻页
  43. const hotkeysEnable = GM_getValue(HOTKEYS_ENABLE_KEY) || false;
  44.  
  45. // 版面名称
  46. const forumNameEnable = GM_getValue(FORUM_NAME_ENABLE_KEY) || false;
  47.  
  48. // 抽楼检测
  49. const postLossDetectionEnable = GM_getValue(POST_LOSS_DETECTION_KEY) || false;
  50.  
  51. // 只看楼主
  52. const authorOnlyEnable = GM_getValue(AUTHOR_ONLY_KEY) || false;
  53.  
  54. // 自动签到
  55. const autoCheckInEnable = GM_getValue(AUTO_CHECK_IN_ENABLE_KEY) || false;
  56.  
  57. // 自动签到时间
  58. const autoCheckInLastTime = GM_getValue(AUTO_CHECK_IN_LAST_TIME_KEY) || 0;
  59.  
  60. // 自动签到 UA
  61. const autoCheckInUserAgent = "Nga_Official/80024(Android12)";
  62.  
  63. // STYLE
  64. GM_addStyle(`
  65. .s-table-wrapper {
  66. max-height: 80vh;
  67. overflow-y: auto;
  68. }
  69. .s-table {
  70. margin: 0;
  71. }
  72. .s-table th,
  73. .s-table td {
  74. position: relative;
  75. white-space: nowrap;
  76. }
  77. .s-table th {
  78. position: sticky;
  79. top: 2px;
  80. z-index: 1;
  81. }
  82. .s-table input:not([type]), .s-table input[type="text"] {
  83. margin: 0;
  84. box-sizing: border-box;
  85. height: 100%;
  86. width: 100%;
  87. }
  88. .s-input-wrapper {
  89. position: absolute;
  90. top: 6px;
  91. right: 6px;
  92. bottom: 6px;
  93. left: 6px;
  94. }
  95. .s-text-ellipsis {
  96. display: flex;
  97. }
  98. .s-text-ellipsis > * {
  99. flex: 1;
  100. width: 1px;
  101. overflow: hidden;
  102. text-overflow: ellipsis;
  103. }
  104. .s-button-group {
  105. margin: -.1em -.2em;
  106. }
  107. `);
  108.  
  109. // 防抖
  110. const debounce = (func, delay = 500) => {
  111. let id;
  112.  
  113. return (...args) => {
  114. clearTimeout(id);
  115.  
  116. id = setTimeout(() => func(...args), delay);
  117. };
  118. };
  119.  
  120. // 扩展菜单
  121. const enhanceMenu = (() => {
  122. // 大召唤术
  123. const summon = (() => {
  124. let window;
  125.  
  126. return () => {
  127. // 标识
  128. const key = "SUMMON";
  129.  
  130. // 标题
  131. const title = "大召唤术";
  132.  
  133. // 内容
  134. const content = (() => {
  135. const c = document.createElement("DIV");
  136.  
  137. c.innerHTML = `
  138. <div class="s-table-wrapper" style="width: 1000px; max-width: 95vw;">
  139. <table class="s-table forumbox">
  140. <thead>
  141. <tr class="block_txt_c0">
  142. <th class="c1" width="1">用户</th>
  143. <th class="c2">内容</th>
  144. <th class="c3" width="1">选择</th>
  145. </tr>
  146. </thead>
  147. <tbody></tbody>
  148. </table>
  149. </div>
  150. <div style="display: flex; margin-top: 10px;">
  151. <textarea readonly rows="3" style="flex: 1; overflow: auto;"></textarea>
  152. <div style="display: flex; flex-direction: column;">
  153. <button style="flex: 1;">复制</button>
  154. <button style="flex: 1;">全选/全不选</button>
  155. </div>
  156. </div>
  157. `;
  158.  
  159. return c;
  160. })();
  161.  
  162. // 选项改变事件
  163. const handleChange = (e) => {
  164. const items = content.querySelectorAll("INPUT");
  165.  
  166. if (e) {
  167. const { checked } = e.target;
  168.  
  169. const id = e.target.getAttribute("data-id");
  170.  
  171. items.forEach((item) => {
  172. if (item.getAttribute("data-id") === id) {
  173. item.checked = checked;
  174. }
  175. });
  176. }
  177.  
  178. const result = content.querySelector("TEXTAREA");
  179.  
  180. const resultItems = [...items]
  181. .filter((item) => item.checked)
  182. .map((item) => item.getAttribute("data-name"))
  183. .filter((name, index, self) => self.indexOf(name) === index);
  184.  
  185. result.value = resultItems.join("");
  186. };
  187.  
  188. // 刷新列表
  189. const refresh = (() => {
  190. const container = content.querySelector("TBODY");
  191.  
  192. const func = () => {
  193. container.innerHTML = "";
  194.  
  195. Object.values(ui.postArg.data).forEach((item) => {
  196. const { pAid, contentC, uInfoC } = item;
  197.  
  198. if (pAid < 0 || pAid === String(uid || 0)) {
  199. return;
  200. }
  201.  
  202. if (uInfoC === undefined) {
  203. return;
  204. }
  205.  
  206. const pName = (() => {
  207. const container =
  208. uInfoC.closest("TR").querySelector(".posterInfoLine") ||
  209. uInfoC;
  210.  
  211. return container.querySelector(".author").innerText;
  212. })();
  213.  
  214. const tc = document.createElement("TR");
  215.  
  216. tc.className = `row${
  217. (container.querySelectorAll("TR").length % 2) + 1
  218. }`;
  219.  
  220. tc.innerHTML = `
  221. <td class="c1">
  222. <a href="/nuke.php?func=ucp&uid=${pAid}" class="b nobr">
  223. [@${pName}]
  224. </a>
  225. </td>
  226. <td class="c2">
  227. <div class="filter-text-ellipsis">
  228. <span title="${contentC.innerText}">
  229. ${contentC.innerText}
  230. </span>
  231. </div>
  232. </td>
  233. <td class="c3">
  234. <input type="checkbox" data-id="${pAid}" data-name="[@${pName}]" />
  235. </td>
  236. `;
  237.  
  238. tc.querySelector("INPUT").addEventListener(
  239. "change",
  240. handleChange
  241. );
  242.  
  243. container.appendChild(tc);
  244. });
  245.  
  246. handleChange();
  247. };
  248.  
  249. // 绑定事件
  250. (() => {
  251. const textarea = content.querySelector("textarea");
  252.  
  253. textarea.addEventListener("click", () => {
  254. textarea.select();
  255. });
  256.  
  257. const clipboard = content.querySelector("button:first-child");
  258.  
  259. clipboard.addEventListener("click", () => {
  260. GM_setClipboard(textarea.value);
  261. });
  262.  
  263. const selectAll = content.querySelector("button:last-child");
  264.  
  265. selectAll.addEventListener("click", () => {
  266. const items = content.querySelectorAll("INPUT");
  267.  
  268. const checked = [...items].some((item) => !item.checked);
  269.  
  270. items.forEach((item) => {
  271. item.checked = checked;
  272. });
  273.  
  274. handleChange();
  275. });
  276. })();
  277.  
  278. return func;
  279. })();
  280.  
  281. // 生成菜单
  282. ui.postBtn.d[key] = {
  283. n2: title,
  284. n3: title,
  285. on: () => {
  286. if (window === undefined) {
  287. window = ui.createCommmonWindow();
  288. }
  289.  
  290. refresh();
  291.  
  292. window._.addContent(null);
  293. window._.addTitle(title);
  294. window._.addContent(content);
  295. window._.show();
  296. },
  297. ck: () => true,
  298. };
  299.  
  300. // 写入系统菜单
  301. if (ui.postBtn.all["扩展"].indexOf(key) < 0) {
  302. ui.postBtn.all["扩展"].push(key);
  303. }
  304. };
  305. })();
  306.  
  307. return () => {
  308. if (ui.postBtn) {
  309. ui.postBtn.all["扩展"] = ui.postBtn.all["扩展"] || [];
  310.  
  311. // 大召唤术
  312. summon();
  313. }
  314. };
  315. })();
  316.  
  317. // 加载脚本
  318. (() => {
  319. const hookFunction = (object, functionName, callback) => {
  320. ((originalFunction) => {
  321. object[functionName] = function () {
  322. const returnValue = originalFunction.apply(this, arguments);
  323.  
  324. callback.apply(this, [returnValue, originalFunction, arguments]);
  325.  
  326. return returnValue;
  327. };
  328. })(object[functionName]);
  329. };
  330.  
  331. const hooked = {
  332. autoPagerize: false,
  333. uniqueTopic: false,
  334. attachmentStyle: false,
  335. pageButtonStyle: false,
  336. hotkeys: false,
  337. forumName: false,
  338. postLossDetection: false,
  339. postLossDetectionTopic: false,
  340. authorOnly: false,
  341. };
  342.  
  343. const hook = () => {
  344. // 翻页
  345. const loadReadHidden = (() => {
  346. const THREAD_MAX_PAGE = 500;
  347.  
  348. // const delay = (interval) =>
  349. // new Promise((resolve) => setTimeout(resolve, interval));
  350.  
  351. // const retry = async (fn, retriesLeft = 10, interval = 160) => {
  352. // try {
  353. // return await fn();
  354. // } catch (error) {
  355. // await delay(interval);
  356.  
  357. // if (retriesLeft > 0) {
  358. // return await retry(fn, retriesLeft - 1, interval);
  359. // }
  360. // }
  361. // };
  362.  
  363. return (p, opt = 1) => {
  364. // if (ui.loadReadHidden) {
  365. // retry(() => {
  366. // if (ui.loadReadHidden.lock) {
  367. // throw new Error();
  368. // }
  369.  
  370. if (__PAGE) {
  371. const max = __PAGE[1];
  372. const cur = __PAGE[2];
  373.  
  374. if (location.pathname === "/thread.php") {
  375. if (p > THREAD_MAX_PAGE) {
  376. return;
  377. }
  378.  
  379. if (p === 0 && opt === 2 && cur === THREAD_MAX_PAGE) {
  380. return;
  381. }
  382. }
  383.  
  384. if (p < 1 && opt === 1) {
  385. return;
  386. }
  387.  
  388. if (p > max && max > 0) {
  389. p = max;
  390. }
  391.  
  392. if (p === cur) {
  393. return;
  394. }
  395.  
  396. // ui.loadReadHidden(p, opt);
  397.  
  398. // 临时的处理
  399. const anchor = (() => {
  400. if (opt === 2) {
  401. return document.querySelector('[title="加载下一页"]');
  402. }
  403.  
  404. if (opt === 4) {
  405. return document.querySelector('[title="加载上一页"]');
  406. }
  407.  
  408. // 获取当前页面参数
  409. const params = new URLSearchParams(location.search);
  410.  
  411. // 替换 page 参数
  412. if (p > 1) {
  413. params.set("page", p);
  414. } else {
  415. params.delete("page");
  416. }
  417.  
  418. // 计算目标 URL
  419. const target = location.pathname + "?" + params.toString();
  420.  
  421. // 返回目标 URL
  422. return document.querySelector(`A[href="${target}"]`);
  423. })();
  424.  
  425. if (anchor) {
  426. anchor.click();
  427. }
  428. }
  429. // });
  430. // }
  431. };
  432. })();
  433.  
  434. // 自动翻页
  435. if (hooked.autoPagerize === false) {
  436. if (ui.pageBtn) {
  437. const execute = (() => {
  438. const observer = new IntersectionObserver((entries) => {
  439. if (entries.find((item) => item.isIntersecting)) {
  440. if (
  441. location.pathname === "/thread.php" &&
  442. location.search.indexOf("authorid") > 0
  443. ) {
  444. return;
  445. }
  446.  
  447. loadReadHidden(0, 2);
  448. }
  449. });
  450.  
  451. return debounce(() => {
  452. const anchor = document.querySelector('[title="加载下一页"]');
  453.  
  454. if (anchor) {
  455. observer.observe(anchor);
  456. } else {
  457. observer.disconnect();
  458. }
  459. }, 2000);
  460. })();
  461.  
  462. hookFunction(ui, "pageBtn", execute);
  463.  
  464. hooked.autoPagerize = true;
  465.  
  466. execute();
  467. }
  468. }
  469.  
  470. // 移除重复内容
  471. if (hooked.uniqueTopic === false) {
  472. if (ui.topicArg) {
  473. const execute = () => {
  474. if (location.search.indexOf("searchpost=1") > 0) {
  475. return;
  476. }
  477.  
  478. ui.topicArg.data = ui.topicArg.data.reduce(
  479. (accumulator, currentValue) => {
  480. if (document.contains(currentValue[0])) {
  481. const index = accumulator.findIndex(
  482. (item) => item[8] === currentValue[8]
  483. );
  484.  
  485. if (index < 0) {
  486. return [...accumulator, currentValue];
  487. }
  488.  
  489. currentValue[0].closest("TBODY").style.display = "none";
  490. }
  491.  
  492. return accumulator;
  493. },
  494. []
  495. );
  496. };
  497.  
  498. hookFunction(ui.topicArg, "loadAll", execute);
  499.  
  500. hooked.uniqueTopic = true;
  501.  
  502. execute();
  503. }
  504. }
  505.  
  506. // 附件样式
  507. if (hooked.attachmentStyle === false && attachmentStyleEnable) {
  508. if (ui.topicArg) {
  509. const execute = () => {
  510. const elements =
  511. document.querySelectorAll('[title="主题中有附件"]');
  512.  
  513. elements.forEach((element) => {
  514. element.className = "block_txt white nobr vertmod";
  515. element.style = "background-color: #BD7E6D";
  516. element.innerHTML = "附件";
  517. });
  518. };
  519.  
  520. hookFunction(ui.topicArg, "loadAll", execute);
  521.  
  522. hooked.attachmentStyle = true;
  523.  
  524. execute();
  525. }
  526. }
  527.  
  528. // 页码样式
  529. if (hooked.pageButtonStyle === false && pageButtonStyleEnable) {
  530. const execute = () => {
  531. if (ui.pageBtn) {
  532. const elements = document.querySelectorAll('[name="pageball"] A');
  533.  
  534. elements.forEach((element) => {
  535. const matches = element.innerHTML.match(/\d+/);
  536.  
  537. if (matches) {
  538. element.innerHTML = `&nbsp;${matches[0]}&nbsp;`;
  539. }
  540. });
  541. }
  542. };
  543.  
  544. hookFunction(ui, "pageBtn", execute);
  545.  
  546. hooked.pageButtonStyle = true;
  547.  
  548. execute();
  549. }
  550.  
  551. // 快捷翻页
  552. if (hooked.hotkeys === false && hotkeysEnable) {
  553. const execute = () => {
  554. document.addEventListener("keydown", ({ key, ctrlKey }) => {
  555. if (__PAGE) {
  556. const max = __PAGE[1];
  557. // const cur = __PAGE[2];
  558.  
  559. const activeElement = document.activeElement;
  560.  
  561. if (activeElement === null || activeElement.tagName !== "BODY") {
  562. return;
  563. }
  564.  
  565. if (key === "ArrowLeft" && ctrlKey) {
  566. loadReadHidden(1);
  567. return;
  568. }
  569.  
  570. if (key === "ArrowRight" && ctrlKey) {
  571. loadReadHidden(max);
  572. return;
  573. }
  574.  
  575. if (key === "ArrowLeft") {
  576. // document.getElementById("m_pbtntop").scrollIntoView();
  577.  
  578. loadReadHidden(0, 4);
  579. return;
  580. }
  581.  
  582. if (key === "ArrowRight") {
  583. // document.getElementById("m_pbtnbtm").scrollIntoView();
  584.  
  585. loadReadHidden(0, 2);
  586. return;
  587. }
  588. }
  589. });
  590. };
  591.  
  592. hooked.hotkeys = true;
  593.  
  594. execute();
  595. }
  596.  
  597. // 版面名称
  598. if (hooked.forumName === false && forumNameEnable) {
  599. if (ui.topicArg) {
  600. if (!n.doRequest || !api.indexForumList) {
  601. return;
  602. }
  603.  
  604. class Queue {
  605. execute(task) {
  606. task(this.data).finally(() => {
  607. if (this.waitingQueue.length) {
  608. const next = this.waitingQueue.shift();
  609.  
  610. this.execute(next);
  611. } else {
  612. this.isRunning = false;
  613. }
  614. });
  615. }
  616.  
  617. enqueue(task) {
  618. if (this.initialized === false) {
  619. this.initialized = true;
  620. this.init();
  621. }
  622.  
  623. if (this.isRunning) {
  624. this.waitingQueue.push(task);
  625. } else {
  626. this.isRunning = true;
  627.  
  628. this.execute(task);
  629. }
  630. }
  631.  
  632. init() {
  633. this.enqueue(async () => {
  634. this.data = await new Promise((resolve) => {
  635. try {
  636. n.doRequest({
  637. u: api.indexForumList(),
  638. f: function (res) {
  639. if (res.data) {
  640. resolve(res.data[0]);
  641. } else {
  642. resolve({});
  643. }
  644. },
  645. });
  646. } catch (e) {
  647. resolve({});
  648. }
  649. });
  650. });
  651. }
  652.  
  653. constructor() {
  654. this.waitingQueue = [];
  655. this.isRunning = false;
  656.  
  657. this.initialized = false;
  658. }
  659. }
  660.  
  661. const deepSearch = (content = {}, fid = 0) => {
  662. const children = Object.values(content);
  663.  
  664. for (let i = 0; i < children.length; i += 1) {
  665. const item = children[i];
  666.  
  667. if (item.fid === fid) {
  668. return item;
  669. }
  670.  
  671. if (item.content) {
  672. const result = deepSearch(item.content || [], fid);
  673.  
  674. if (result !== null) {
  675. return result;
  676. }
  677. }
  678. }
  679.  
  680. return null;
  681. };
  682.  
  683. const queue = new Queue();
  684.  
  685. const execute = () => {
  686. if (location.search.indexOf("authorid") < 0) {
  687. return;
  688. }
  689.  
  690. ui.topicArg.data.forEach((item) => {
  691. const parentNode = item[1].closest(".c2");
  692.  
  693. if (parentNode.querySelector(".titleadd2") === null) {
  694. const fid = item[7];
  695.  
  696. queue.enqueue(async (data) => {
  697. const result = deepSearch(data.all, parseInt(fid, 10));
  698.  
  699. if (result) {
  700. const anchor = parentNode.querySelector(".topic_content");
  701.  
  702. const title = document.createElement("SPAN");
  703.  
  704. title.className = "titleadd2";
  705. title.innerHTML = `<a href="/thread.php?fid=${fid}" class="silver">[${result.name}]</a>`;
  706.  
  707. if (anchor) {
  708. anchor.before(title);
  709. } else {
  710. parentNode.append(title);
  711. }
  712. }
  713. });
  714. }
  715. });
  716. };
  717.  
  718. hookFunction(ui.topicArg, "loadAll", execute);
  719.  
  720. hooked.forumName = true;
  721.  
  722. execute();
  723. }
  724. }
  725.  
  726. // 抽楼检测
  727. if (postLossDetectionEnable) {
  728. const cache = {};
  729. const action = "&action=modify";
  730.  
  731. const fetchData = async (key, tid, pid, extraArg = "") => {
  732. if (cache[key] === undefined) {
  733. const url = `/post.php?lite=js&tid=${tid}&pid=${pid}${extraArg}`;
  734.  
  735. cache[key] = await new Promise((resolve) => {
  736. fetch(url)
  737. .then((res) => res.blob())
  738. .then((blob) => {
  739. const reader = new FileReader();
  740.  
  741. reader.onload = async () => {
  742. const text = reader.result;
  743. const result = JSON.parse(
  744. text.replace("window.script_muti_get_var_store=", "")
  745. );
  746.  
  747. const { data, error } = result;
  748.  
  749. if (error) {
  750. resolve(error[0]);
  751. return;
  752. }
  753.  
  754. if (data && data["post_type"] & 2) {
  755. resolve("只有作者/版主可见");
  756. return;
  757. }
  758.  
  759. if (url.indexOf(action) < 0) {
  760. resolve(await fetchData(key, tid, pid, action));
  761. return;
  762. }
  763.  
  764. resolve("");
  765. };
  766.  
  767. reader.readAsText(blob, "GBK");
  768. })
  769. .catch(() => {
  770. resolve("");
  771. });
  772. });
  773. }
  774.  
  775. return cache[key];
  776. };
  777.  
  778. if (hooked.postLossDetection === false) {
  779. if (ui.postArg && uid) {
  780. const execute = debounce(() => {
  781. if (ui.postArg === undefined) {
  782. return;
  783. }
  784.  
  785. Object.values(ui.postArg.data).forEach(
  786. async ({ tid, pid, pAid, pInfoC }) => {
  787. if (parseInt(pAid, 10) !== uid) {
  788. return;
  789. }
  790.  
  791. const key = `${tid}#${pid}`;
  792.  
  793. const error = await fetchData(key, tid, pid);
  794.  
  795. if (error) {
  796. if (pInfoC) {
  797. if (pInfoC.querySelector(`[id="${key}"]`)) {
  798. return;
  799. }
  800.  
  801. const node = document.createElement("SPAN");
  802.  
  803. node.id = key;
  804. node.className =
  805. "small_colored_text_btn block_txt_c0 stxt";
  806. node.style = "margin-left: 0.4em; line-height: inherit;";
  807. node.innerHTML = error;
  808.  
  809. pInfoC.prepend(node);
  810. }
  811. }
  812. }
  813. );
  814. }, 2000);
  815.  
  816. hookFunction(ui, "loadReadHidden", execute);
  817.  
  818. hooked.postLossDetection = true;
  819.  
  820. execute();
  821. }
  822. }
  823.  
  824. if (hooked.postLossDetectionTopic === false) {
  825. if (ui.topicArg && uid) {
  826. const execute = debounce(() => {
  827. Object.values(ui.topicArg.data).forEach(async (item) => {
  828. const tid = item[8];
  829. const pid = item[9] || 0;
  830.  
  831. const author = item[2];
  832. const authorID =
  833. parseInt(
  834. author.getAttribute("href").match(/uid=(\S+)/)[1],
  835. 10
  836. ) || 0;
  837.  
  838. const postDate = item[12];
  839.  
  840. if (authorID !== uid) {
  841. return;
  842. }
  843.  
  844. if (tid && postDate) {
  845. const key = `${tid}#${pid}`;
  846.  
  847. const error = await fetchData(key, tid, pid);
  848.  
  849. if (error) {
  850. const node = document.createElement("SPAN");
  851.  
  852. node.id = key;
  853. node.className = "small_colored_text_btn block_txt_c0";
  854. node.innerHTML = error;
  855.  
  856. const anchor = author.parentNode;
  857.  
  858. anchor.innerHTML = "";
  859. anchor.appendChild(node);
  860. }
  861. }
  862. });
  863. }, 2000);
  864.  
  865. hookFunction(ui.topicArg, "loadAll", execute);
  866.  
  867. hooked.postLossDetectionTopic = true;
  868.  
  869. execute();
  870. }
  871. }
  872. }
  873.  
  874. // 只看楼主
  875. if (hooked.authorOnly === false && authorOnlyEnable) {
  876. if (ui.topicBtn) {
  877. const key = 99;
  878. const execute = () => {
  879. if (ui.topicBtn.d[key]) {
  880. return;
  881. }
  882.  
  883. const anchor = document.querySelector("#postbtop");
  884.  
  885. if (anchor) {
  886. ui.topicBtn.d[key] = {
  887. n1: "楼主",
  888. n2: "只看楼主",
  889. on: (_, { tid }) => {
  890. const api = `/read.php?tid=${tid}`;
  891.  
  892. const params = new URLSearchParams(location.search);
  893.  
  894. // 如果已经是匿名的只看楼主状态,则直接跳转原始页面
  895. if (params.get("opt")) {
  896. location.href = api;
  897. return;
  898. }
  899.  
  900. // 请求获取顶楼 UID
  901. fetch(api)
  902. .then((res) => res.blob())
  903. .then((blob) => {
  904. const getLastIndex = (content, position) => {
  905. if (position >= 0) {
  906. let nextIndex = position + 1;
  907.  
  908. while (nextIndex < content.length) {
  909. if (content[nextIndex] === "}") {
  910. return nextIndex;
  911. }
  912.  
  913. if (content[nextIndex] === "{") {
  914. nextIndex = getLastIndex(content, nextIndex);
  915.  
  916. if (nextIndex < 0) {
  917. break;
  918. }
  919. }
  920.  
  921. nextIndex = nextIndex + 1;
  922. }
  923. }
  924.  
  925. return -1;
  926. };
  927.  
  928. const reader = new FileReader();
  929.  
  930. reader.onload = async () => {
  931. const parser = new DOMParser();
  932.  
  933. const doc = parser.parseFromString(
  934. reader.result,
  935. "text/html"
  936. );
  937.  
  938. // 验证帖子正常
  939. const verify = doc.querySelector("#m_posts");
  940.  
  941. if (verify) {
  942. // 取得顶楼 UID
  943. const uid = (() => {
  944. const ele = doc.querySelector("#postauthor0");
  945.  
  946. if (ele) {
  947. const res = ele
  948. .getAttribute("href")
  949. .match(/uid=(\S+)/);
  950.  
  951. if (res) {
  952. return res[1];
  953. }
  954. }
  955.  
  956. return 0;
  957. })();
  958.  
  959. // 匿名贴
  960. if (uid <= 0) {
  961. location.href = `${api}&opt=512`;
  962. return;
  963. }
  964.  
  965. // 判断 UID 是否一致
  966. if (uid !== params.get("authorid")) {
  967. location.href = `${api}&authorid=${uid}`;
  968. return;
  969. }
  970.  
  971. // 跳转原始页面
  972. location.href = api;
  973. }
  974. };
  975.  
  976. reader.readAsText(blob, "GBK");
  977. });
  978. },
  979. };
  980.  
  981. ui.topicBtn.def.push(key);
  982.  
  983. ui.topicBtn.load(anchor);
  984. }
  985. };
  986.  
  987. hookFunction(ui.topicBtn, "load", execute);
  988.  
  989. hooked.authorOnly = true;
  990.  
  991. execute();
  992. }
  993. }
  994. };
  995.  
  996. hookFunction(ui, "eval", () => {
  997. enhanceMenu();
  998.  
  999. if (Object.values(hooked).findIndex((item) => item === false) < 0) {
  1000. return;
  1001. }
  1002.  
  1003. hook();
  1004. });
  1005.  
  1006. hook();
  1007.  
  1008. enhanceMenu();
  1009. })();
  1010.  
  1011. // 加载菜单项
  1012. (() => {
  1013. if (attachmentStyleEnable) {
  1014. GM_registerMenuCommand("附件样式:启用", () => {
  1015. GM_setValue(ATTACHMENT_STYLE_ENABLE_KEY, false);
  1016. location.reload();
  1017. });
  1018. } else {
  1019. GM_registerMenuCommand("附件样式:禁用", () => {
  1020. GM_setValue(ATTACHMENT_STYLE_ENABLE_KEY, true);
  1021. location.reload();
  1022. });
  1023. }
  1024.  
  1025. if (pageButtonStyleEnable) {
  1026. GM_registerMenuCommand("页码样式:启用", () => {
  1027. GM_setValue(PAGE_BUTTON_STYLE_ENABLE_KEY, false);
  1028. location.reload();
  1029. });
  1030. } else {
  1031. GM_registerMenuCommand("页码样式:禁用", () => {
  1032. GM_setValue(PAGE_BUTTON_STYLE_ENABLE_KEY, true);
  1033. location.reload();
  1034. });
  1035. }
  1036.  
  1037. if (hotkeysEnable) {
  1038. GM_registerMenuCommand("快捷翻页:启用", () => {
  1039. GM_setValue(HOTKEYS_ENABLE_KEY, false);
  1040. location.reload();
  1041. });
  1042. } else {
  1043. GM_registerMenuCommand("快捷翻页:禁用", () => {
  1044. GM_setValue(HOTKEYS_ENABLE_KEY, true);
  1045. location.reload();
  1046. });
  1047. }
  1048.  
  1049. if (forumNameEnable) {
  1050. GM_registerMenuCommand("版面名称:启用", () => {
  1051. GM_setValue(FORUM_NAME_ENABLE_KEY, false);
  1052. location.reload();
  1053. });
  1054. } else {
  1055. GM_registerMenuCommand("版面名称:禁用", () => {
  1056. GM_setValue(FORUM_NAME_ENABLE_KEY, true);
  1057. location.reload();
  1058. });
  1059. }
  1060.  
  1061. if (postLossDetectionEnable) {
  1062. GM_registerMenuCommand("抽楼检测:启用", () => {
  1063. GM_setValue(POST_LOSS_DETECTION_KEY, false);
  1064. location.reload();
  1065. });
  1066. } else {
  1067. GM_registerMenuCommand("抽楼检测:禁用", () => {
  1068. GM_setValue(POST_LOSS_DETECTION_KEY, true);
  1069. location.reload();
  1070. });
  1071. }
  1072.  
  1073. if (authorOnlyEnable) {
  1074. GM_registerMenuCommand("只看楼主:启用", () => {
  1075. GM_setValue(AUTHOR_ONLY_KEY, false);
  1076. location.reload();
  1077. });
  1078. } else {
  1079. GM_registerMenuCommand("只看楼主:禁用", () => {
  1080. GM_setValue(AUTHOR_ONLY_KEY, true);
  1081. location.reload();
  1082. });
  1083. }
  1084.  
  1085. if (autoCheckInEnable) {
  1086. GM_registerMenuCommand("自动签到:启用", () => {
  1087. GM_setValue(AUTO_CHECK_IN_ENABLE_KEY, false);
  1088. GM_setValue(AUTO_CHECK_IN_LAST_TIME_KEY, 0);
  1089. location.reload();
  1090. });
  1091. } else {
  1092. GM_registerMenuCommand("自动签到:禁用", () => {
  1093. GM_setValue(AUTO_CHECK_IN_ENABLE_KEY, true);
  1094. location.reload();
  1095. });
  1096. }
  1097. })();
  1098.  
  1099. // 自动签到
  1100. if (autoCheckInEnable && uid) {
  1101. const today = new Date();
  1102.  
  1103. const lastTime = new Date(autoCheckInLastTime);
  1104.  
  1105. const isToday =
  1106. lastTime.getDate() === today.getDate() &&
  1107. lastTime.getMonth() === today.getMonth() &&
  1108. lastTime.getFullYear() === today.getFullYear();
  1109.  
  1110. if (isToday === false) {
  1111. fetch(`/nuke.php?__lib=check_in&__act=check_in&lite=js`, {
  1112. method: "POST",
  1113. headers: {
  1114. "X-User-Agent": autoCheckInUserAgent,
  1115. },
  1116. })
  1117. .then((res) => res.blob())
  1118. .then((blob) => {
  1119. const reader = new FileReader();
  1120.  
  1121. reader.onload = () => {
  1122. const text = reader.result;
  1123. const result = JSON.parse(
  1124. text.replace("window.script_muti_get_var_store=", "")
  1125. );
  1126.  
  1127. const { data, error } = result;
  1128.  
  1129. if (data || error) {
  1130. alert((data || error)[0]);
  1131. }
  1132.  
  1133. GM_setValue(AUTO_CHECK_IN_LAST_TIME_KEY, today.getTime());
  1134. };
  1135.  
  1136. reader.readAsText(blob, "GBK");
  1137. });
  1138. }
  1139. }
  1140. })(commonui, __NUKE, __API, __CURRENT_UID);