Greasy Fork 还支持 简体中文。

JustMoeComments

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

目前為 2023-10-12 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name JustMoeComments
  3. // @namespace https://github.com/gui-ying233/JustMoeComments
  4. // @version 2.10.2
  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. // @icon https://moegirl.uk/images/a/a2/%E7%B2%89%E8%89%B2%E5%A4%A7%E7%8C%9B%E5%AD%97.png
  12. // @supportURL https://github.com/gui-ying233/JustMoeComments/issues
  13. // ==/UserScript==
  14.  
  15. (async function () {
  16. "use strict";
  17. await new Promise(resolve => {
  18. const intervId = setInterval(() => {
  19. if (typeof mw !== "undefined" && typeof wgULS !== "undefined") {
  20. clearInterval(intervId);
  21. resolve();
  22. }
  23. }, 50);
  24. });
  25. if (
  26. mw.config.get("wgAction") === "view" &&
  27. [0, 2, 4, 12, 274].includes(mw.config.get("wgNamespaceNumber"))
  28. ) {
  29. const api = new mw.Api();
  30. const generatePost = post => {
  31. let timestamp;
  32. if (typeof post.timestamp === "number") {
  33. const diff = Date.now() - post.timestamp * 1000;
  34. if (diff > 0 && diff < 86400000) {
  35. timestamp = moment(post.timestamp * 1000)
  36. .locale(mw.config.get("wgUserLanguage"))
  37. .fromNow();
  38. } else {
  39. timestamp = moment(post.timestamp * 1000)
  40. .locale(mw.config.get("wgUserLanguage"))
  41. .format("LL, HH:mm:ss");
  42. }
  43. } else {
  44. timestamp = post.timestamp;
  45. }
  46. const postDiv = document.createElement("div");
  47. postDiv.className = "comment-thread";
  48. postDiv.innerHTML = `<div class="comment-post"><div class="comment-avatar"><a href="//moegirl.uk/U:${
  49. post.username
  50. }"><img src="//moegirl.uk/extensions/Avatar/avatar.php?user=${
  51. post.username
  52. }" decofing="async" loading="lazy" fetchpriority="low"></a></div><div class="comment-body"><div class="comment-user"><a href="//moegirl.uk/U:${
  53. post.username
  54. }">${post.username}</a></div><div class="comment-text">${
  55. post.text
  56. }</div><div class="comment-footer"><span class="comment-time">${timestamp}</span>${
  57. post.like
  58. ? `<span class="comment-like">赞 ${post.like}</span>`
  59. : ""
  60. }</div></div></div>`;
  61. [...postDiv.querySelectorAll("img[src^='/images/']")].forEach(i => {
  62. i.src = `//img.moegirl.org.cn/common/${new URL(
  63. i.src
  64. ).pathname.slice(8)}`;
  65. i.srcset = i.srcset.replaceAll(
  66. "/images/",
  67. "//img.moegirl.org.cn/common/"
  68. );
  69. });
  70. [
  71. ...postDiv.querySelectorAll(
  72. '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"]'
  73. ),
  74. ].forEach(i => {
  75. try {
  76. const _i = i.cloneNode();
  77. if (
  78. new mw.Uri(_i.src || _i.dataset.lazySrc).host ===
  79. "img.moegirl.org.cn"
  80. ) {
  81. _i.src = _i.src
  82. .replace("/thumb/", "/")
  83. .replace(/\.svg\/[^/]+\.svg\.png$/, ".svg")
  84. .replace(/\.gif\/[^/]+\.gif$/, ".gif");
  85. _i.removeAttribute("srcset");
  86. _i.removeAttribute("data-lazy-src");
  87. _i.removeAttribute("data-lazy-srcset");
  88. _i.removeAttribute("data-lazy-state");
  89. _i.classList.remove("lazyload");
  90. _i.onload = function () {
  91. i.replaceWith(_i);
  92. };
  93. }
  94. } catch {}
  95. });
  96. [
  97. ...postDiv.querySelectorAll(
  98. "a.extiw[title^='moe:'], a.extiw[title^='zhmoe:']"
  99. ),
  100. ].forEach(a => {
  101. a.classList.remove("extiw");
  102. api.get({
  103. action: "query",
  104. format: "json",
  105. titles: decodeURI(a.pathname.slice(1)),
  106. utf8: 1,
  107. formatversion: 2,
  108. }).done(b => {
  109. if (b.query.pages[0].missing) {
  110. a.href = `/index.php?title=${a.pathname.slice(
  111. 1
  112. )}&action=edit&redlink=1`;
  113. a.title += wgULS(" (页面不存在)", "(頁面不存在)");
  114. a.classList += "new";
  115. } else if (
  116. b.query.pages[0].pageid === mw.config.get("wgArticleId")
  117. ) {
  118. a.href = "";
  119. a.title = "";
  120. a.classList += "mw-selflink selflink";
  121. } else {
  122. a.href = a.pathname;
  123. a.title = a.title.replace(/moe:|zhmoe:/, "");
  124. }
  125. });
  126. });
  127. [...postDiv.getElementsByTagName("script")].forEach(s => {
  128. const _s = document.createElement("script");
  129. _s.innerHTML = s.innerHTML;
  130. [...s.attributes].forEach(a => {
  131. _s.setAttribute(a.name, a.value);
  132. });
  133. s.parentNode.replaceChild(_s, s);
  134. });
  135. return postDiv;
  136. };
  137. mw.loader.using(["moment"]).done(() => {
  138. const commentCSS = document.createElement("style");
  139. commentCSS.innerHTML =
  140. "#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}";
  141. document.head.appendChild(commentCSS);
  142. const containerTop = document.createElement("div");
  143. containerTop.className = "comment-container-top";
  144. const container = document.createElement("div");
  145. container.className = "comment-container";
  146. const postContent = document.createElement("div");
  147. postContent.id = "flowthread";
  148. postContent.className = "post-content";
  149. postContent.appendChild(containerTop);
  150. postContent.appendChild(container);
  151. switch (mw.config.get("skin")) {
  152. case "vector":
  153. document.getElementById("footer").appendChild(postContent);
  154. break;
  155. case "moeskin":
  156. default:
  157. document
  158. .getElementById("moe-global-footer")
  159. .appendChild(postContent);
  160. break;
  161. }
  162. fetch(
  163. `https://moegirl.uk/api.php?${new URLSearchParams({
  164. action: "query",
  165. format: "json",
  166. prop: "pageprops",
  167. titles: mw.config.get("wgPageName"),
  168. utf8: 1,
  169. formatversion: 2,
  170. origin: "*",
  171. })}`
  172. )
  173. .then(a => a.json())
  174. .then(a =>
  175. (function getComment(offset) {
  176. fetch(
  177. `https://moegirl.uk/api.php?${new URLSearchParams({
  178. action: "flowthread",
  179. format: "json",
  180. type: "list",
  181. pageid: a.query.pages[0].pageid,
  182. limit: 15,
  183. offset,
  184. utf8: 1,
  185. formatversion: 2,
  186. origin: "*",
  187. })}`
  188. )
  189. .then(b => b.json())
  190. .then(b => {
  191. if (b.flowthread.popular.length) {
  192. document.body.getElementsByClassName(
  193. "comment-container-top"
  194. )[0].innerHTML = "<div>热门评论</div>";
  195. for (const post of b.flowthread.popular) {
  196. const _post = generatePost(post);
  197. _post.classList.add("comment-popular");
  198. document.body
  199. .getElementsByClassName(
  200. "comment-container-top"
  201. )[0]
  202. .appendChild(_post);
  203. }
  204. }
  205. if (mw.config.get("skin") === "moeskin") {
  206. setTimeout(() => {
  207. if (
  208. [
  209. (mw.config.get("wgPageName"),
  210. mw.config
  211. .get("wgPageName")
  212. .replace(
  213. /^(.+)\(.+?\)$/,
  214. "$1"
  215. ),
  216. document.getElementById(
  217. "firstHeading"
  218. ).innerText),
  219. ].includes(
  220. document.body.getElementsByClassName(
  221. "artwork-title"
  222. )[0]?.innerText
  223. )
  224. ) {
  225. document.body
  226. .getElementsByClassName(
  227. "comment-item"
  228. )
  229. .forEach(c => {
  230. if (
  231. c.getElementsByClassName(
  232. "comment-title"
  233. )[0].innerText ||
  234. c.getElementsByClassName(
  235. "comment-content"
  236. )[0].innerText
  237. ) {
  238. const post = {
  239. like: +c
  240. .getElementsByClassName(
  241. "n-button-group"
  242. )[0]
  243. .innerText.split(
  244. "\n"
  245. )[0],
  246. username:
  247. c.getElementsByClassName(
  248. "comment-author"
  249. )[0].innerText,
  250. text:
  251. c.getElementsByClassName(
  252. "comment-title"
  253. )[0].innerText +
  254. (c.getElementsByClassName(
  255. "comment-title"
  256. )[0]
  257. .innerText &&
  258. c.getElementsByClassName(
  259. "comment-content"
  260. )[0].innerText
  261. ? document.createElement(
  262. "br"
  263. )
  264. .outerHTML
  265. : "") +
  266. c.getElementsByClassName(
  267. "comment-content"
  268. )[0].innerText,
  269. timestamp:
  270. c.getElementsByClassName(
  271. "comment-time"
  272. )[0].innerText,
  273. };
  274. document
  275. .getElementsByClassName(
  276. "comment-container"
  277. )[0]
  278. .appendChild(
  279. generatePost(
  280. post
  281. )
  282. );
  283. }
  284. });
  285. }
  286. }, 5000);
  287. }
  288. for (const post of b.flowthread.posts) {
  289. const _post = generatePost(post);
  290. _post.id = `comment-${post.id}`;
  291. if (post.parentid) {
  292. document
  293. .getElementById(
  294. `comment-${post.parentid}`
  295. )
  296. .appendChild(_post);
  297. } else {
  298. document
  299. .getElementsByClassName(
  300. "comment-container"
  301. )[0]
  302. .appendChild(_post);
  303. }
  304. }
  305. if (b.flowthread.count > offset + 15) {
  306. new IntersectionObserver(
  307. (entries, observer) => {
  308. entries.forEach(entry => {
  309. if (entry.isIntersecting) {
  310. getComment(offset + 15);
  311. observer.unobserve(
  312. entry.target
  313. );
  314. }
  315. });
  316. }
  317. ).observe(
  318. document.querySelector(
  319. ".comment-container > div.comment-thread:last-of-type"
  320. )
  321. );
  322. }
  323. });
  324. })(0)
  325. )
  326. .catch(() => {
  327. fetch(
  328. "https://raw.githubusercontent.com/gui-ying233/JustMoeComments/main/flowthread.json"
  329. )
  330. .then(a => a.json())
  331. .then(a => {
  332. let f = 0;
  333. for (const t of a) {
  334. if (
  335. t.title ===
  336. mw.config
  337. .get("wgPageName")
  338. .replace("_", " ")
  339. ) {
  340. a = t.posts;
  341. f = 1;
  342. break;
  343. }
  344. }
  345. if (!f) return;
  346. for (const b of a) {
  347. if (+b.status) continue;
  348. const _post = generatePost({
  349. username: b.username,
  350. text: b.text,
  351. timestamp: "",
  352. });
  353. _post.id = `comment-${b.id}`;
  354. if (b.parentid) {
  355. document
  356. .getElementById(`comment-${b.parentid}`)
  357. .appendChild(_post);
  358. } else {
  359. document
  360. .getElementsByClassName(
  361. "comment-container"
  362. )[0]
  363. .appendChild(_post);
  364. }
  365. }
  366. });
  367. });
  368. });
  369. }
  370. })();