NGA Fast View Post Fix

修复论坛“快速浏览这个帖子”功能,并可在“快速浏览这个帖子”时点赞,以及点赞后更新实时点赞数

  1. // ==UserScript==
  2. // @name NGA Fast View Post Fix
  3. // @name:zh-CN NGA “快速浏览这个帖子” 修复
  4.  
  5. // @description 修复论坛“快速浏览这个帖子”功能,并可在“快速浏览这个帖子”时点赞,以及点赞后更新实时点赞数
  6.  
  7. // @namespace https://gf.qytechs.cn/users/263018
  8. // @version 1.1.1
  9. // @author snyssss
  10. // @license MIT
  11.  
  12. // @match *://bbs.nga.cn/*
  13. // @match *://ngabbs.com/*
  14. // @match *://nga.178.com/*
  15.  
  16. // @require https://update.gf.qytechs.cn/scripts/486070/1378387/NGA%20Library.js
  17.  
  18. // @grant GM_setValue
  19. // @grant GM_getValue
  20. // @grant GM_registerMenuCommand
  21.  
  22. // @noframes
  23. // ==/UserScript==
  24.  
  25. ((ui, ubbcode) => {
  26. if (!ui) return;
  27. if (!ubbcode) return;
  28.  
  29. // KEY
  30. const USER_AGENT_KEY = "USER_AGENT";
  31. const IS_SHOW_SCORE_KEY = "IS_SHOW_SCORE";
  32. const IS_SHOW_SCORE_RESULT_KEY = "IS_SHOW_SCORE_RESULT_KEY";
  33.  
  34. // User Agent
  35. const USER_AGENT = (() => {
  36. const data = GM_getValue(USER_AGENT_KEY) || "Nga_Official";
  37.  
  38. GM_registerMenuCommand(`修改UA${data}`, () => {
  39. const value = prompt("修改UA", data);
  40.  
  41. if (value) {
  42. GM_setValue(USER_AGENT_KEY, value);
  43.  
  44. location.reload();
  45. }
  46. });
  47.  
  48. return data;
  49. })();
  50.  
  51. // 是否显示评分
  52. const showScore = (() => {
  53. const data = GM_getValue(IS_SHOW_SCORE_KEY) || false;
  54.  
  55. GM_registerMenuCommand(`显示评分:${data ? "是" : "否"}`, () => {
  56. GM_setValue(IS_SHOW_SCORE_KEY, !data);
  57.  
  58. location.reload();
  59. });
  60.  
  61. return data;
  62. })();
  63.  
  64. // 是否显示评分结果
  65. const showScoreResult = (() => {
  66. const data = GM_getValue(IS_SHOW_SCORE_RESULT_KEY) || false;
  67.  
  68. GM_registerMenuCommand(`显示评分结果:${data ? "是" : "否"}`, () => {
  69. GM_setValue(IS_SHOW_SCORE_RESULT_KEY, !data);
  70.  
  71. location.reload();
  72. });
  73.  
  74. return data;
  75. })();
  76.  
  77. // 简单的统一请求
  78. const request = (url, config = {}) =>
  79. fetch(url, {
  80. headers: {
  81. "X-User-Agent": USER_AGENT,
  82. },
  83. ...config,
  84. });
  85.  
  86. // 获取帖子信息
  87. const getPostInfo = async (tid, pid) => {
  88. const url = `/read.php?tid=${tid}&pid=${pid}`;
  89.  
  90. const data = await new Promise((resolve) =>
  91. request(url)
  92. .then((res) => res.blob())
  93. .then((res) => {
  94. // 读取内容
  95. const reader = new FileReader();
  96.  
  97. reader.onload = () => {
  98. const parser = new DOMParser();
  99.  
  100. const doc = parser.parseFromString(reader.result, "text/html");
  101.  
  102. // 验证帖子正常
  103. const verify = doc.querySelector("#m_posts");
  104.  
  105. if (verify === null) {
  106. throw new Error();
  107. }
  108.  
  109. // 取得顶楼 UID
  110. const uid = (() => {
  111. const ele = doc.querySelector("#postauthor0");
  112.  
  113. if (ele) {
  114. const res = ele.getAttribute("href").match(/uid=((-?)(\S+))/);
  115.  
  116. if (res) {
  117. return res[1];
  118. }
  119. }
  120.  
  121. return 0;
  122. })();
  123.  
  124. // 取得顶楼标题
  125. const subject = doc.querySelector("#postsubject0").innerHTML;
  126.  
  127. // 取得顶楼内容
  128. const content = doc.querySelector("#postcontent0").innerHTML;
  129.  
  130. // 取得用户信息
  131. const user = (() => {
  132. const text = Tools.searchPair(reader.result, `"${uid}":`);
  133.  
  134. if (text) {
  135. try {
  136. return JSON.parse(text);
  137. } catch {
  138. return null;
  139. }
  140. }
  141.  
  142. return null;
  143. })();
  144.  
  145. // 取得额外信息
  146. const extra = (() => {
  147. const content = reader.result.substring(
  148. reader.result.indexOf(`commonui.postArg.proc( 0,`)
  149. );
  150.  
  151. const text = Tools.searchPair(
  152. content,
  153. `commonui.postArg.proc`,
  154. `(`,
  155. `)`
  156. );
  157.  
  158. if (text) {
  159. return text;
  160. }
  161.  
  162. return null;
  163. })();
  164.  
  165. // 返回结果
  166. resolve({
  167. subject,
  168. content,
  169. user,
  170. extra,
  171. });
  172. };
  173.  
  174. reader.readAsText(res, "GBK");
  175. })
  176. .catch(() => {
  177. resolve(null);
  178. })
  179. );
  180.  
  181. return data;
  182. };
  183.  
  184. // 快速浏览
  185. const fastViewPost = (() => {
  186. const window = ui.createCommmonWindow();
  187.  
  188. const container = document.createElement("DIV");
  189.  
  190. container.className = `fastViewPost`;
  191. container.innerHTML = `
  192. <div class="forumbox">
  193. <div class="postrow"></div>
  194. </div>
  195. `;
  196.  
  197. const list = container.querySelector(".postrow");
  198.  
  199. window._.addContent(null);
  200. window._.addContent(container);
  201.  
  202. return async (_, tid, pid = 0, opt) => {
  203. // 如果 opt 是 16,则说明是快速浏览窗口中的快速浏览,追加楼层
  204. // 反之清空楼层
  205. if (opt !== 16) {
  206. list.innerHTML = "";
  207. }
  208.  
  209. // 如果已加载过,直接返回
  210. if (list && list.querySelector(`[data-pid="${pid}"]`)) {
  211. return;
  212. }
  213.  
  214. // 请求内容,移除了 opt 参数
  215. // 泥潭会在快速浏览窗口里再次点击快速浏览时,把整个相关对话列出来,反而找不到当前帖子
  216. const data = await getPostInfo(tid, pid);
  217.  
  218. // 没有内容
  219. if (data === null) {
  220. return;
  221. }
  222.  
  223. // 解析数据
  224. const { content, user, extra } = data;
  225.  
  226. // 发帖人姓名
  227. const username = ui.htmlName(user.username);
  228.  
  229. // 加载楼层
  230. const row = document.createElement("DIV");
  231.  
  232. row.className = `row${
  233. 2 - (list.querySelectorAll(":scope > div").length % 2)
  234. }`;
  235. row.innerHTML = `
  236. <div class="c2" data-pid="${pid}">
  237. <div class="posterInfoLine b">${username}</div>
  238. <span class="postcontent ubbcode">${content}</span>
  239. </div>
  240. `;
  241.  
  242. list.insertBefore(row, list.firstChild);
  243.  
  244. // 格式转换完毕后显示窗口
  245. // 但泥潭这样做实际毫无意义,如果窗口开着的情况下进行格式转换,照样会短暂显示乱码
  246. ubbcode.bbsCode({
  247. c: row.querySelector(".ubbcode"),
  248. tId: tid,
  249. pId: pid,
  250. opt: 8,
  251. authorId: user.uid,
  252. noImg: 1,
  253. isNukePost: 0,
  254. callBack: () => {
  255. window._.show();
  256. },
  257. });
  258.  
  259. // 显示评分
  260. if (showScore) {
  261. // 取得评分数据
  262. const recommend = extra.match(/'(\d+),(\d+),(\d+)'/);
  263.  
  264. if (recommend) {
  265. const score_1 = recommend[2];
  266. const score_2 = recommend[3];
  267.  
  268. const ele = document.createElement("DIV");
  269.  
  270. ele.className = `right_`;
  271. ele.innerHTML = `
  272. <div style="display: inline-block; font-weight: normal;">
  273. <span class="small_colored_text_btn stxt block_txt_c2 vertmod">
  274. <span class="white">
  275. <a class="white" href="javascript: void(0);" title="支持" style="text-decoration: none;">
  276. <span style="font-family: comm_glyphs; -webkit-font-smoothing: antialiased; line-height: 1em; padding: 0 0.1em;">⯅</span>
  277. </a>
  278. <span class="recommendvalue"></span>
  279. <a class="white" href="javascript: void(0);" title="反对" style="text-decoration: none;">
  280. <span style="font-family: comm_glyphs; -webkit-font-smoothing: antialiased; line-height: 1em; padding: 0 0.1em;">⯆</span>
  281. </a>
  282. </span>
  283. </span>
  284. </div>
  285. `;
  286.  
  287. ele.querySelector(".recommendvalue").innerHTML =
  288. score_1 - score_2 || "&nbsp;";
  289.  
  290. row.querySelector(".posterInfoLine").appendChild(ele);
  291.  
  292. // 绑定事件
  293. (() => {
  294. const bindEvent = () => {
  295. const like = ele.querySelector("a:first-child");
  296.  
  297. like.onclick = () => {
  298. ui.postScoreAdd(like, {
  299. tid,
  300. pid,
  301. });
  302. };
  303.  
  304. const unlike = ele.querySelector("a:last-child");
  305.  
  306. unlike.onclick = () => {
  307. ui.postScoreAdd(
  308. unlike,
  309. {
  310. tid,
  311. pid,
  312. },
  313. 1
  314. );
  315. };
  316. };
  317.  
  318. // 绑定事件
  319. if (ui.postScoreAdd) {
  320. bindEvent();
  321. } else {
  322. __SCRIPTS.asyncLoad("read", bindEvent);
  323. }
  324. })();
  325. }
  326. }
  327. };
  328. })();
  329.  
  330. // 刷新评分
  331. const refreshScore = (anchor, { tid, pid }) => {
  332. const target = anchor.parentNode.querySelector(".recommendvalue");
  333.  
  334. if (target === null) {
  335. return;
  336. }
  337.  
  338. const observer = new MutationObserver(() => {
  339. observer.disconnect();
  340.  
  341. getPostInfo(tid, pid).then(({ extra }) => {
  342. if (extra) {
  343. const recommend = extra.match(/'(\d+),(\d+),(\d+)'/);
  344.  
  345. if (recommend) {
  346. const score_1 = recommend[2];
  347. const score_2 = recommend[3];
  348.  
  349. target.innerHTML = score_1 - score_2 || "&nbsp;";
  350. }
  351. }
  352. });
  353. });
  354.  
  355. observer.observe(target, {
  356. childList: true,
  357. });
  358. };
  359.  
  360. // 加载脚本
  361. (() => {
  362. // 修复快速浏览
  363. ubbcode.fastViewPost = fastViewPost;
  364.  
  365. // 绑定评分事件
  366. if (ui && showScoreResult) {
  367. Tools.interceptProperty(ui, "postScoreAdd", {
  368. afterGet: (_, args) => {
  369. refreshScore(...args);
  370. },
  371. });
  372. }
  373. })();
  374. })(commonui, ubbcode);

QingJ © 2025

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