NGA Auto Refresh

Refresh NGA post automatically.

  1. // ==UserScript==
  2. // @name NGA Auto Refresh
  3. // @version 0.2.0
  4. // @description Refresh NGA post automatically.
  5. // @license MIT
  6. // @author eight04 <eight04@gmail.com> (https://github.com/eight04)
  7. // @homepageURL https://github.com/eight04/nga-auto-refresh
  8. // @supportURL https://github.com/eight04/nga-auto-refresh/issues
  9. // @namespace https://github.com/eight04
  10. // @match *://bbs.nga.cn/read.php?*
  11. // @match *://bbs.nga.cn/thread.php?*
  12. // @grant none
  13. // ==/UserScript==
  14.  
  15. /* eslint-env browser */
  16. (async () => {
  17. // https://github.com/Tampermonkey/tampermonkey/issues/705
  18. const setTimeout = window.setTimeout.bind(window);
  19. const fetch = window.fetch.bind(window);
  20. const updater = createUpdater({
  21. interval: 60 * 1000
  22. });
  23. updater.start();
  24. new MutationObserver(updater.maybeRestart).observe(document.querySelector("#mc"), {childList: true});
  25. function createUpdater({interval}) {
  26. let status = "COMPLETE";
  27. let lastUpdate = Date.now();
  28. let checkTimer;
  29. let abortController;
  30. const el = document.createElement("div");
  31. el.className = "nga-auto-refresh-status";
  32. el.style.margin = "10px";
  33. el.style.textAlign = "center";
  34. // FIXME: only activate updateCounter if status === "WAITING"
  35. setInterval(updateCounter, 1000);
  36. return {start, maybeRestart};
  37. function insertEl() {
  38. const posts = document.querySelector("#m_posts");
  39. if (!posts) return false;
  40. posts.parentNode.insertBefore(el, posts.nextSibling);
  41. return true;
  42. }
  43. function start() {
  44. if (insertEl()) {
  45. check(false);
  46. }
  47. }
  48. function maybeRestart() {
  49. if (document.body.contains(el)) {
  50. return;
  51. }
  52. clearTimeout(checkTimer);
  53. if (abortController) {
  54. abortController.abort();
  55. }
  56. start();
  57. }
  58. async function check(refresh = true) {
  59. status = "CHECKING";
  60. if (document.querySelector("[title=加载下一页]")) {
  61. status = "COMPLETE";
  62. return;
  63. }
  64. if (refresh) {
  65. abortController = new AbortController();
  66. const {signal} = abortController;
  67. try {
  68. await fetchAndRefresh(signal);
  69. } catch (err) {
  70. console.warn(err);
  71. if (signal.aborted) {
  72. return;
  73. }
  74. }
  75. }
  76. lastUpdate = Date.now();
  77. status = "WAITING";
  78. checkTimer = setTimeout(check, interval);
  79. }
  80. function updateCounter() {
  81. if (status === "COMPLETE") {
  82. el.textContent = "current page completed";
  83. } else if (status === "WAITING") {
  84. el.textContent = Math.round((interval - (Date.now() - lastUpdate)) / 1000);
  85. } else if (status === "CHECKING") {
  86. el.textContent = "loading";
  87. }
  88. }
  89. }
  90. async function fetchAndRefresh(signal) {
  91. const r = await fetch(location.href, {signal});
  92. if (!r.ok) {
  93. throw new Error("connection error");
  94. }
  95. const buffer = await r.arrayBuffer();
  96. const parser = new DOMParser;
  97. const root = parser.parseFromString(await decodeGBK(buffer, signal), "text/html");
  98. const loadedIds = getLoadedIds();
  99.  
  100. const nodes = root.querySelector("#m_posts_c").children;
  101. const posts = [];
  102. for (let i = 0; i < nodes.length; i += 2) {
  103. const id = nodes[i].firstElementChild.firstElementChild.id;
  104. if (loadedIds.has(id)) {
  105. continue;
  106. }
  107. posts.push({
  108. table: nodes[i],
  109. js: createScript(nodes[i + 1])
  110. });
  111. }
  112. if (posts.length) {
  113. // update userinfo
  114. for (const script of root.querySelectorAll("script")) {
  115. if (script.textContent.includes("userinfostart")) {
  116. document.head.appendChild(createScript(script));
  117. break;
  118. }
  119. }
  120. // update post
  121. const container = document.querySelector("#m_posts_c");
  122. for (const post of posts) {
  123. container.append(post.table, post.js);
  124. }
  125.  
  126. }
  127. // update pagination
  128. const buttonBar = document.querySelector("#pagebbtm");
  129. const newButtonBar = root.querySelector("#pagebbtm");
  130. buttonBar.parentNode.replaceChild(newButtonBar, buttonBar);
  131. refreshScripts(newButtonBar);
  132. }
  133.  
  134. function getLoadedIds() {
  135. const s = new Set;
  136. const rows = document.querySelectorAll(".postrow");
  137. for (const row of rows) {
  138. s.add(row.id);
  139. }
  140. return s;
  141. }
  142.  
  143. function decodeGBK(buffer, signal) {
  144. const decoder = new TextDecoder("gbk");
  145. return Promise.race([
  146. decoder.decode(buffer),
  147. waitAbort(signal)
  148. ]);
  149. }
  150. function waitAbort(signal) {
  151. return new Promise((resolve, reject) => {
  152. if (signal.aborted) {
  153. doReject();
  154. return;
  155. }
  156. signal.addEventListener("abort", doReject, {once: true});
  157. function doReject() {
  158. reject(new Error("aborted"));
  159. }
  160. });
  161. }
  162. function createScript(script) {
  163. const newScript = document.createElement("script");
  164. newScript.textContent = script.textContent;
  165. return newScript;
  166. }
  167. function refreshScripts(el) {
  168. const scripts = el.querySelectorAll("script");
  169. for (const script of scripts) {
  170. const newScript = createScript(script);
  171. script.parentNode.replaceChild(newScript, script);
  172. }
  173. }
  174. })();