├── .deepsource.toml
├── .github
└── workflows
│ └── 导出FlowThread评论.yaml
├── LICENSE
├── flowthread.json
├── main.css
├── main.user.js
├── package-lock.json
├── package.json
└── scripts
└── 导出FlowThread评论.js
/.deepsource.toml:
--------------------------------------------------------------------------------
1 | version = 1
2 |
3 | [[analyzers]]
4 | name = "javascript"
5 |
6 | [analyzers.meta]
7 | environment = ["browser"]
--------------------------------------------------------------------------------
/.github/workflows/导出FlowThread评论.yaml:
--------------------------------------------------------------------------------
1 | name: 导出 FlowThread 评论
2 | on:
3 | schedule:
4 | - cron: "*/5 * * * *"
5 | workflow_dispatch:
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: 签出
11 | uses: actions/checkout@v4
12 | - name: 设置 node.js
13 | uses: actions/setup-node@v4
14 | with:
15 | node-version: 20
16 | cache: npm
17 | - name: 安装 npm 依赖
18 | run: npm install
19 | - name: 配置 git
20 | run: |
21 | git config user.name "github-actions[bot]"
22 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
23 | git pull --rebase
24 | - name: 导出 FlowThread 评论
25 | env:
26 | MOEGIRL_UK_MGPUSERID: ${{ vars.MOEGIRL_UK_MGPUSERID }}
27 | MOEGIRL_UK_MGPTOKEN: ${{ secrets.MOEGIRL_UK_MGPTOKEN }}
28 | run: node scripts/导出FlowThread评论
29 | - name: 更新 flowthread.json
30 | env:
31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32 | run: |
33 | git add -f flowthread.json
34 | if ! git diff-index --quiet HEAD --; then
35 | git commit -m "Update threeds"
36 | git push https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/gui-ying233/JustMoeComments.git
37 | else
38 | echo "评论无更新"
39 | fi
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 鬼影233
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/main.css:
--------------------------------------------------------------------------------
1 | #flowthread {
2 | clear: both;
3 | padding: 1.5em
4 | }
5 | body.skin-moeskin #flowthread {
6 | background-color: var(--theme-background-color)
7 | }
8 | .comment-container-top:not(:empty) {
9 | border: 1px #ccc solid;
10 | border-radius: 5px
11 | }
12 | body.skin-vector .comment-container-top {
13 | background-color: rgb(191 234 181 / 20%)
14 | }
15 | body.skin-moeskin .comment-container-top {
16 | background-color: var(--theme-card-background-color)
17 | }
18 | .comment-container-top>div:first-child {
19 | height: 24px;
20 | line-height: 24px;
21 | text-indent: 1em;
22 | font-size: small;
23 | border-radius: 5px 5px 0 0;
24 | font-weight: bold
25 | }
26 | body.skin-vector .comment-container-top>div:first-child {
27 | background-color: rgb(18 152 34 / 47%);
28 | color: #fff
29 | }
30 | body.skin-moeskin .comment-container-top>div:first-child {
31 | background-color: var(--theme-accent-color);
32 | color: var(--theme-accent-link-color)
33 | }
34 | .comment-thread {
35 | border-top: 1px solid rgba(0,0,0,0.13)
36 | }
37 | .comment-thread .comment-thread {
38 | margin-left: 40px
39 | }
40 | .comment-post {
41 | padding: 10px
42 | }
43 | .comment-avatar {
44 | float: left
45 | }
46 | .comment-avatar img {
47 | width: 50px;
48 | height: 50px
49 | }
50 | .comment-body {
51 | padding-left: 60px
52 | }
53 | .comment-thread>div:not(:first-of-type) .comment-avatar img {
54 | width: 30px;
55 | height: 30px
56 | }
57 | .comment-thread>div:not(:first-of-type) .comment-body {
58 | padding-left: 40px
59 | }
60 | .comment-user,.comment-user a {
61 | color: #777;
62 | font-size: 13px;
63 | margin-right: 8px
64 | }
65 | .post-content .comment-text {
66 | position: static
67 | }
68 | .comment-text {
69 | font-size: 13px;
70 | line-height: 1.5em;
71 | margin: .5em 0;
72 | word-wrap: break-word;
73 | position: relative;
74 | overflow: hidden;
75 | min-height: 1em
76 | }
77 | .comment-footer {
78 | font-size: 12px;
79 | margin-right: 8px;
80 | color: #999
81 | }
82 | .comment-like {
83 | margin-left: 5px
84 | }
85 |
--------------------------------------------------------------------------------
/main.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name JustMoeComments
3 | // @namespace https://github.com/gui-ying233/JustMoeComments
4 | // @version 2.16.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 | // @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 | // @grant none
20 | // ==/UserScript==
21 |
22 | (async () => {
23 | "use strict";
24 | if (new URLSearchParams(window.location.search).get("safemode")) return;
25 | await new Promise(resolve => {
26 | const intervId = setInterval(
27 | () => window?.mw?.Api && (clearInterval(intervId), resolve()),
28 | 50
29 | );
30 | });
31 | window.wgULS ??= (hans, hant, cn, tw, hk, sg, zh, mo, my) =>
32 | ({
33 | zh: zh || hans || hant || cn || tw || hk || sg || mo || my,
34 | "zh-hans": hans || cn || sg || my,
35 | "zh-hant": hant || tw || hk || mo,
36 | "zh-cn": cn || hans || sg || my,
37 | "zh-sg": sg || hans || cn || my,
38 | "zh-tw": tw || hant || hk || mo,
39 | "zh-hk": hk || hant || mo || tw,
40 | "zh-mo": mo || hant || hk || tw,
41 | }[mw.config.get("wgUserLanguage")] ||
42 | zh ||
43 | hans ||
44 | hant ||
45 | cn ||
46 | tw ||
47 | hk ||
48 | sg ||
49 | mo ||
50 | my);
51 | if (
52 | mw.config.get("wgAction") !== "view" ||
53 | ![0, 2, 4, 12, 274].includes(mw.config.get("wgNamespaceNumber"))
54 | )
55 | return;
56 | const api = new mw.Api();
57 | const generatePost = ({ username, text, timestamp, like }) => {
58 | let _timestamp;
59 | if (typeof timestamp === "number") {
60 | const diff = Date.now() - timestamp * 1000;
61 | if (diff > 0 && diff < 86400000) {
62 | _timestamp = moment(timestamp * 1000)
63 | .locale(mw.config.get("wgUserLanguage"))
64 | .fromNow();
65 | } else {
66 | _timestamp = moment(timestamp * 1000)
67 | .locale(mw.config.get("wgUserLanguage"))
68 | .format("LL, HH:mm:ss");
69 | }
70 | } else {
71 | _timestamp = timestamp;
72 | }
73 | const postDiv = document.createElement("div");
74 | postDiv.className = "comment-thread";
75 | postDiv.innerHTML = `
`;
78 | postDiv.querySelector(".comment-avatar > a > img").onerror =
79 | function () {
80 | if (new URL(this.src).host === "moegirl.uk")
81 | this.src = `//commons.moegirl.org.cn/extensions/Avatar/avatar.php?user=${username}`;
82 | };
83 | [...postDiv.querySelectorAll("img[src^='/images/']")].forEach(i => {
84 | i.src = `//img.moegirl.org.cn/common/${new URL(
85 | i.src
86 | ).pathname.slice(8)}`;
87 | i.srcset = i.srcset.replaceAll(
88 | "/images/",
89 | "//img.moegirl.org.cn/common/"
90 | );
91 | });
92 | [
93 | ...postDiv.querySelectorAll(
94 | '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"]'
95 | ),
96 | ].forEach(i => {
97 | try {
98 | const _i = i.cloneNode();
99 | if (
100 | new mw.Uri(_i.src || _i.dataset.lazySrc).host ===
101 | "img.moegirl.org.cn"
102 | ) {
103 | _i.src = _i.src
104 | .replace("/thumb/", "/")
105 | .replace(/\.svg\/[^/]+\.svg\.png$/, ".svg")
106 | .replace(/\.gif\/[^/]+\.gif$/, ".gif");
107 | _i.removeAttribute("srcset");
108 | _i.removeAttribute("data-lazy-src");
109 | _i.removeAttribute("data-lazy-srcset");
110 | _i.removeAttribute("data-lazy-state");
111 | _i.classList.remove("lazyload");
112 | _i.onload = function () {
113 | i.replaceWith(_i);
114 | };
115 | }
116 | } catch {}
117 | });
118 | [
119 | ...postDiv.querySelectorAll(
120 | "a.extiw[title^='moe:'], a.extiw[title^='zhmoe:']"
121 | ),
122 | ].forEach(a => {
123 | a.classList.remove("extiw");
124 | api.get({
125 | action: "query",
126 | format: "json",
127 | titles: decodeURI(a.pathname.slice(1)),
128 | utf8: 1,
129 | formatversion: 2,
130 | }).done(b => {
131 | if (b.query.pages[0].missing) {
132 | a.href = `/index.php?title=${a.pathname.slice(
133 | 1
134 | )}&action=edit&redlink=1`;
135 | a.title += wgULS(" (页面不存在)", "(頁面不存在)");
136 | a.classList += "new";
137 | } else if (
138 | b.query.pages[0].pageid === mw.config.get("wgArticleId")
139 | ) {
140 | a.href = "";
141 | a.title = "";
142 | a.classList += "mw-selflink selflink";
143 | } else {
144 | a.href = a.pathname;
145 | a.title = a.title.replace(/moe:|zhmoe:/, "");
146 | }
147 | });
148 | });
149 | [...postDiv.getElementsByTagName("script")].forEach(s => {
150 | const _s = document.createElement("script");
151 | _s.innerHTML = s.innerHTML;
152 | [...s.attributes].forEach(a => {
153 | _s.setAttribute(a.name, a.value);
154 | });
155 | s.parentNode.replaceChild(_s, s);
156 | });
157 | return postDiv;
158 | };
159 | mw.loader.using(["moment"]).done(() => {
160 | const commentCSS = document.createElement("style");
161 | commentCSS.innerHTML =
162 | "#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}";
163 | document.head.appendChild(commentCSS);
164 | const containerTop = document.createElement("div");
165 | containerTop.className = "comment-container-top";
166 | const container = document.createElement("div");
167 | container.className = "comment-container";
168 | const postContent = document.createElement("div");
169 | postContent.id = "flowthread";
170 | postContent.className = "post-content";
171 | postContent.appendChild(containerTop);
172 | postContent.appendChild(container);
173 | document
174 | .getElementById(
175 | mw.config.get("skin") === "vector"
176 | ? "footer"
177 | : "moe-global-footer"
178 | )
179 | .appendChild(postContent);
180 | fetch(
181 | `https://moegirl.uk/api.php?${new URLSearchParams({
182 | action: "query",
183 | format: "json",
184 | prop: "pageprops",
185 | titles: mw.config.get("wgPageName"),
186 | utf8: 1,
187 | formatversion: 2,
188 | origin: "*",
189 | })}`
190 | )
191 | .then(a => a.json())
192 | .then(a => {
193 | if (a.query.pages[0].missing) return;
194 | (function getComment(offset) {
195 | fetch(
196 | `https://moegirl.uk/api.php?${new URLSearchParams({
197 | action: "flowthread",
198 | format: "json",
199 | type: "list",
200 | pageid: a.query.pages[0].pageid,
201 | limit: 15,
202 | offset,
203 | utf8: 1,
204 | formatversion: 2,
205 | origin: "*",
206 | })}`
207 | )
208 | .then(b => b.json())
209 | .then(b => {
210 | if (b.flowthread.popular.length) {
211 | document.body.getElementsByClassName(
212 | "comment-container-top"
213 | )[0].innerHTML = "热门评论
";
214 | for (const post of b.flowthread.popular) {
215 | const _post = generatePost(post);
216 | _post.classList.add("comment-popular");
217 | document.body
218 | .getElementsByClassName(
219 | "comment-container-top"
220 | )[0]
221 | .appendChild(_post);
222 | }
223 | }
224 | for (const post of b.flowthread.posts) {
225 | const _post = generatePost(post);
226 | _post.id = `comment-${post.id}`;
227 | if (post.parentid) {
228 | document
229 | .getElementById(
230 | `comment-${post.parentid}`
231 | )
232 | .appendChild(_post);
233 | } else {
234 | document
235 | .getElementsByClassName(
236 | "comment-container"
237 | )[0]
238 | .appendChild(_post);
239 | }
240 | }
241 | if (b.flowthread.count > offset + 15) {
242 | new IntersectionObserver(
243 | (entries, observer) => {
244 | entries.forEach(entry => {
245 | if (entry.isIntersecting) {
246 | getComment(offset + 15);
247 | observer.unobserve(
248 | entry.target
249 | );
250 | }
251 | });
252 | }
253 | ).observe(
254 | document.querySelector(
255 | ".comment-container > div.comment-thread:last-of-type"
256 | )
257 | );
258 | }
259 | });
260 | })(0);
261 | })
262 | .catch(() => {
263 | fetch(
264 | "https://testingcf.jsdelivr.net/gh/gui-ying233/JustMoeComments/flowthread.json"
265 | )
266 | .then(a => a.json())
267 | .then(a => {
268 | let f = 0;
269 | for (const t of a) {
270 | if (
271 | t.title !==
272 | mw.config.get("wgPageName").replace("_", " ")
273 | )
274 | continue;
275 | a = t.posts;
276 | f = 1;
277 | break;
278 | }
279 | if (!f) return;
280 | for (const b of a) {
281 | if (+b.status) continue;
282 | const _post = generatePost({
283 | username: b.username,
284 | text: b.text,
285 | timestamp: "",
286 | });
287 | _post.id = `comment-${b.id}`;
288 | (b.parentid
289 | ? document.getElementById(
290 | `comment-${b.parentid}`
291 | )
292 | : document.getElementsByClassName(
293 | "comment-container"
294 | )[0]
295 | ).appendChild(_post);
296 | }
297 | });
298 | });
299 | if (mw.config.get("skin") !== "moeskin") return;
300 | setTimeout(() => {
301 | if (
302 | ![
303 | (mw.config.get("wgPageName"),
304 | mw.config.get("wgPageName").replace(/^(.+)\(.+?\)$/, "$1"),
305 | document.getElementById("firstHeading").innerText),
306 | ].includes(
307 | document.body.getElementsByClassName("artwork-title")[0]
308 | ?.innerText
309 | )
310 | )
311 | return;
312 | document.body.getElementsByClassName("comment-item").forEach(c => {
313 | if (
314 | !c.getElementsByClassName("comment-title")[0].innerText &&
315 | !c.getElementsByClassName("comment-content")[0].innerText
316 | )
317 | return;
318 | const post = {
319 | like: +c
320 | .getElementsByClassName("n-button-group")[0]
321 | .innerText.split("\n")[0],
322 | username:
323 | c.getElementsByClassName("comment-author")[0].innerText,
324 | text:
325 | c.getElementsByClassName("comment-title")[0].innerText +
326 | (c.getElementsByClassName("comment-title")[0]
327 | .innerText &&
328 | c.getElementsByClassName("comment-content")[0].innerText
329 | ? document.createElement("br").outerHTML
330 | : "") +
331 | c.getElementsByClassName("comment-content")[0]
332 | .innerText,
333 | timestamp:
334 | c.getElementsByClassName("comment-time")[0].innerText,
335 | };
336 | document
337 | .getElementsByClassName("comment-container")[0]
338 | .appendChild(generatePost(post));
339 | });
340 | }, 5000);
341 | });
342 | })();
343 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "justmoecomments",
3 | "version": "2.15.1",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "justmoecomments",
9 | "version": "2.15.1",
10 | "license": "MIT"
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "justmoecomments",
3 | "version": "2.15.1",
4 | "description": "萌娘百科看Lih的镜像站的评论,同时集成了作品讨论的评论",
5 | "scripts": {
6 | "export": "node scripts/导出FlowThread评论"
7 | },
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/gui-ying233/JustMoeComments.git"
11 | },
12 | "keywords": [
13 | "tampermonkey",
14 | "comments",
15 | "moegirlpedia"
16 | ],
17 | "author": "鬼影233",
18 | "license": "MIT",
19 | "bugs": {
20 | "url": "https://github.com/gui-ying233/JustMoeComments/issues"
21 | },
22 | "homepage": "https://github.com/gui-ying233/JustMoeComments"
23 | }
24 |
--------------------------------------------------------------------------------
/scripts/导出FlowThread评论.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | const {
3 | env: { MOEGIRL_UK_MGPUSERID, MOEGIRL_UK_MGPTOKEN },
4 | } = require("process");
5 | const { writeFileSync } = require("fs");
6 | fetch(
7 | "https://moegirl.uk/Special:%E5%AF%BC%E5%87%BAFlowThread%E8%AF%84%E8%AE%BA",
8 | {
9 | method: "POST",
10 | headers: {
11 | cookie: `mgpUserID=${MOEGIRL_UK_MGPUSERID}; mgpToken=${MOEGIRL_UK_MGPTOKEN}`,
12 | },
13 | }
14 | )
15 | .then(res => res.json())
16 | .then(d =>
17 | writeFileSync(
18 | "flowthread.json",
19 | JSON.stringify(
20 | d.map(({ title, posts }) => {
21 | return {
22 | title,
23 | posts: posts.filter(({ status }) => !status),
24 | };
25 | })
26 | )
27 | )
28 | );
29 |
--------------------------------------------------------------------------------