JustMoeComments

萌娘百科看Lih的镜像站的评论,同时集成了作品讨论的评论

目前为 2024-06-13 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name JustMoeComments
  3. // @namespace https://github.com/gui-ying233/JustMoeComments
  4. // @version 2.15.0
  5. // @description 萌娘百科看Lih的镜像站的评论,同时集成了作品讨论的评论
  6. // @author 鬼影233
  7. // @license MIT
  8. // @match zh.moegirl.org.cn/*
  9. // @match mzh.moegirl.org.cn/*
  10. // @match mobile.moegirl.org.cn/*
  11. // @match moegirl.icu/*
  12. // @match zh.moegirl.tw/*
  13. // @match zh.moegirl.tw/*
  14. // @match mzh.moegirl.tw/*
  15. // @match mobile.moegirl.tw/*
  16. // @icon https://moegirl.uk/images/a/a2/%E7%B2%89%E8%89%B2%E5%A4%A7%E7%8C%9B%E5%AD%97.png
  17. // @supportURL https://github.com/gui-ying233/JustMoeComments/issues
  18. // ==/UserScript==
  19.  
  20. (async () => {
  21. "use strict";
  22. if (
  23. window.JMC ||
  24. new URLSearchParams(window.location.search).get("safemode")
  25. )
  26. return;
  27. window.JMC = true;
  28. await new Promise(resolve => {
  29. const intervId = setInterval(() => {
  30. if (typeof mw !== "undefined" && typeof wgULS !== "undefined") {
  31. clearInterval(intervId);
  32. resolve();
  33. }
  34. }, 50);
  35. });
  36. if (
  37. mw.config.get("wgAction") !== "view" ||
  38. ![0, 2, 4, 12, 274].includes(mw.config.get("wgNamespaceNumber"))
  39. )
  40. return;
  41. const api = new mw.Api();
  42. const generatePost = ({ username, text, timestamp, like }) => {
  43. let _timestamp;
  44. if (typeof timestamp === "number") {
  45. const diff = Date.now() - timestamp * 1000;
  46. if (diff > 0 && diff < 86400000) {
  47. _timestamp = moment(timestamp * 1000)
  48. .locale(mw.config.get("wgUserLanguage"))
  49. .fromNow();
  50. } else {
  51. _timestamp = moment(timestamp * 1000)
  52. .locale(mw.config.get("wgUserLanguage"))
  53. .format("LL, HH:mm:ss");
  54. }
  55. } else {
  56. _timestamp = timestamp;
  57. }
  58. const postDiv = document.createElement("div");
  59. postDiv.className = "comment-thread";
  60. postDiv.innerHTML = `<div class="comment-post"><div class="comment-avatar"><a href="//moegirl.uk/U:${username}"><img src="//moegirl.uk/extensions/Avatar/avatar.php?user=${username}" decofing="async" loading="lazy" fetchpriority="low"></a></div><div class="comment-body"><div class="comment-user"><a href="//moegirl.uk/U:${username}">${username}</a></div><div class="comment-text">${text}</div><div class="comment-footer"><span class="comment-time">${_timestamp}</span>${
  61. like ? `<span class="comment-like">赞 ${like}</span>` : ""
  62. }</div></div></div>`;
  63. postDiv.querySelector(".comment-avatar > a > img").onerror =
  64. function () {
  65. if (new URL(this.src).host === "moegirl.uk")
  66. this.src = `//commons.moegirl.org.cn/extensions/Avatar/avatar.php?user=${username}`;
  67. };
  68. [...postDiv.querySelectorAll("img[src^='/images/']")].forEach(i => {
  69. i.src = `//img.moegirl.org.cn/common/${new URL(
  70. i.src
  71. ).pathname.slice(8)}`;
  72. i.srcset = i.srcset.replaceAll(
  73. "/images/",
  74. "//img.moegirl.org.cn/common/"
  75. );
  76. });
  77. [
  78. ...postDiv.querySelectorAll(
  79. 'img[src*="thumb"][src$=".svg.png"], img[src*="thumb"][data-lazy-src$=".svg.png"], img[src*="thumb"][src$=".gif"], img[data-lazy-src*="thumb"][data-lazy-src$=".gif"]'
  80. ),
  81. ].forEach(i => {
  82. try {
  83. const _i = i.cloneNode();
  84. if (
  85. new mw.Uri(_i.src || _i.dataset.lazySrc).host ===
  86. "img.moegirl.org.cn"
  87. ) {
  88. _i.src = _i.src
  89. .replace("/thumb/", "/")
  90. .replace(/\.svg\/[^/]+\.svg\.png$/, ".svg")
  91. .replace(/\.gif\/[^/]+\.gif$/, ".gif");
  92. _i.removeAttribute("srcset");
  93. _i.removeAttribute("data-lazy-src");
  94. _i.removeAttribute("data-lazy-srcset");
  95. _i.removeAttribute("data-lazy-state");
  96. _i.classList.remove("lazyload");
  97. _i.onload = function () {
  98. i.replaceWith(_i);
  99. };
  100. }
  101. } catch {}
  102. });
  103. [
  104. ...postDiv.querySelectorAll(
  105. "a.extiw[title^='moe:'], a.extiw[title^='zhmoe:']"
  106. ),
  107. ].forEach(a => {
  108. a.classList.remove("extiw");
  109. api.get({
  110. action: "query",
  111. format: "json",
  112. titles: decodeURI(a.pathname.slice(1)),
  113. utf8: 1,
  114. formatversion: 2,
  115. }).done(b => {
  116. if (b.query.pages[0].missing) {
  117. a.href = `/index.php?title=${a.pathname.slice(
  118. 1
  119. )}&action=edit&redlink=1`;
  120. a.title += wgULS(" (页面不存在)", "(頁面不存在)");
  121. a.classList += "new";
  122. } else if (
  123. b.query.pages[0].pageid === mw.config.get("wgArticleId")
  124. ) {
  125. a.href = "";
  126. a.title = "";
  127. a.classList += "mw-selflink selflink";
  128. } else {
  129. a.href = a.pathname;
  130. a.title = a.title.replace(/moe:|zhmoe:/, "");
  131. }
  132. });
  133. });
  134. [...postDiv.getElementsByTagName("script")].forEach(s => {
  135. const _s = document.createElement("script");
  136. _s.innerHTML = s.innerHTML;
  137. [...s.attributes].forEach(a => {
  138. _s.setAttribute(a.name, a.value);
  139. });
  140. s.parentNode.replaceChild(_s, s);
  141. });
  142. return postDiv;
  143. };
  144. mw.loader.using(["moment"]).done(() => {
  145. const commentCSS = document.createElement("style");
  146. commentCSS.innerHTML =
  147. "#flowthread{clear:both;padding:1.5em}body.skin-moeskin #flowthread{background-color:var(--theme-background-color)}.comment-container-top:not(:empty){border:1px #ccc solid;border-radius:5px}body.skin-vector .comment-container-top{background-color:rgb(191 234 181 / 20%)}body.skin-moeskin .comment-container-top{background-color:var(--theme-card-background-color)}.comment-container-top>div:first-child{height:24px;line-height:24px;text-indent:1em;font-size:small;border-radius:5px 5px 0 0;font-weight:bold}body.skin-vector .comment-container-top>div:first-child{background-color:rgb(18 152 34 / 47%);color:#fff}body.skin-moeskin .comment-container-top>div:first-child{background-color:var(--theme-accent-color);color:var(--theme-accent-link-color)}.comment-thread{border-top:1px solid rgba(0,0,0,0.13)}.comment-thread .comment-thread{margin-left:40px}.comment-post{padding:10px}.comment-avatar{float:left}.comment-avatar img{width:50px;height:50px}.comment-body{padding-left:60px}.comment-thread>div:not(:first-of-type) .comment-avatar img{width:30px;height:30px}.comment-thread>div:not(:first-of-type) .comment-body{padding-left:40px}.comment-user,.comment-user a{color:#777;font-size:13px;margin-right:8px}.post-content .comment-text{position:static}.comment-text{font-size:13px;line-height:1.5em;margin:.5em 0;word-wrap:break-word;position:relative;overflow:hidden;min-height:1em}.comment-footer{font-size:12px;margin-right:8px;color:#999}.comment-like{margin-left:5px}";
  148. document.head.appendChild(commentCSS);
  149. const containerTop = document.createElement("div");
  150. containerTop.className = "comment-container-top";
  151. const container = document.createElement("div");
  152. container.className = "comment-container";
  153. const postContent = document.createElement("div");
  154. postContent.id = "flowthread";
  155. postContent.className = "post-content";
  156. postContent.appendChild(containerTop);
  157. postContent.appendChild(container);
  158. document
  159. .getElementById(
  160. mw.config.get("skin") === "vector"
  161. ? "footer"
  162. : "moe-global-footer"
  163. )
  164. .appendChild(postContent);
  165. fetch(
  166. `https://moegirl.uk/api.php?${new URLSearchParams({
  167. action: "query",
  168. format: "json",
  169. prop: "pageprops",
  170. titles: mw.config.get("wgPageName"),
  171. utf8: 1,
  172. formatversion: 2,
  173. origin: "*",
  174. })}`
  175. )
  176. .then(a => a.json())
  177. .then(a =>
  178. (function getComment(offset) {
  179. fetch(
  180. `https://moegirl.uk/api.php?${new URLSearchParams({
  181. action: "flowthread",
  182. format: "json",
  183. type: "list",
  184. pageid: a.query.pages[0].pageid,
  185. limit: 15,
  186. offset,
  187. utf8: 1,
  188. formatversion: 2,
  189. origin: "*",
  190. })}`
  191. )
  192. .then(b => b.json())
  193. .then(b => {
  194. if (b.flowthread.popular.length) {
  195. document.body.getElementsByClassName(
  196. "comment-container-top"
  197. )[0].innerHTML = "<div>热门评论</div>";
  198. for (const post of b.flowthread.popular) {
  199. const _post = generatePost(post);
  200. _post.classList.add("comment-popular");
  201. document.body
  202. .getElementsByClassName(
  203. "comment-container-top"
  204. )[0]
  205. .appendChild(_post);
  206. }
  207. }
  208. for (const post of b.flowthread.posts) {
  209. const _post = generatePost(post);
  210. _post.id = `comment-${post.id}`;
  211. if (post.parentid) {
  212. document
  213. .getElementById(
  214. `comment-${post.parentid}`
  215. )
  216. .appendChild(_post);
  217. } else {
  218. document
  219. .getElementsByClassName(
  220. "comment-container"
  221. )[0]
  222. .appendChild(_post);
  223. }
  224. }
  225. if (b.flowthread.count > offset + 15) {
  226. new IntersectionObserver(
  227. (entries, observer) => {
  228. entries.forEach(entry => {
  229. if (entry.isIntersecting) {
  230. getComment(offset + 15);
  231. observer.unobserve(
  232. entry.target
  233. );
  234. }
  235. });
  236. }
  237. ).observe(
  238. document.querySelector(
  239. ".comment-container > div.comment-thread:last-of-type"
  240. )
  241. );
  242. }
  243. });
  244. })(0)
  245. )
  246. .catch(() => {
  247. fetch(
  248. "https://testingcf.jsdelivr.net/gh/gui-ying233/JustMoeComments/flowthread.json"
  249. )
  250. .then(a => a.json())
  251. .then(a => {
  252. let f = 0;
  253. for (const t of a) {
  254. if (
  255. t.title !==
  256. mw.config.get("wgPageName").replace("_", " ")
  257. )
  258. continue;
  259. a = t.posts;
  260. f = 1;
  261. break;
  262. }
  263. if (!f) return;
  264. for (const b of a) {
  265. if (+b.status) continue;
  266. const _post = generatePost({
  267. username: b.username,
  268. text: b.text,
  269. timestamp: "",
  270. });
  271. _post.id = `comment-${b.id}`;
  272. (b.parentid
  273. ? document.getElementById(
  274. `comment-${b.parentid}`
  275. )
  276. : document.getElementsByClassName(
  277. "comment-container"
  278. )[0]
  279. ).appendChild(_post);
  280. }
  281. });
  282. });
  283. if (mw.config.get("skin") !== "moeskin") return;
  284. setTimeout(() => {
  285. if (
  286. ![
  287. (mw.config.get("wgPageName"),
  288. mw.config.get("wgPageName").replace(/^(.+)\(.+?\)$/, "$1"),
  289. document.getElementById("firstHeading").innerText),
  290. ].includes(
  291. document.body.getElementsByClassName("artwork-title")[0]
  292. ?.innerText
  293. )
  294. )
  295. return;
  296. document.body.getElementsByClassName("comment-item").forEach(c => {
  297. if (
  298. !c.getElementsByClassName("comment-title")[0].innerText &&
  299. !c.getElementsByClassName("comment-content")[0].innerText
  300. )
  301. return;
  302. const post = {
  303. like: +c
  304. .getElementsByClassName("n-button-group")[0]
  305. .innerText.split("\n")[0],
  306. username:
  307. c.getElementsByClassName("comment-author")[0].innerText,
  308. text:
  309. c.getElementsByClassName("comment-title")[0].innerText +
  310. (c.getElementsByClassName("comment-title")[0]
  311. .innerText &&
  312. c.getElementsByClassName("comment-content")[0].innerText
  313. ? document.createElement("br").outerHTML
  314. : "") +
  315. c.getElementsByClassName("comment-content")[0]
  316. .innerText,
  317. timestamp:
  318. c.getElementsByClassName("comment-time")[0].innerText,
  319. };
  320. document
  321. .getElementsByClassName("comment-container")[0]
  322. .appendChild(generatePost(post));
  323. });
  324. }, 5000);
  325. });
  326. })();