koishi-plugin-cfmrmod 1.1.8 → 1.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mcmod/cards/comment-card.js +266 -21
- package/dist/mcmod/cards.js +3 -1
- package/dist/mcmod/plugin.js +95 -0
- package/dist/mcmod/search.js +6 -0
- package/package.json +1 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.fetchMcmodCommentThread = fetchMcmodCommentThread;
|
|
4
|
+
exports.fetchMcmodCommentList = fetchMcmodCommentList;
|
|
5
|
+
exports.drawMcmodCommentList = drawMcmodCommentList;
|
|
4
6
|
exports.drawMcmodCommentThread = drawMcmodCommentThread;
|
|
5
7
|
const cheerio = require('cheerio');
|
|
6
8
|
const constants_1 = require("../constants");
|
|
@@ -10,17 +12,74 @@ const utils_1 = require("../utils");
|
|
|
10
12
|
const COMMENT_ROW_URL = `${constants_1.BASE_URL}/frame/comment/CommentRow/`;
|
|
11
13
|
const COMMENT_REPLY_URL = `${constants_1.BASE_URL}/frame/comment/CommentReply/`;
|
|
12
14
|
const MCMOD_REPLY_API_PAGE_SIZE = 5;
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
function readPageTitle(html, context) {
|
|
16
|
+
const $ = cheerio.load(html);
|
|
17
|
+
return ((0, utils_1.cleanText)($('meta[property="og:title"]').attr('content') || '') ||
|
|
18
|
+
(0, utils_1.cleanText)($('title').text()).replace(/\s*-\s*MC百科.*$/, '') ||
|
|
19
|
+
context.container);
|
|
20
|
+
}
|
|
21
|
+
function inferCommentContextFromUrl(url) {
|
|
22
|
+
let parsed;
|
|
23
|
+
try {
|
|
24
|
+
parsed = new URL(url);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
22
28
|
}
|
|
23
|
-
|
|
29
|
+
const path = parsed.pathname || '';
|
|
30
|
+
const simpleMatch = path.match(/^\/(class|modpack|post|author)\/(\d+)(?:\.html)?\/?$/i);
|
|
31
|
+
if (simpleMatch)
|
|
32
|
+
return { type: simpleMatch[1].toLowerCase(), container: simpleMatch[2] };
|
|
33
|
+
if (/^\/item\//i.test(path)) {
|
|
34
|
+
const itemMatch = path.match(/^\/item\/([^/?#]+)(?:\.html)?\/?$/i);
|
|
35
|
+
if (itemMatch)
|
|
36
|
+
return { type: 'item', container: itemMatch[1] };
|
|
37
|
+
}
|
|
38
|
+
if (/^center\.mcmod\.cn$/i.test(parsed.hostname)) {
|
|
39
|
+
const centerMatch = path.match(/^\/(\d+)\/?$/);
|
|
40
|
+
if (centerMatch)
|
|
41
|
+
return { type: 'center', container: centerMatch[1] };
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
function extractCommentContext(url, html) {
|
|
46
|
+
var _a, _b;
|
|
47
|
+
const text = String(html || '');
|
|
48
|
+
const type = ((_a = text.match(/comment_type\s*=\s*['"]([^'"]+)['"]/)) === null || _a === void 0 ? void 0 : _a[1]) || '';
|
|
49
|
+
const container = ((_b = text.match(/comment_container\s*=\s*['"]([^'"]+)['"]/)) === null || _b === void 0 ? void 0 : _b[1]) || '';
|
|
50
|
+
if (type && container)
|
|
51
|
+
return { type, container };
|
|
52
|
+
if (!text.includes('common-comment-block'))
|
|
53
|
+
return null;
|
|
54
|
+
return inferCommentContextFromUrl(url);
|
|
55
|
+
}
|
|
56
|
+
function buildCommentPageCandidates(pageUrl) {
|
|
57
|
+
const candidates = [pageUrl];
|
|
58
|
+
try {
|
|
59
|
+
const parsed = new URL(pageUrl);
|
|
60
|
+
const classMatch = parsed.pathname.match(/^\/class\/(\d+)(?:\.html)?\/?$/i);
|
|
61
|
+
if (classMatch)
|
|
62
|
+
candidates.push(`${constants_1.BASE_URL}/modpack/${classMatch[1]}.html`);
|
|
63
|
+
}
|
|
64
|
+
catch { }
|
|
65
|
+
return Array.from(new Set(candidates));
|
|
66
|
+
}
|
|
67
|
+
async function fetchCommentDocument(url, timeout) {
|
|
68
|
+
const firstUrl = (0, utils_1.fixUrl)(url);
|
|
69
|
+
if (!firstUrl)
|
|
70
|
+
throw new Error('MCMod 页面地址不能为空。');
|
|
71
|
+
let lastHtml = '';
|
|
72
|
+
for (const pageUrl of buildCommentPageCandidates(firstUrl)) {
|
|
73
|
+
const html = await (0, http_1.fetchMcmodText)(pageUrl, { headers: (0, http_1.getHeaders)(pageUrl) }, timeout);
|
|
74
|
+
lastHtml = html;
|
|
75
|
+
const context = extractCommentContext(pageUrl, html);
|
|
76
|
+
if (context)
|
|
77
|
+
return { pageUrl, html, context };
|
|
78
|
+
}
|
|
79
|
+
const fallback = extractCommentContext(firstUrl, lastHtml);
|
|
80
|
+
if (fallback)
|
|
81
|
+
return { pageUrl: firstUrl, html: lastHtml, context: fallback };
|
|
82
|
+
throw new Error('无法从页面解析评论上下文。');
|
|
24
83
|
}
|
|
25
84
|
function parseTarget(input) {
|
|
26
85
|
var _a, _b, _c;
|
|
@@ -69,11 +128,18 @@ function normalizeFloor(value) {
|
|
|
69
128
|
const match = String(value || '').match(/(\d+)/);
|
|
70
129
|
return match ? Number(match[1]) : null;
|
|
71
130
|
}
|
|
131
|
+
function cleanCommentFloor(value) {
|
|
132
|
+
const raw = String(value || '');
|
|
133
|
+
if (!raw)
|
|
134
|
+
return '';
|
|
135
|
+
const text = (0, utils_1.cleanText)(cheerio.load(`<div>${raw}</div>`)('div').text() || raw);
|
|
136
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
137
|
+
}
|
|
72
138
|
function rowToComment(row) {
|
|
73
139
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
74
140
|
return {
|
|
75
141
|
id: String((row === null || row === void 0 ? void 0 : row.id) || ''),
|
|
76
|
-
floor:
|
|
142
|
+
floor: cleanCommentFloor(row === null || row === void 0 ? void 0 : row.floor),
|
|
77
143
|
floorNo: normalizeFloor(row === null || row === void 0 ? void 0 : row.floor),
|
|
78
144
|
user: {
|
|
79
145
|
id: String(((_a = row === null || row === void 0 ? void 0 : row.user) === null || _a === void 0 ? void 0 : _a.id) || '0'),
|
|
@@ -161,20 +227,15 @@ async function fetchReplies(commentId, pageUrl, page, pageSize, timeout) {
|
|
|
161
227
|
};
|
|
162
228
|
}
|
|
163
229
|
async function fetchMcmodCommentThread(url, targetInput, page = 1, pageSize = 5, timeout = 15000) {
|
|
164
|
-
const pageUrl = (
|
|
165
|
-
if (!pageUrl)
|
|
166
|
-
throw new Error('MCMod 页面地址不能为空。');
|
|
167
|
-
const html = await (0, http_1.fetchMcmodText)(pageUrl, { headers: (0, http_1.getHeaders)(pageUrl) }, timeout);
|
|
168
|
-
const context = parseCommentContext(pageUrl, html);
|
|
230
|
+
const { pageUrl, html, context } = await fetchCommentDocument(url, timeout);
|
|
169
231
|
const target = parseTarget(targetInput);
|
|
170
232
|
const found = await findComment(context, pageUrl, target, timeout);
|
|
171
|
-
const replies =
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
context.container;
|
|
233
|
+
const replies = found.comment.replyCount > 0
|
|
234
|
+
? await fetchReplies(found.comment.id, pageUrl, Math.max(1, Number(page) || 1), pageSize, timeout)
|
|
235
|
+
: { replies: [], totalRows: 0, totalPages: 1 };
|
|
175
236
|
return {
|
|
176
237
|
pageUrl,
|
|
177
|
-
title,
|
|
238
|
+
title: readPageTitle(html, context),
|
|
178
239
|
context,
|
|
179
240
|
target,
|
|
180
241
|
main: found.comment,
|
|
@@ -187,6 +248,55 @@ async function fetchMcmodCommentThread(url, targetInput, page = 1, pageSize = 5,
|
|
|
187
248
|
replies: replies.replies,
|
|
188
249
|
};
|
|
189
250
|
}
|
|
251
|
+
async function fetchMcmodCommentList(url, page = 1, pageSize = 5, timeout = 15000) {
|
|
252
|
+
const listPage = Math.max(1, Number(page) || 1);
|
|
253
|
+
const listPageSize = Math.max(1, Math.floor(Number(pageSize) || 5));
|
|
254
|
+
const { pageUrl, html, context } = await fetchCommentDocument(url, timeout);
|
|
255
|
+
const firstData = await fetchCommentRows(context, pageUrl, 1, timeout);
|
|
256
|
+
const firstRows = Array.isArray(firstData.row) ? firstData.row : [];
|
|
257
|
+
const pageInfo = firstData.page || {};
|
|
258
|
+
const apiTotalRows = Number(pageInfo.total_row || 0) || 0;
|
|
259
|
+
const apiTotalPages = Number(pageInfo.total_page || pageInfo.end || (firstRows.length ? 1 : 0)) || (firstRows.length ? 1 : 0);
|
|
260
|
+
const apiPageSize = apiTotalRows && apiTotalPages
|
|
261
|
+
? Math.max(1, Math.ceil(apiTotalRows / apiTotalPages))
|
|
262
|
+
: Math.max(1, firstRows.length || listPageSize);
|
|
263
|
+
const pinnedRows = apiTotalRows && firstRows.length > apiPageSize ? firstRows.length - apiPageSize : 0;
|
|
264
|
+
const totalRows = apiTotalRows ? apiTotalRows + pinnedRows : firstRows.length;
|
|
265
|
+
const totalPages = Math.max(1, Math.ceil(totalRows / listPageSize));
|
|
266
|
+
const start = (listPage - 1) * listPageSize;
|
|
267
|
+
const comments = [];
|
|
268
|
+
if (start < totalRows) {
|
|
269
|
+
let collected = [];
|
|
270
|
+
if (start < firstRows.length) {
|
|
271
|
+
collected = firstRows.slice(start, Math.min(firstRows.length, start + listPageSize));
|
|
272
|
+
}
|
|
273
|
+
let globalIndex = start + collected.length;
|
|
274
|
+
let apiPage = Math.floor(Math.max(0, globalIndex - firstRows.length) / apiPageSize) + 2;
|
|
275
|
+
let offset = Math.max(0, globalIndex - firstRows.length) % apiPageSize;
|
|
276
|
+
while (collected.length < listPageSize && apiPage <= apiTotalPages) {
|
|
277
|
+
const data = await fetchCommentRows(context, pageUrl, apiPage, timeout);
|
|
278
|
+
const rows = Array.isArray(data.row) ? data.row : [];
|
|
279
|
+
if (!rows.length)
|
|
280
|
+
break;
|
|
281
|
+
const need = listPageSize - collected.length;
|
|
282
|
+
collected = collected.concat(rows.slice(offset, offset + need));
|
|
283
|
+
globalIndex = start + collected.length;
|
|
284
|
+
apiPage += 1;
|
|
285
|
+
offset = 0;
|
|
286
|
+
}
|
|
287
|
+
collected.forEach(row => comments.push(rowToComment(row)));
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
pageUrl,
|
|
291
|
+
title: readPageTitle(html, context),
|
|
292
|
+
context,
|
|
293
|
+
page: listPage,
|
|
294
|
+
pageSize: listPageSize,
|
|
295
|
+
totalRows,
|
|
296
|
+
totalPages,
|
|
297
|
+
comments,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
190
300
|
function parseContentNodes(html, includeImages) {
|
|
191
301
|
var _a, _b;
|
|
192
302
|
const $ = cheerio.load(`<div class="root">${html || ''}</div>`, { decodeEntities: true });
|
|
@@ -225,6 +335,8 @@ function parseContentNodes(html, includeImages) {
|
|
|
225
335
|
buffer += alt;
|
|
226
336
|
else if (isEmotion)
|
|
227
337
|
buffer += ' [表情] ';
|
|
338
|
+
else
|
|
339
|
+
buffer += ' [图片] ';
|
|
228
340
|
return;
|
|
229
341
|
}
|
|
230
342
|
flush();
|
|
@@ -358,6 +470,139 @@ function badge(ctx, text, x, y, font, color = '#1f7a9d') {
|
|
|
358
470
|
ctx.fillText(text, x + 7, y + 4);
|
|
359
471
|
return w;
|
|
360
472
|
}
|
|
473
|
+
function measureBadge(ctx, text, font) {
|
|
474
|
+
ctx.font = `700 12px "${font}"`;
|
|
475
|
+
return ctx.measureText(text).width + 14;
|
|
476
|
+
}
|
|
477
|
+
function commentPreviewText(html, maxChars = 180) {
|
|
478
|
+
const nodes = parseContentNodes(html, false);
|
|
479
|
+
const text = nodes
|
|
480
|
+
.map(node => node.type === 'text' ? node.text : (node.alt ? `[图片: ${node.alt}]` : '[图片]'))
|
|
481
|
+
.join('\n')
|
|
482
|
+
.replace(/[ \t\f\v]+/g, ' ')
|
|
483
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
484
|
+
.trim();
|
|
485
|
+
if (!text)
|
|
486
|
+
return '(无内容)';
|
|
487
|
+
return text.length > maxChars ? `${text.slice(0, maxChars).trim()}...` : text;
|
|
488
|
+
}
|
|
489
|
+
async function drawMcmodCommentList(list, options = {}) {
|
|
490
|
+
const font = rendering_1.GLOBAL_FONT_FAMILY;
|
|
491
|
+
const width = 900;
|
|
492
|
+
const margin = 28;
|
|
493
|
+
const contentW = width - margin * 2;
|
|
494
|
+
const bodyW = contentW - 124;
|
|
495
|
+
const dummyCanvas = (0, rendering_1.createCanvas)(100, 100);
|
|
496
|
+
const dummy = dummyCanvas.getContext('2d');
|
|
497
|
+
for (const comment of list.comments) {
|
|
498
|
+
comment._avatar = await loadAvatar(comment.user.avatar, list.pageUrl);
|
|
499
|
+
comment._preview = commentPreviewText(comment.content, options.previewChars || 180);
|
|
500
|
+
}
|
|
501
|
+
const headerH = 112;
|
|
502
|
+
const listHeaderH = 58;
|
|
503
|
+
const itemHeights = list.comments.map(comment => {
|
|
504
|
+
dummy.font = `15px "${font}"`;
|
|
505
|
+
const previewH = (0, rendering_1.wrapText)(dummy, comment._preview, 0, 0, bodyW, 23, 4, false);
|
|
506
|
+
return Math.max(118, 84 + previewH);
|
|
507
|
+
});
|
|
508
|
+
const emptyH = list.comments.length ? 0 : 68;
|
|
509
|
+
const itemsH = itemHeights.reduce((sum, h) => sum + h + 12, 0);
|
|
510
|
+
const totalH = margin + headerH + 18 + listHeaderH + itemsH + emptyH + 42;
|
|
511
|
+
const canvas = (0, rendering_1.createCanvas)(width, totalH);
|
|
512
|
+
const ctx = canvas.getContext('2d');
|
|
513
|
+
ctx.textBaseline = 'top';
|
|
514
|
+
ctx.fillStyle = '#eef4f7';
|
|
515
|
+
ctx.fillRect(0, 0, width, totalH);
|
|
516
|
+
ctx.fillStyle = '#0f5d7a';
|
|
517
|
+
(0, rendering_1.roundRect)(ctx, margin, margin, contentW, headerH, 14);
|
|
518
|
+
ctx.fill();
|
|
519
|
+
ctx.fillStyle = '#2f9ab7';
|
|
520
|
+
(0, rendering_1.roundRect)(ctx, margin + 18, margin + 18, 78, 78, 12);
|
|
521
|
+
ctx.fill();
|
|
522
|
+
ctx.fillStyle = '#ffffff';
|
|
523
|
+
ctx.font = `800 30px "${font}"`;
|
|
524
|
+
ctx.textAlign = 'center';
|
|
525
|
+
ctx.fillText('MC', margin + 57, margin + 32);
|
|
526
|
+
ctx.font = `700 13px "${font}"`;
|
|
527
|
+
ctx.fillText('MOD', margin + 57, margin + 66);
|
|
528
|
+
ctx.textAlign = 'left';
|
|
529
|
+
ctx.fillStyle = '#fff';
|
|
530
|
+
ctx.font = `800 28px "${font}"`;
|
|
531
|
+
(0, rendering_1.wrapText)(ctx, list.title || 'MCMod 评论列表', margin + 116, margin + 20, contentW - 146, 34, 2, true);
|
|
532
|
+
ctx.font = `600 14px "${font}"`;
|
|
533
|
+
ctx.fillStyle = 'rgba(255,255,255,0.84)';
|
|
534
|
+
ctx.fillText(`mcmod.cn / ${list.context.type}:${list.context.container}`, margin + 116, margin + 82);
|
|
535
|
+
let y = margin + headerH + 18;
|
|
536
|
+
ctx.fillStyle = '#153743';
|
|
537
|
+
ctx.font = `800 20px "${font}"`;
|
|
538
|
+
ctx.fillText(`主评论 ${list.page}/${list.totalPages}`, margin + 4, y + 4);
|
|
539
|
+
ctx.fillStyle = '#607d8b';
|
|
540
|
+
ctx.font = `13px "${font}"`;
|
|
541
|
+
ctx.fillText(`每页 ${list.pageSize} 条,共 ${list.totalRows} 条;输入 n/p 翻页,q 退出`, margin + 4, y + 32);
|
|
542
|
+
y += listHeaderH;
|
|
543
|
+
if (!list.comments.length) {
|
|
544
|
+
ctx.fillStyle = '#ffffff';
|
|
545
|
+
(0, rendering_1.roundRect)(ctx, margin, y, contentW, 56, 10);
|
|
546
|
+
ctx.fill();
|
|
547
|
+
ctx.strokeStyle = '#dceaf0';
|
|
548
|
+
ctx.lineWidth = 1;
|
|
549
|
+
(0, rendering_1.roundRect)(ctx, margin, y, contentW, 56, 10);
|
|
550
|
+
ctx.stroke();
|
|
551
|
+
ctx.fillStyle = '#78909c';
|
|
552
|
+
ctx.font = `14px "${font}"`;
|
|
553
|
+
ctx.fillText('本页没有评论。', margin + 18, y + 18);
|
|
554
|
+
y += emptyH;
|
|
555
|
+
}
|
|
556
|
+
for (let i = 0; i < list.comments.length; i++) {
|
|
557
|
+
const comment = list.comments[i];
|
|
558
|
+
const cardH = itemHeights[i];
|
|
559
|
+
ctx.fillStyle = '#ffffff';
|
|
560
|
+
(0, rendering_1.roundRect)(ctx, margin, y, contentW, cardH, 10);
|
|
561
|
+
ctx.fill();
|
|
562
|
+
ctx.strokeStyle = '#dceaf0';
|
|
563
|
+
ctx.lineWidth = 1;
|
|
564
|
+
(0, rendering_1.roundRect)(ctx, margin, y, contentW, cardH, 10);
|
|
565
|
+
ctx.stroke();
|
|
566
|
+
drawAvatar(ctx, comment._avatar, margin + 22, y + 22, 58, comment.user.name, font);
|
|
567
|
+
let tx = margin + 98;
|
|
568
|
+
let ty = y + 18;
|
|
569
|
+
let right = width - margin - 18;
|
|
570
|
+
const replyText = `回复 ${comment.replyCount}`;
|
|
571
|
+
if (comment.replyCount > 0) {
|
|
572
|
+
const w = measureBadge(ctx, replyText, font);
|
|
573
|
+
badge(ctx, replyText, right - w, ty + 1, font, '#607d8b');
|
|
574
|
+
right -= w + 8;
|
|
575
|
+
}
|
|
576
|
+
if (comment.user.level !== undefined) {
|
|
577
|
+
const levelText = `Lv.${comment.user.level}`;
|
|
578
|
+
const w = measureBadge(ctx, levelText, font);
|
|
579
|
+
badge(ctx, levelText, right - w, ty + 1, font, '#1f7a9d');
|
|
580
|
+
right -= w + 8;
|
|
581
|
+
}
|
|
582
|
+
if (comment.floor) {
|
|
583
|
+
const w = measureBadge(ctx, comment.floor, font);
|
|
584
|
+
badge(ctx, comment.floor, right - w, ty + 1, font, '#d2691e');
|
|
585
|
+
right -= w + 8;
|
|
586
|
+
}
|
|
587
|
+
ctx.fillStyle = '#102a35';
|
|
588
|
+
ctx.font = `800 18px "${font}"`;
|
|
589
|
+
await (0, rendering_1.drawTextWithTwemoji)(ctx, comment.user.name, tx, ty, Math.max(80, right - tx), 23, 1, true);
|
|
590
|
+
ty += 28;
|
|
591
|
+
ctx.fillStyle = '#78909c';
|
|
592
|
+
ctx.font = `13px "${font}"`;
|
|
593
|
+
ctx.fillText(`${comment.time.source || '--'} ${comment.time.range || ''} ID:${comment.id}`, tx, ty);
|
|
594
|
+
ty += 30;
|
|
595
|
+
ctx.fillStyle = '#263238';
|
|
596
|
+
ctx.font = `15px "${font}"`;
|
|
597
|
+
await (0, rendering_1.drawTextWithTwemoji)(ctx, comment._preview, tx, ty, bodyW, 23, 4, true);
|
|
598
|
+
y += cardH + 12;
|
|
599
|
+
}
|
|
600
|
+
ctx.fillStyle = '#78909c';
|
|
601
|
+
ctx.font = `12px "${font}"`;
|
|
602
|
+
ctx.textAlign = 'center';
|
|
603
|
+
ctx.fillText('Generated by Koishi | Powered by MCMod', width / 2, totalH - 24);
|
|
604
|
+
return await canvas.encode('png');
|
|
605
|
+
}
|
|
361
606
|
async function drawMcmodCommentThread(thread, options = {}) {
|
|
362
607
|
var _a;
|
|
363
608
|
const includeImages = options.includeImages !== false;
|
package/dist/mcmod/cards.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.fetchMcmodCommentThread = exports.drawMcmodCommentThread = exports.createInfoCard = exports.drawCenterCardImpl = exports.drawCenterCard = exports.drawAuthorCard = exports.drawTutorialCard = exports.drawModCard = void 0;
|
|
3
|
+
exports.fetchMcmodCommentThread = exports.fetchMcmodCommentList = exports.drawMcmodCommentThread = exports.drawMcmodCommentList = exports.createInfoCard = exports.drawCenterCardImpl = exports.drawCenterCard = exports.drawAuthorCard = exports.drawTutorialCard = exports.drawModCard = void 0;
|
|
4
4
|
var mod_card_1 = require("./cards/mod-card");
|
|
5
5
|
Object.defineProperty(exports, "drawModCard", { enumerable: true, get: function () { return mod_card_1.drawModCard; } });
|
|
6
6
|
var tutorial_card_1 = require("./cards/tutorial-card");
|
|
@@ -13,5 +13,7 @@ Object.defineProperty(exports, "drawCenterCardImpl", { enumerable: true, get: fu
|
|
|
13
13
|
var info_card_1 = require("./cards/info-card");
|
|
14
14
|
Object.defineProperty(exports, "createInfoCard", { enumerable: true, get: function () { return info_card_1.createInfoCard; } });
|
|
15
15
|
var comment_card_1 = require("./cards/comment-card");
|
|
16
|
+
Object.defineProperty(exports, "drawMcmodCommentList", { enumerable: true, get: function () { return comment_card_1.drawMcmodCommentList; } });
|
|
16
17
|
Object.defineProperty(exports, "drawMcmodCommentThread", { enumerable: true, get: function () { return comment_card_1.drawMcmodCommentThread; } });
|
|
18
|
+
Object.defineProperty(exports, "fetchMcmodCommentList", { enumerable: true, get: function () { return comment_card_1.fetchMcmodCommentList; } });
|
|
17
19
|
Object.defineProperty(exports, "fetchMcmodCommentThread", { enumerable: true, get: function () { return comment_card_1.fetchMcmodCommentThread; } });
|
package/dist/mcmod/plugin.js
CHANGED
|
@@ -12,6 +12,7 @@ const utils_1 = require("./utils");
|
|
|
12
12
|
// ================= 状态管理和常量 =================
|
|
13
13
|
const searchStates = new Map();
|
|
14
14
|
const commentStates = new Map();
|
|
15
|
+
const commentListStates = new Map();
|
|
15
16
|
// ================= Koishi =================
|
|
16
17
|
exports.name = 'mcmod-search';
|
|
17
18
|
exports.Config = Schema.object({
|
|
@@ -67,6 +68,12 @@ function apply(ctx, config) {
|
|
|
67
68
|
clearTimeout(state.timer);
|
|
68
69
|
commentStates.delete(cid);
|
|
69
70
|
}
|
|
71
|
+
function clearCommentListState(cid) {
|
|
72
|
+
const state = commentListStates.get(cid);
|
|
73
|
+
if (state && state.timer)
|
|
74
|
+
clearTimeout(state.timer);
|
|
75
|
+
commentListStates.delete(cid);
|
|
76
|
+
}
|
|
70
77
|
// --- 排队系统 ---
|
|
71
78
|
const queue = [];
|
|
72
79
|
let isProcessing = false;
|
|
@@ -166,6 +173,29 @@ function apply(ctx, config) {
|
|
|
166
173
|
commentStates.set(session.cid, nextState);
|
|
167
174
|
return nextState;
|
|
168
175
|
}
|
|
176
|
+
async function renderCommentListPage(session, state, nextPage) {
|
|
177
|
+
const page = Math.max(1, Number(nextPage) || 1);
|
|
178
|
+
const list = await (0, cards_1.fetchMcmodCommentList)(state.url, page, state.pageSize, config.requestTimeout || 15000);
|
|
179
|
+
if (page > list.totalPages) {
|
|
180
|
+
await session.send('没有更多页面了。');
|
|
181
|
+
return state;
|
|
182
|
+
}
|
|
183
|
+
const img = await (0, cards_1.drawMcmodCommentList)(list);
|
|
184
|
+
await tryWithdraw(session, state.messageIds);
|
|
185
|
+
const sentMessageIds = await session.send(h.image(await (0, utils_1.toImageSrc)(img)));
|
|
186
|
+
const timer = setTimeout(() => commentListStates.delete(session.cid), config.timeouts || constants_1.TIMEOUT_MS);
|
|
187
|
+
if (state.timer)
|
|
188
|
+
clearTimeout(state.timer);
|
|
189
|
+
const nextState = {
|
|
190
|
+
...state,
|
|
191
|
+
page: list.page,
|
|
192
|
+
totalPages: list.totalPages,
|
|
193
|
+
messageIds: normalizeMessageIds(sentMessageIds),
|
|
194
|
+
timer,
|
|
195
|
+
};
|
|
196
|
+
commentListStates.set(session.cid, nextState);
|
|
197
|
+
return nextState;
|
|
198
|
+
}
|
|
169
199
|
// --- 注册指令 ---
|
|
170
200
|
const prefix = ((_a = config === null || config === void 0 ? void 0 : config.prefixes) === null || _a === void 0 ? void 0 : _a.cnmc) || 'cnmc';
|
|
171
201
|
const commandTypes = ['mod', 'data', 'pack', 'tutorial', 'author', 'user'];
|
|
@@ -173,6 +203,7 @@ function apply(ctx, config) {
|
|
|
173
203
|
`${prefix} <关键词> | 默认搜索 Mod`,
|
|
174
204
|
`${prefix}.mod/.data/.pack/.tutorial/.author/.user <关键词>`,
|
|
175
205
|
`${prefix}.comment <url> <楼层|id:评论ID> [page] | 渲染评论与子评论`,
|
|
206
|
+
`${prefix}.comments <url> [page] | 渲染页面主评论列表`,
|
|
176
207
|
'列表交互:输入序号查看,n 下一页,p 上一页,q 退出',
|
|
177
208
|
].join('\n'));
|
|
178
209
|
ctx.command(`${prefix}.comment <url:string> <target:string> [page:number]`, '渲染 MCMod 评论与子评论')
|
|
@@ -185,6 +216,7 @@ function apply(ctx, config) {
|
|
|
185
216
|
await (0, http_1.ensureValidCookie)();
|
|
186
217
|
clearState(session.cid);
|
|
187
218
|
clearCommentState(session.cid);
|
|
219
|
+
clearCommentListState(session.cid);
|
|
188
220
|
const state = {
|
|
189
221
|
url,
|
|
190
222
|
target,
|
|
@@ -202,6 +234,33 @@ function apply(ctx, config) {
|
|
|
202
234
|
}
|
|
203
235
|
});
|
|
204
236
|
});
|
|
237
|
+
ctx.command(`${prefix}.comments <url:string> [page:number]`, '渲染 MCMod 页面主评论列表')
|
|
238
|
+
.option('size', '-s, --size <size:number> 主评论单页数量')
|
|
239
|
+
.action(async ({ session, options }, url, page) => {
|
|
240
|
+
if (!url)
|
|
241
|
+
return `用法:${prefix}.comments <url> [page] [-s 数量]`;
|
|
242
|
+
enqueue(session, 'comment-list-render', async () => {
|
|
243
|
+
try {
|
|
244
|
+
await (0, http_1.ensureValidCookie)();
|
|
245
|
+
clearState(session.cid);
|
|
246
|
+
clearCommentState(session.cid);
|
|
247
|
+
clearCommentListState(session.cid);
|
|
248
|
+
const state = {
|
|
249
|
+
url,
|
|
250
|
+
pageSize: getCommentPageSize(options === null || options === void 0 ? void 0 : options.size),
|
|
251
|
+
page: Math.max(1, Number(page) || 1),
|
|
252
|
+
totalPages: 1,
|
|
253
|
+
messageIds: [],
|
|
254
|
+
timer: null,
|
|
255
|
+
};
|
|
256
|
+
await renderCommentListPage(session, state, state.page);
|
|
257
|
+
}
|
|
258
|
+
catch (e) {
|
|
259
|
+
logger.error(e);
|
|
260
|
+
await session.send(`评论列表渲染失败: ${e.message}`);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
});
|
|
205
264
|
commandTypes.forEach(type => {
|
|
206
265
|
ctx.command(`${prefix}.${type} <keyword:text>`)
|
|
207
266
|
.action(async ({ session }, keyword) => {
|
|
@@ -301,6 +360,42 @@ function apply(ctx, config) {
|
|
|
301
360
|
}
|
|
302
361
|
});
|
|
303
362
|
});
|
|
363
|
+
// --- 中间件 (处理评论列表翻页) ---
|
|
364
|
+
ctx.middleware(async (session, next) => {
|
|
365
|
+
const state = commentListStates.get(session.cid);
|
|
366
|
+
if (!state)
|
|
367
|
+
return next();
|
|
368
|
+
const input = session.content.trim().toLowerCase();
|
|
369
|
+
if (input === 'q' || input === '退出') {
|
|
370
|
+
clearCommentListState(session.cid);
|
|
371
|
+
await tryWithdraw(session, state.messageIds);
|
|
372
|
+
await session.send('已退出评论列表翻页。');
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
if (input === 'p' || input === 'n') {
|
|
376
|
+
enqueue(session, 'comment-list-page-turn', async () => {
|
|
377
|
+
const currentState = commentListStates.get(session.cid);
|
|
378
|
+
if (!currentState)
|
|
379
|
+
return;
|
|
380
|
+
const nextPage = input === 'n'
|
|
381
|
+
? Number(currentState.page || 1) + 1
|
|
382
|
+
: Number(currentState.page || 1) - 1;
|
|
383
|
+
if (nextPage < 1 || nextPage > Number(currentState.totalPages || 1)) {
|
|
384
|
+
await session.send('没有更多页面了。');
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
try {
|
|
388
|
+
await renderCommentListPage(session, currentState, nextPage);
|
|
389
|
+
}
|
|
390
|
+
catch (e) {
|
|
391
|
+
logger.error(e);
|
|
392
|
+
await session.send(`评论列表翻页失败: ${e.message}`);
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
return next();
|
|
398
|
+
});
|
|
304
399
|
// --- 中间件 (处理评论翻页) ---
|
|
305
400
|
ctx.middleware(async (session, next) => {
|
|
306
401
|
const state = commentStates.get(session.cid);
|
package/dist/mcmod/search.js
CHANGED
|
@@ -88,6 +88,12 @@ async function fetchSearchFallback(query, typeKey) {
|
|
|
88
88
|
summary = (0, utils_1.cleanText)($el.find('i').text());
|
|
89
89
|
link = `https://www.mcmod.cn/author/${id}.html`;
|
|
90
90
|
}
|
|
91
|
+
else if (typeKey === 'pack') {
|
|
92
|
+
const rawText = (0, utils_1.cleanText)($el.text());
|
|
93
|
+
title = rawText.replace(/^ID:\d+\s*/, '');
|
|
94
|
+
link = `https://www.mcmod.cn/modpack/${id}.html`;
|
|
95
|
+
summary = `ID: ${id}`;
|
|
96
|
+
}
|
|
91
97
|
else {
|
|
92
98
|
const rawText = (0, utils_1.cleanText)($el.text());
|
|
93
99
|
title = rawText.replace(/^ID:\d+\s*/, '');
|