koishi-plugin-cfmrmod 1.1.2 → 1.1.3

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.
@@ -0,0 +1,531 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.drawTutorialCard = drawTutorialCard;
4
+ const cheerio = require('cheerio');
5
+ const constants_1 = require("../constants");
6
+ const http_1 = require("../http");
7
+ const rendering_1 = require("../rendering");
8
+ const utils_1 = require("../utils");
9
+ // ================= 渲染:教程卡片 (macOS 风格) =================
10
+ async function drawTutorialCard(url) {
11
+ var _a;
12
+ const html = await (0, http_1.fetchMcmodText)(url, { headers: (0, http_1.getHeaders)(url) });
13
+ const $ = cheerio.load(html);
14
+ // --- 1. 核心数据抓取 ---
15
+ // 标题
16
+ const title = (0, utils_1.cleanText)($('h1, .post-title, .article-title, .postname h5').first().text()) || (0, utils_1.cleanText)($('title').text().split('-')[0]);
17
+ // 作者
18
+ let author = (0, utils_1.cleanText)($('.post-user-frame .post-user-name a').first().text());
19
+ if (!author)
20
+ author = (0, utils_1.cleanText)($('.post-user-name a').first().text());
21
+ if (!author)
22
+ author = (0, utils_1.cleanText)($('a[href*="/center/"]').first().text());
23
+ if (!author)
24
+ author = '未知作者';
25
+ // 头像
26
+ let authorAvatar = (0, utils_1.fixUrl)($('.post-user-frame .post-user-avatar img').attr('src'));
27
+ if (!authorAvatar)
28
+ authorAvatar = (0, utils_1.fixUrl)($('.post-user-avatar img').attr('src'));
29
+ // 浏览量/日期
30
+ let views = '0';
31
+ let date = '';
32
+ $('.common-rowlist-2 li').each((i, el) => {
33
+ const text = $(el).text();
34
+ if (text.includes('浏览量'))
35
+ views = text.replace(/[^0-9]/g, '') || '0';
36
+ if (text.includes('创建日期')) {
37
+ const fullDate = $(el).attr('data-original-title');
38
+ date = fullDate ? fullDate.split(' ')[0] : text.replace('创建日期:', '').trim();
39
+ }
40
+ });
41
+ // 互动数据
42
+ function getSocialNum(className) {
43
+ let result = '0';
44
+ const selectors = [
45
+ `.common-fuc-group[data-category="post"] li.${className} div.nums`,
46
+ `.common-fuc-group li.${className} div.nums`,
47
+ `.common-fuc-group li.${className} .nums`,
48
+ `li.${className} div.nums`,
49
+ ];
50
+ for (const sel of selectors) {
51
+ const el = $(sel);
52
+ if (el.length > 0) {
53
+ const titleAttr = el.attr('title');
54
+ if (titleAttr) {
55
+ const num = titleAttr.replace(/,/g, '').trim();
56
+ if (num && /^\d+$/.test(num))
57
+ return num;
58
+ }
59
+ const text = el.text().replace(/,/g, '').trim();
60
+ if (text && /^\d+$/.test(text))
61
+ return text;
62
+ }
63
+ }
64
+ return result;
65
+ }
66
+ const pushNum = getSocialNum('push');
67
+ const favNum = getSocialNum('like');
68
+ // 目录
69
+ const tocItems = [];
70
+ $('a[href^="javascript:void(0);"]').each((i, el) => {
71
+ const text = (0, utils_1.cleanText)($(el).text());
72
+ if (text && text.length > 2 && text.length < 50 && !text.includes('百科') && !text.includes('登录')) {
73
+ tocItems.push(text);
74
+ }
75
+ });
76
+ // 正文提取
77
+ const contentNodes = [];
78
+ const contentRoot = $('.post-content, .article-content, .common-text, .news-text').first();
79
+ const BLOCK_TAGS = new Set(['p', 'div', 'section', 'article', 'blockquote', 'ul', 'ol']);
80
+ const SKIP_TAGS = new Set(['script', 'style', 'noscript', 'svg']);
81
+ let textBuffer = '';
82
+ let textTag = 'p';
83
+ const normalizeText = (text) => String(text || '')
84
+ .replace(/\u00a0/g, ' ')
85
+ .replace(/[ \t\f\v]+/g, ' ')
86
+ .replace(/\n{3,}/g, '\n\n')
87
+ .trim();
88
+ const pushTextNode = (text, tag = 'p') => {
89
+ const normalized = normalizeText(text);
90
+ if (!normalized)
91
+ return;
92
+ const last = contentNodes[contentNodes.length - 1];
93
+ if ((last === null || last === void 0 ? void 0 : last.type) === 't' && last.tag === tag && tag !== 'h') {
94
+ last.val = `${last.val}\n${normalized}`;
95
+ return;
96
+ }
97
+ contentNodes.push({ type: 't', val: normalized, tag });
98
+ };
99
+ const flushText = () => {
100
+ if (!textBuffer)
101
+ return;
102
+ pushTextNode(textBuffer, textTag || 'p');
103
+ textBuffer = '';
104
+ textTag = 'p';
105
+ };
106
+ const appendText = (text, tag = 'p') => {
107
+ if (!text)
108
+ return;
109
+ if (textBuffer && textTag !== tag)
110
+ flushText();
111
+ textTag = tag;
112
+ textBuffer += text;
113
+ };
114
+ function parseContent(node, preferredTag = 'p') {
115
+ if (!node)
116
+ return;
117
+ if (node.type === 'text') {
118
+ appendText(node.data || '', preferredTag);
119
+ return;
120
+ }
121
+ if (node.type !== 'tag')
122
+ return;
123
+ const tagName = String(node.name || '').toLowerCase();
124
+ if (!tagName || SKIP_TAGS.has(tagName))
125
+ return;
126
+ if (tagName === 'img') {
127
+ const src = (0, utils_1.extractImageUrl)(node);
128
+ const alt = normalizeText(node.attribs.alt || '');
129
+ const isEmojiLikeAlt = !!alt && /^(\p{Emoji_Presentation}|\p{Emoji}\uFE0F|\p{Emoji}\u200D)+$/u.test(alt);
130
+ const isEmojiLikeSrc = /emoji|smilies|twemoji|emot/i.test(src || '');
131
+ if ((isEmojiLikeAlt || isEmojiLikeSrc) && alt) {
132
+ appendText(alt, preferredTag);
133
+ return;
134
+ }
135
+ flushText();
136
+ if (src && !src.includes('loading') && !src.includes('icon')) {
137
+ contentNodes.push({ type: 'i', src: (0, utils_1.fixUrl)(src) });
138
+ }
139
+ return;
140
+ }
141
+ if (tagName === 'br') {
142
+ appendText('\n', preferredTag);
143
+ return;
144
+ }
145
+ if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) {
146
+ flushText();
147
+ pushTextNode($(node).text(), 'h');
148
+ return;
149
+ }
150
+ if (tagName === 'li') {
151
+ flushText();
152
+ appendText('', 'li');
153
+ if (node.children)
154
+ node.children.forEach(child => parseContent(child, 'li'));
155
+ const text = normalizeText(textBuffer);
156
+ textBuffer = '';
157
+ textTag = 'p';
158
+ if (text)
159
+ contentNodes.push({ type: 'li', val: text });
160
+ return;
161
+ }
162
+ if (tagName === 'table') {
163
+ flushText();
164
+ const galleryItems = (0, utils_1.parseGalleryFromTable)($, node);
165
+ if (galleryItems.length) {
166
+ contentNodes.push({ type: 'g', items: galleryItems });
167
+ return;
168
+ }
169
+ const rows = [];
170
+ $(node).find('tr').each((_, tr) => {
171
+ const row = [];
172
+ $(tr).find('th,td').each((__, cell) => row.push(normalizeText($(cell).text())));
173
+ if (row.some(Boolean))
174
+ rows.push(row);
175
+ });
176
+ if (rows.length)
177
+ contentNodes.push({ type: 'tb', rows });
178
+ return;
179
+ }
180
+ if (tagName === 'a') {
181
+ const text = normalizeText($(node).text());
182
+ const href = (0, utils_1.fixUrl)(node.attribs.href);
183
+ const label = text || (0, utils_1.compactUrlText)(href);
184
+ if (label)
185
+ appendText(label, preferredTag);
186
+ return;
187
+ }
188
+ const isBlock = BLOCK_TAGS.has(tagName);
189
+ if (isBlock)
190
+ flushText();
191
+ if (node.children)
192
+ node.children.forEach(child => parseContent(child, preferredTag));
193
+ if (isBlock)
194
+ flushText();
195
+ }
196
+ if (contentRoot.length) {
197
+ const textContainer = contentRoot.find('.text').first();
198
+ if (textContainer.length > 0)
199
+ textContainer[0].children.forEach(parseContent);
200
+ else
201
+ contentRoot[0].children.forEach(parseContent);
202
+ }
203
+ flushText();
204
+ if (contentNodes.length === 0) {
205
+ const metaDesc = $('meta[name="description"]').attr('content');
206
+ if (metaDesc)
207
+ contentNodes.push({ type: 't', val: metaDesc, tag: 'p' });
208
+ }
209
+ // --- 2. 布局常量定义 ---
210
+ const width = 1000;
211
+ const font = rendering_1.GLOBAL_FONT_FAMILY;
212
+ const margin = 20;
213
+ const winPadding = 40;
214
+ const contentW = width - margin * 2 - winPadding * 2;
215
+ // --- 3. 关键步骤:预加载图片以获取真实高度 ---
216
+ // 并行加载所有图片,确保后续高度计算准确
217
+ await Promise.all(contentNodes.map(async (node) => {
218
+ if (node.type === 'i') {
219
+ try {
220
+ const img = await (0, rendering_1.loadImageWithHeaders)(node.src, constants_1.BASE_URL);
221
+ node.img = img; // 保存 Image 对象
222
+ // 计算自适应尺寸:宽度最大为 contentW,高度按比例缩放,不设上限
223
+ const scale = Math.min(contentW / img.width, 1);
224
+ node.dw = img.width * scale;
225
+ node.dh = img.height * scale;
226
+ }
227
+ catch (e) {
228
+ node.error = true;
229
+ }
230
+ }
231
+ else if (node.type === 'g') {
232
+ for (const item of node.items || []) {
233
+ try {
234
+ const img = await (0, rendering_1.loadImageWithHeaders)(item.src, constants_1.BASE_URL);
235
+ item.imgCache = img;
236
+ let scale = Math.min(contentW / img.width, 1);
237
+ let dw = img.width * scale;
238
+ let dh = img.height * scale;
239
+ if (dh > 460) {
240
+ const r = 460 / dh;
241
+ dh = 460;
242
+ dw = dw * r;
243
+ }
244
+ item.dw = dw;
245
+ item.dh = dh;
246
+ }
247
+ catch (e) {
248
+ item.error = true;
249
+ }
250
+ }
251
+ }
252
+ }));
253
+ // --- 4. 精确计算总高度 ---
254
+ const dummyC = (0, rendering_1.createCanvas)(100, 100);
255
+ const dummy = dummyC.getContext('2d');
256
+ let totalH = 0;
257
+ // Header 高度
258
+ dummy.font = `bold 32px "${font}"`;
259
+ const titleLines = (0, rendering_1.wrapText)(dummy, title, 0, 0, contentW, 45, 5, false) / 45;
260
+ const headerH = 60 + titleLines * 45 + 50 + 20;
261
+ totalH += headerH;
262
+ // TOC 高度
263
+ let tocH = 0;
264
+ if (tocItems.length > 0) {
265
+ tocH = 50 + Math.ceil(tocItems.length / 2) * 35 + 20;
266
+ totalH += tocH;
267
+ }
268
+ // 正文高度 (使用真实图片高度)
269
+ let contentH = 0;
270
+ dummy.font = `16px "${font}"`;
271
+ for (const node of contentNodes) {
272
+ if (node.type === 't') {
273
+ const isHeader = node.tag === 'h';
274
+ const fontSize = isHeader ? 22 : 16;
275
+ dummy.font = `${isHeader ? 'bold' : ''} ${fontSize}px "${font}"`;
276
+ const lineHeight = Math.floor(fontSize * 1.6);
277
+ // 这里不再限制行数 (limit = 10000),显示全部文本
278
+ const lines = (0, rendering_1.wrapText)(dummy, node.val, 0, 0, contentW, lineHeight, 10000, false) / lineHeight;
279
+ contentH += lines * lineHeight + (isHeader ? 25 : 15);
280
+ }
281
+ else if (node.type === 'li') {
282
+ dummy.font = `600 16px "${font}"`;
283
+ const h = (0, rendering_1.wrapText)(dummy, node.val, 0, 0, Math.max(80, contentW - 24), 26, 10000, false);
284
+ contentH += h + 12;
285
+ }
286
+ else if (node.type === 'tb') {
287
+ const tableH = ((_a = (0, rendering_1.measureTableLayout)(dummy, node, contentW, 22, `600 14px "${font}"`, `800 14px "${font}"`)) === null || _a === void 0 ? void 0 : _a.totalH) || 0;
288
+ contentH += tableH + 20;
289
+ }
290
+ else if (node.type === 'g') {
291
+ for (const item of node.items || []) {
292
+ if (item.error || !item.imgCache) {
293
+ contentH += 110;
294
+ continue;
295
+ }
296
+ const captionH = item.caption ? (0, rendering_1.wrapText)(dummy, item.caption, 0, 0, contentW, 22, 5, false) : 0;
297
+ contentH += item.dh + captionH + 24;
298
+ }
299
+ }
300
+ else if (node.type === 'i' && !node.error && node.img) {
301
+ // 使用预加载时计算出的真实高度
302
+ contentH += node.dh + 25;
303
+ }
304
+ }
305
+ if (contentH === 0)
306
+ contentH = 100;
307
+ totalH += contentH + 50; // Padding
308
+ const windowH = totalH + 100;
309
+ const canvasH = windowH + margin * 2;
310
+ // --- 5. 绘制 ---
311
+ const canvas = (0, rendering_1.createCanvas)(width, canvasH);
312
+ const ctx = canvas.getContext('2d');
313
+ // 背景 (Bing)
314
+ try {
315
+ const bgUrl = 'https://bing.biturl.top/?resolution=1920&format=image&index=0&mkt=zh-CN';
316
+ const bgImg = await (0, rendering_1.loadImage)(bgUrl);
317
+ const r = Math.max(width / bgImg.width, canvasH / bgImg.height);
318
+ ctx.drawImage(bgImg, (width - bgImg.width * r) / 2, (canvasH - bgImg.height * r) / 2, bgImg.width * r, bgImg.height * r);
319
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.4)';
320
+ ctx.fillRect(0, 0, width, canvasH);
321
+ }
322
+ catch (e) {
323
+ const grad = ctx.createLinearGradient(0, 0, 0, canvasH);
324
+ grad.addColorStop(0, '#a18cd1');
325
+ grad.addColorStop(1, '#fbc2eb');
326
+ ctx.fillStyle = grad;
327
+ ctx.fillRect(0, 0, width, canvasH);
328
+ }
329
+ // 窗口主体
330
+ const winX = margin, winY = margin;
331
+ ctx.save();
332
+ ctx.shadowColor = 'rgba(0,0,0,0.2)';
333
+ ctx.shadowBlur = 50;
334
+ ctx.shadowOffsetY = 20;
335
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.85)';
336
+ (0, rendering_1.roundRect)(ctx, winX, winY, width - margin * 2, windowH, 16);
337
+ ctx.fill();
338
+ ctx.restore();
339
+ ctx.strokeStyle = 'rgba(255,255,255,0.5)';
340
+ ctx.lineWidth = 1;
341
+ (0, rendering_1.roundRect)(ctx, winX, winY, width - margin * 2, windowH, 16);
342
+ ctx.stroke();
343
+ // 交通灯
344
+ ['#ff5f56', '#ffbd2e', '#27c93f'].forEach((c, i) => {
345
+ ctx.beginPath();
346
+ ctx.arc(winX + 25 + i * 25, winY + 25, 6, 0, Math.PI * 2);
347
+ ctx.fillStyle = c;
348
+ ctx.fill();
349
+ });
350
+ // --- 内容绘制 ---
351
+ let dy = winY + 60;
352
+ const cx = winX + winPadding;
353
+ // 1. Header
354
+ ctx.fillStyle = '#333';
355
+ ctx.font = `bold 32px "${font}"`;
356
+ ctx.textBaseline = 'top';
357
+ const drawnTitleH = (0, rendering_1.wrapText)(ctx, title, cx, dy, contentW, 45, 5, true);
358
+ dy += drawnTitleH + 20;
359
+ // Meta Info
360
+ const avSize = 40;
361
+ if (authorAvatar) {
362
+ try {
363
+ const img = await (0, rendering_1.loadImageWithHeaders)(authorAvatar, constants_1.BASE_URL);
364
+ ctx.save();
365
+ ctx.beginPath();
366
+ ctx.arc(cx + avSize / 2, dy + avSize / 2, avSize / 2, 0, Math.PI * 2);
367
+ ctx.clip();
368
+ ctx.drawImage(img, cx, dy, avSize, avSize);
369
+ ctx.restore();
370
+ }
371
+ catch (e) {
372
+ ctx.fillStyle = '#ccc';
373
+ ctx.beginPath();
374
+ ctx.arc(cx + avSize / 2, dy + avSize / 2, avSize / 2, 0, Math.PI * 2);
375
+ ctx.fill();
376
+ }
377
+ }
378
+ else {
379
+ ctx.fillStyle = '#ccc';
380
+ ctx.beginPath();
381
+ ctx.arc(cx + avSize / 2, dy + avSize / 2, avSize / 2, 0, Math.PI * 2);
382
+ ctx.fill();
383
+ }
384
+ ctx.fillStyle = '#333';
385
+ ctx.font = `bold 16px "${font}"`;
386
+ ctx.fillText(author, cx + avSize + 15, dy + 5);
387
+ ctx.fillStyle = '#888';
388
+ ctx.font = `12px "${font}"`;
389
+ ctx.fillText(date || '未知日期', cx + avSize + 15, dy + 25);
390
+ // Stats
391
+ const statsY = dy + 10;
392
+ let sx = cx + contentW;
393
+ const drawStat = (icon, val, color) => {
394
+ ctx.textAlign = 'right';
395
+ ctx.fillStyle = color;
396
+ ctx.font = `bold 16px "${font}"`;
397
+ const vw = ctx.measureText(val).width;
398
+ ctx.fillText(val, sx, statsY);
399
+ ctx.fillStyle = '#999';
400
+ ctx.font = `12px "${font}"`;
401
+ ctx.fillText(icon, sx - vw - 5, statsY);
402
+ sx -= (vw + 5 + ctx.measureText(icon).width + 20);
403
+ ctx.textAlign = 'left';
404
+ };
405
+ drawStat('收藏', favNum, '#f1c40f');
406
+ drawStat('推荐', pushNum, '#e74c3c');
407
+ drawStat('浏览', views, '#3498db');
408
+ dy += avSize + 30;
409
+ // Divider
410
+ ctx.fillStyle = 'rgba(0,0,0,0.05)';
411
+ ctx.fillRect(cx, dy, contentW, 1);
412
+ dy += 25;
413
+ // 2. TOC
414
+ if (tocItems.length > 0) {
415
+ ctx.fillStyle = 'rgba(0,0,0,0.03)';
416
+ (0, rendering_1.roundRect)(ctx, cx, dy, contentW, tocH - 20, 10);
417
+ ctx.fill();
418
+ ctx.fillStyle = '#555';
419
+ ctx.font = `bold 16px "${font}"`;
420
+ ctx.fillText('目录', cx + 20, dy + 30);
421
+ let tx = cx + 20;
422
+ let ty = dy + 60;
423
+ const colW = (contentW - 40) / 2;
424
+ ctx.fillStyle = '#666';
425
+ ctx.font = `14px "${font}"`;
426
+ tocItems.forEach((item, i) => {
427
+ const col = i % 2;
428
+ if (col === 0 && i > 0)
429
+ ty += 30;
430
+ const x = tx + col * colW;
431
+ let displayTitle = item;
432
+ if (ctx.measureText(displayTitle).width > colW - 20) {
433
+ while (ctx.measureText(displayTitle + '...').width > colW - 20 && displayTitle.length > 0)
434
+ displayTitle = displayTitle.slice(0, -1);
435
+ displayTitle += '...';
436
+ }
437
+ ctx.fillText(`${i + 1}. ${displayTitle}`, x, ty);
438
+ });
439
+ dy += tocH + 10;
440
+ }
441
+ // 3. Content (Drawing loop)
442
+ for (const node of contentNodes) {
443
+ if (node.type === 't') {
444
+ const isHeader = node.tag === 'h';
445
+ const fontSize = isHeader ? 22 : 16;
446
+ ctx.font = `${isHeader ? '800' : '600'} ${fontSize}px "${font}"`;
447
+ ctx.fillStyle = isHeader ? '#2c3e50' : '#444';
448
+ if (isHeader) {
449
+ ctx.fillStyle = '#3498db';
450
+ ctx.fillRect(cx - 15, dy + 5, 4, fontSize);
451
+ ctx.fillStyle = '#2c3e50';
452
+ }
453
+ const lineHeight = Math.floor(fontSize * 1.6);
454
+ dy = await (0, rendering_1.drawTextWithTwemoji)(ctx, node.val, cx, dy, contentW, lineHeight, 10000, true) + (isHeader ? 20 : 15);
455
+ }
456
+ else if (node.type === 'li') {
457
+ const bulletX = cx + 4;
458
+ const textX = cx + 24;
459
+ ctx.fillStyle = '#444';
460
+ ctx.font = `600 16px "${font}"`;
461
+ ctx.fillText('•', bulletX, dy);
462
+ ctx.font = `600 16px "${font}"`;
463
+ dy = await (0, rendering_1.drawTextWithTwemoji)(ctx, node.val, textX, dy, Math.max(80, contentW - (textX - cx)), 26, 10000, true) + 12;
464
+ }
465
+ else if (node.type === 'tb') {
466
+ const tableH = (0, rendering_1.drawTable)(ctx, node, cx, dy, contentW, 22, `600 14px "${font}"`, `800 14px "${font}"`, { headerBg: 'rgba(52,152,219,0.12)', cellBg: 'rgba(255,255,255,0.7)', border: 'rgba(52,152,219,0.25)', text: '#2f3742' });
467
+ dy += tableH + 20;
468
+ }
469
+ else if (node.type === 'g') {
470
+ for (const item of node.items || []) {
471
+ if (item.error || !item.imgCache) {
472
+ ctx.fillStyle = 'rgba(0,0,0,0.06)';
473
+ (0, rendering_1.roundRect)(ctx, cx, dy, contentW, 90, 8);
474
+ ctx.fill();
475
+ ctx.fillStyle = '#999';
476
+ ctx.font = `600 14px "${font}"`;
477
+ ctx.fillText('Image failed to load', cx + 16, dy + 38);
478
+ dy += 110;
479
+ continue;
480
+ }
481
+ const dx = cx + (contentW - item.dw) / 2;
482
+ ctx.save();
483
+ (0, rendering_1.roundRect)(ctx, dx, dy, item.dw, item.dh, 8);
484
+ ctx.clip();
485
+ ctx.drawImage(item.imgCache, dx, dy, item.dw, item.dh);
486
+ ctx.restore();
487
+ dy += item.dh + 8;
488
+ if (item.caption) {
489
+ ctx.fillStyle = '#666';
490
+ ctx.font = `600 14px "${font}"`;
491
+ dy = await (0, rendering_1.drawTextWithTwemoji)(ctx, item.caption, cx, dy, contentW, 22, 5, true) + 12;
492
+ }
493
+ else {
494
+ dy += 8;
495
+ }
496
+ }
497
+ }
498
+ else if (node.type === 'i' && !node.error && node.img) {
499
+ // 绘制预加载的图片
500
+ // 居中显示
501
+ const dx = cx + (contentW - node.dw) / 2;
502
+ ctx.save();
503
+ ctx.shadowColor = 'rgba(0,0,0,0.1)';
504
+ ctx.shadowBlur = 15;
505
+ ctx.shadowOffsetY = 5;
506
+ // 绘制图片 (圆角效果)
507
+ (0, rendering_1.roundRect)(ctx, dx, dy, node.dw, node.dh, 8);
508
+ ctx.shadowColor = 'transparent'; // clip 前清除阴影以免影响性能
509
+ ctx.clip();
510
+ ctx.drawImage(node.img, dx, dy, node.dw, node.dh);
511
+ ctx.restore();
512
+ dy += node.dh + 25;
513
+ }
514
+ else if (node.type === 'i' && (node.error || !node.img)) {
515
+ ctx.fillStyle = 'rgba(0,0,0,0.06)';
516
+ (0, rendering_1.roundRect)(ctx, cx, dy, contentW, 90, 8);
517
+ ctx.fill();
518
+ ctx.fillStyle = '#999';
519
+ ctx.font = `600 14px "${font}"`;
520
+ ctx.fillText('Image failed to load', cx + 16, dy + 38);
521
+ dy += 110;
522
+ }
523
+ }
524
+ // Footer
525
+ dy += 30;
526
+ ctx.fillStyle = '#aaa';
527
+ ctx.font = `12px "${font}"`;
528
+ ctx.textAlign = 'center';
529
+ ctx.fillText('mcmod.cn | Powered by Koishi', width / 2, canvasH - 15);
530
+ return await canvas.encode('png');
531
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createInfoCard = exports.drawCenterCardImpl = exports.drawCenterCard = exports.drawAuthorCard = exports.drawTutorialCard = exports.drawModCard = void 0;
4
+ var mod_card_1 = require("./cards/mod-card");
5
+ Object.defineProperty(exports, "drawModCard", { enumerable: true, get: function () { return mod_card_1.drawModCard; } });
6
+ var tutorial_card_1 = require("./cards/tutorial-card");
7
+ Object.defineProperty(exports, "drawTutorialCard", { enumerable: true, get: function () { return tutorial_card_1.drawTutorialCard; } });
8
+ var author_card_1 = require("./cards/author-card");
9
+ Object.defineProperty(exports, "drawAuthorCard", { enumerable: true, get: function () { return author_card_1.drawAuthorCard; } });
10
+ var center_card_1 = require("./cards/center-card");
11
+ Object.defineProperty(exports, "drawCenterCard", { enumerable: true, get: function () { return center_card_1.drawCenterCard; } });
12
+ Object.defineProperty(exports, "drawCenterCardImpl", { enumerable: true, get: function () { return center_card_1.drawCenterCardImpl; } });
13
+ var info_card_1 = require("./cards/info-card");
14
+ Object.defineProperty(exports, "createInfoCard", { enumerable: true, get: function () { return info_card_1.createInfoCard; } });
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FALLBACK_TYPE_MAP = exports.COMMON_SELECT_URL = exports.CENTER_URL = exports.BASE_URL = exports.TIMEOUT_MS = exports.PAGE_SIZE = void 0;
4
+ exports.PAGE_SIZE = 10;
5
+ exports.TIMEOUT_MS = 60000;
6
+ exports.BASE_URL = 'https://www.mcmod.cn';
7
+ exports.CENTER_URL = 'https://center.mcmod.cn';
8
+ exports.COMMON_SELECT_URL = `${exports.BASE_URL}/object/CommonSelect/`;
9
+ exports.FALLBACK_TYPE_MAP = {
10
+ mod: 'post_relation_mod',
11
+ pack: 'post_relation_modpack',
12
+ author: 'author',
13
+ };