JustMoeComments

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

安裝腳本?
作者推薦腳本

您可能也會喜歡 灰机wiki查看版本历史

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