NGA Auto Pagerize

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

  1. // ==UserScript==
  2. // @name NGA Auto Pagerize
  3. // @namespace https://greasyfork.org/users/263018
  4. // @version 1.7.4
  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.  
  730. const fetchData = async (key, tid, pid) => {
  731. if (cache[key] === undefined) {
  732. cache[key] = await new Promise((resolve) => {
  733. fetch(`/post.php?lite=js&tid=${tid}&pid=${pid}`)
  734. .then((res) => res.blob())
  735. .then((blob) => {
  736. const reader = new FileReader();
  737.  
  738. reader.onload = () => {
  739. const text = reader.result;
  740. const result = JSON.parse(
  741. text.replace("window.script_muti_get_var_store=", "")
  742. );
  743.  
  744. const { error } = result;
  745.  
  746. if (error) {
  747. resolve(error[0]);
  748. } else {
  749. resolve("");
  750. }
  751. };
  752.  
  753. reader.readAsText(blob, "GBK");
  754. })
  755. .catch(() => {
  756. resolve("");
  757. });
  758. });
  759. }
  760.  
  761. return cache[key];
  762. };
  763.  
  764. if (hooked.postLossDetection === false) {
  765. if (ui.postArg && uid) {
  766. const execute = debounce(() => {
  767. if (ui.postArg === undefined) {
  768. return;
  769. }
  770.  
  771. Object.values(ui.postArg.data).forEach(
  772. async ({ tid, pid, pAid, pInfoC }) => {
  773. if (parseInt(pAid, 10) !== uid) {
  774. return;
  775. }
  776.  
  777. const key = `${tid}#${pid}`;
  778.  
  779. const error = await fetchData(key, tid, pid);
  780.  
  781. if (error) {
  782. if (pInfoC) {
  783. if (pInfoC.querySelector(`[id="${key}"]`)) {
  784. return;
  785. }
  786.  
  787. const node = document.createElement("SPAN");
  788.  
  789. node.id = key;
  790. node.className =
  791. "small_colored_text_btn block_txt_c0 stxt";
  792. node.style = "margin-left: 0.4em; line-height: inherit;";
  793. node.innerHTML = error;
  794.  
  795. pInfoC.prepend(node);
  796. }
  797. }
  798. }
  799. );
  800. }, 2000);
  801.  
  802. hookFunction(ui, "loadReadHidden", execute);
  803.  
  804. hooked.postLossDetection = true;
  805.  
  806. execute();
  807. }
  808. }
  809.  
  810. if (hooked.postLossDetectionTopic === false) {
  811. if (ui.topicArg && uid) {
  812. const execute = debounce(() => {
  813. Object.values(ui.topicArg.data).forEach(async (item) => {
  814. const tid = item[8];
  815. const pid = item[9] || 0;
  816.  
  817. const author = item[2];
  818. const authorID =
  819. parseInt(
  820. author.getAttribute("href").match(/uid=(\S+)/)[1],
  821. 10
  822. ) || 0;
  823.  
  824. const postDate = item[12];
  825.  
  826. if (authorID !== uid) {
  827. return;
  828. }
  829.  
  830. if (tid && postDate) {
  831. const key = `${tid}#${pid}`;
  832.  
  833. const error = await fetchData(key, tid, pid);
  834.  
  835. if (error) {
  836. const node = document.createElement("SPAN");
  837.  
  838. node.id = key;
  839. node.className = "small_colored_text_btn block_txt_c0";
  840. node.innerHTML = error;
  841.  
  842. const anchor = author.parentNode;
  843.  
  844. anchor.innerHTML = "";
  845. anchor.appendChild(node);
  846. }
  847. }
  848. });
  849. }, 2000);
  850.  
  851. hookFunction(ui.topicArg, "loadAll", execute);
  852.  
  853. hooked.postLossDetectionTopic = true;
  854.  
  855. execute();
  856. }
  857. }
  858. }
  859.  
  860. // 只看楼主
  861. if (hooked.authorOnly === false && authorOnlyEnable) {
  862. if (ui.topicBtn) {
  863. const key = 99;
  864. const execute = () => {
  865. if (ui.topicBtn.d[key]) {
  866. return;
  867. }
  868.  
  869. const anchor = document.querySelector("#postbtop");
  870.  
  871. if (anchor) {
  872. ui.topicBtn.d[key] = {
  873. n1: "楼主",
  874. n2: "只看楼主",
  875. on: (_, { tid }) => {
  876. const api = `/read.php?tid=${tid}`;
  877.  
  878. const params = new URLSearchParams(location.search);
  879.  
  880. // 如果已经是匿名的只看楼主状态,则直接跳转原始页面
  881. if (params.get("opt")) {
  882. location.href = api;
  883. return;
  884. }
  885.  
  886. // 请求获取顶楼 UID
  887. fetch(api)
  888. .then((res) => res.blob())
  889. .then((blob) => {
  890. const getLastIndex = (content, position) => {
  891. if (position >= 0) {
  892. let nextIndex = position + 1;
  893.  
  894. while (nextIndex < content.length) {
  895. if (content[nextIndex] === "}") {
  896. return nextIndex;
  897. }
  898.  
  899. if (content[nextIndex] === "{") {
  900. nextIndex = getLastIndex(content, nextIndex);
  901.  
  902. if (nextIndex < 0) {
  903. break;
  904. }
  905. }
  906.  
  907. nextIndex = nextIndex + 1;
  908. }
  909. }
  910.  
  911. return -1;
  912. };
  913.  
  914. const reader = new FileReader();
  915.  
  916. reader.onload = async () => {
  917. const parser = new DOMParser();
  918.  
  919. const doc = parser.parseFromString(
  920. reader.result,
  921. "text/html"
  922. );
  923.  
  924. // 验证帖子正常
  925. const verify = doc.querySelector("#m_posts");
  926.  
  927. if (verify) {
  928. // 取得顶楼 UID
  929. const uid = (() => {
  930. const ele = doc.querySelector("#postauthor0");
  931.  
  932. if (ele) {
  933. const res = ele
  934. .getAttribute("href")
  935. .match(/uid=(\S+)/);
  936.  
  937. if (res) {
  938. return res[1];
  939. }
  940. }
  941.  
  942. return 0;
  943. })();
  944.  
  945. // 匿名贴
  946. if (uid <= 0) {
  947. location.href = `${api}&opt=512`;
  948. return;
  949. }
  950.  
  951. // 判断 UID 是否一致
  952. if (uid !== params.get("authorid")) {
  953. location.href = `${api}&authorid=${uid}`;
  954. return;
  955. }
  956.  
  957. // 跳转原始页面
  958. location.href = api;
  959. }
  960. };
  961.  
  962. reader.readAsText(blob, "GBK");
  963. });
  964. },
  965. };
  966.  
  967. ui.topicBtn.def.push(key);
  968.  
  969. ui.topicBtn.load(anchor);
  970. }
  971. };
  972.  
  973. hookFunction(ui.topicBtn, "load", execute);
  974.  
  975. hooked.authorOnly = true;
  976.  
  977. execute();
  978. }
  979. }
  980. };
  981.  
  982. hookFunction(ui, "eval", () => {
  983. enhanceMenu();
  984.  
  985. if (Object.values(hooked).findIndex((item) => item === false) < 0) {
  986. return;
  987. }
  988.  
  989. hook();
  990. });
  991.  
  992. hook();
  993.  
  994. enhanceMenu();
  995. })();
  996.  
  997. // 加载菜单项
  998. (() => {
  999. if (attachmentStyleEnable) {
  1000. GM_registerMenuCommand("附件样式:启用", () => {
  1001. GM_setValue(ATTACHMENT_STYLE_ENABLE_KEY, false);
  1002. location.reload();
  1003. });
  1004. } else {
  1005. GM_registerMenuCommand("附件样式:禁用", () => {
  1006. GM_setValue(ATTACHMENT_STYLE_ENABLE_KEY, true);
  1007. location.reload();
  1008. });
  1009. }
  1010.  
  1011. if (pageButtonStyleEnable) {
  1012. GM_registerMenuCommand("页码样式:启用", () => {
  1013. GM_setValue(PAGE_BUTTON_STYLE_ENABLE_KEY, false);
  1014. location.reload();
  1015. });
  1016. } else {
  1017. GM_registerMenuCommand("页码样式:禁用", () => {
  1018. GM_setValue(PAGE_BUTTON_STYLE_ENABLE_KEY, true);
  1019. location.reload();
  1020. });
  1021. }
  1022.  
  1023. if (hotkeysEnable) {
  1024. GM_registerMenuCommand("快捷翻页:启用", () => {
  1025. GM_setValue(HOTKEYS_ENABLE_KEY, false);
  1026. location.reload();
  1027. });
  1028. } else {
  1029. GM_registerMenuCommand("快捷翻页:禁用", () => {
  1030. GM_setValue(HOTKEYS_ENABLE_KEY, true);
  1031. location.reload();
  1032. });
  1033. }
  1034.  
  1035. if (forumNameEnable) {
  1036. GM_registerMenuCommand("版面名称:启用", () => {
  1037. GM_setValue(FORUM_NAME_ENABLE_KEY, false);
  1038. location.reload();
  1039. });
  1040. } else {
  1041. GM_registerMenuCommand("版面名称:禁用", () => {
  1042. GM_setValue(FORUM_NAME_ENABLE_KEY, true);
  1043. location.reload();
  1044. });
  1045. }
  1046.  
  1047. if (postLossDetectionEnable) {
  1048. GM_registerMenuCommand("抽楼检测:启用", () => {
  1049. GM_setValue(POST_LOSS_DETECTION_KEY, false);
  1050. location.reload();
  1051. });
  1052. } else {
  1053. GM_registerMenuCommand("抽楼检测:禁用", () => {
  1054. GM_setValue(POST_LOSS_DETECTION_KEY, true);
  1055. location.reload();
  1056. });
  1057. }
  1058.  
  1059. if (authorOnlyEnable) {
  1060. GM_registerMenuCommand("只看楼主:启用", () => {
  1061. GM_setValue(AUTHOR_ONLY_KEY, false);
  1062. location.reload();
  1063. });
  1064. } else {
  1065. GM_registerMenuCommand("只看楼主:禁用", () => {
  1066. GM_setValue(AUTHOR_ONLY_KEY, true);
  1067. location.reload();
  1068. });
  1069. }
  1070.  
  1071. if (autoCheckInEnable) {
  1072. GM_registerMenuCommand("自动签到:启用", () => {
  1073. GM_setValue(AUTO_CHECK_IN_ENABLE_KEY, false);
  1074. GM_setValue(AUTO_CHECK_IN_LAST_TIME_KEY, 0);
  1075. location.reload();
  1076. });
  1077. } else {
  1078. GM_registerMenuCommand("自动签到:禁用", () => {
  1079. GM_setValue(AUTO_CHECK_IN_ENABLE_KEY, true);
  1080. location.reload();
  1081. });
  1082. }
  1083. })();
  1084.  
  1085. // 自动签到
  1086. if (autoCheckInEnable && uid) {
  1087. const today = new Date();
  1088.  
  1089. const lastTime = new Date(autoCheckInLastTime);
  1090.  
  1091. const isToday =
  1092. lastTime.getDate() === today.getDate() &&
  1093. lastTime.getMonth() === today.getMonth() &&
  1094. lastTime.getFullYear() === today.getFullYear();
  1095.  
  1096. if (isToday === false) {
  1097. fetch(`/nuke.php?__lib=check_in&__act=check_in&lite=js`, {
  1098. method: "POST",
  1099. headers: {
  1100. "X-User-Agent": autoCheckInUserAgent,
  1101. },
  1102. })
  1103. .then((res) => res.blob())
  1104. .then((blob) => {
  1105. const reader = new FileReader();
  1106.  
  1107. reader.onload = () => {
  1108. const text = reader.result;
  1109. const result = JSON.parse(
  1110. text.replace("window.script_muti_get_var_store=", "")
  1111. );
  1112.  
  1113. const { data, error } = result;
  1114.  
  1115. if (data || error) {
  1116. alert((data || error)[0]);
  1117. }
  1118.  
  1119. GM_setValue(AUTO_CHECK_IN_LAST_TIME_KEY, today.getTime());
  1120. };
  1121.  
  1122. reader.readAsText(blob, "GBK");
  1123. });
  1124. }
  1125. }
  1126. })(commonui, __NUKE, __API, __CURRENT_UID);