koishi-plugin-fimtale-api 0.0.8 → 0.0.95
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/lib/index.js +81 -86
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -47,7 +47,7 @@ var Config = import_koishi.Schema.object({
|
|
|
47
47
|
cookies: import_koishi.Schema.string().role("secret").description("浏览器 Cookie (用于解除安全模式,必填)"),
|
|
48
48
|
pollInterval: import_koishi.Schema.number().default(60 * 1e3).description("追更轮询间隔(ms)"),
|
|
49
49
|
autoParseLink: import_koishi.Schema.boolean().default(true).description("自动解析链接为预览卡片"),
|
|
50
|
-
// 渲染配置 (iPhone 12/13
|
|
50
|
+
// 渲染配置 (iPhone 12/13 标准)
|
|
51
51
|
deviceWidth: import_koishi.Schema.number().default(390).description("阅读器渲染宽度(px)"),
|
|
52
52
|
deviceHeight: import_koishi.Schema.number().default(844).description("阅读器渲染高度(px)"),
|
|
53
53
|
fontSize: import_koishi.Schema.number().default(20).description("正文字号(px)")
|
|
@@ -62,7 +62,7 @@ function apply(ctx, config) {
|
|
|
62
62
|
}, { primary: "id", autoInc: true });
|
|
63
63
|
const sleep = /* @__PURE__ */ __name((ms) => new Promise((resolve) => setTimeout(resolve, ms)), "sleep");
|
|
64
64
|
const formatDate = /* @__PURE__ */ __name((timestamp) => {
|
|
65
|
-
if (!timestamp) return "
|
|
65
|
+
if (!timestamp) return "Unknown";
|
|
66
66
|
const date = new Date(timestamp * 1e3);
|
|
67
67
|
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, "0")}-${date.getDate().toString().padStart(2, "0")}`;
|
|
68
68
|
}, "formatDate");
|
|
@@ -83,9 +83,10 @@ function apply(ctx, config) {
|
|
|
83
83
|
}, "generateGradient");
|
|
84
84
|
const cleanContent = /* @__PURE__ */ __name((html) => {
|
|
85
85
|
if (!html) return "";
|
|
86
|
-
return html.replace(
|
|
86
|
+
return html.replace(/style="[^"]*"/gi, "").replace(/<p[^>]*>\s*( |<br\s*\/?>|\s)*\s*<\/p>/gi, "").replace(/(<br\s*\/?>\s*){2,}/gi, "<br>").trim();
|
|
87
87
|
}, "cleanContent");
|
|
88
88
|
const fontStack = '"Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", "WenQuanYi Micro Hei", Arial, sans-serif';
|
|
89
|
+
const fontSerif = '"Noto Serif SC", "Source Han Serif SC", "SimSun", serif';
|
|
89
90
|
const injectCookies = /* @__PURE__ */ __name(async (page) => {
|
|
90
91
|
if (!config.cookies) return;
|
|
91
92
|
const cookies = config.cookies.split(";").map((pair) => {
|
|
@@ -239,7 +240,7 @@ function apply(ctx, config) {
|
|
|
239
240
|
.id-tag { position: absolute; top: 12px; left: 12px; background: rgba(0,0,0,0.6); color: #fff; padding: 4px 10px; border-radius: 6px; font-size: 13px; font-weight: bold; backdrop-filter: blur(4px); font-family: monospace; }
|
|
240
241
|
.info { flex: 1; padding: 24px; display: flex; flex-direction: column; justify-content: space-between; overflow: hidden; }
|
|
241
242
|
|
|
242
|
-
.header-group { display: flex; flex-direction: column; }
|
|
243
|
+
.header-group { display: flex; flex-direction: column; flex-shrink: 0; }
|
|
243
244
|
.title {
|
|
244
245
|
font-size: 22px; font-weight: 700; color: #333; line-height: 1.35;
|
|
245
246
|
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
|
|
@@ -255,13 +256,16 @@ function apply(ctx, config) {
|
|
|
255
256
|
.tag { background: #eff2f5; color: #5c6b7f; padding: 3px 9px; border-radius: 4px; font-size: 11px; font-weight: 500; }
|
|
256
257
|
.tag-imp { background: #e3f2fd; color: #1565c0; }
|
|
257
258
|
|
|
259
|
+
/* 简介区域防截断优化 */
|
|
258
260
|
.summary-box {
|
|
259
|
-
flex: 1;
|
|
261
|
+
flex: 1; position: relative; margin-top: 4px; overflow: hidden;
|
|
260
262
|
}
|
|
261
263
|
.summary {
|
|
262
|
-
font-size: 13px; color: #666;
|
|
263
|
-
|
|
264
|
+
font-size: 13px; color: #666;
|
|
265
|
+
line-height: 1.5; /* 设定标准行高 */
|
|
266
|
+
max-height: 6em; /* 4行 = 1.5 * 4 */
|
|
264
267
|
display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;
|
|
268
|
+
padding-bottom: 2px; /* 底部缓冲 */
|
|
265
269
|
}
|
|
266
270
|
|
|
267
271
|
.footer { border-top: 1px solid #eee; padding-top: 14px; display: flex; justify-content: space-between; font-size: 12px; color: #888; margin-top: 15px; flex-shrink: 0;}
|
|
@@ -277,8 +281,13 @@ function apply(ctx, config) {
|
|
|
277
281
|
${subTitle ? `<div class="subtitle">${subTitle}</div>` : ""}
|
|
278
282
|
<div class="author">@${info.UserName}</div>
|
|
279
283
|
</div>
|
|
284
|
+
|
|
280
285
|
<div class="tags">${displayTags.map((t) => `<span class="tag ${["文", "译", "R"].includes(t) ? "tag-imp" : ""}">${t}</span>`).join("")}</div>
|
|
281
|
-
|
|
286
|
+
|
|
287
|
+
<div class="summary-box">
|
|
288
|
+
<div class="summary">${summary}</div>
|
|
289
|
+
</div>
|
|
290
|
+
|
|
282
291
|
<div class="footer">
|
|
283
292
|
<span class="stat"><b style="color:#009688">热度</b>${views}</span>
|
|
284
293
|
<span class="stat"><b style="color:#673ab7">评论</b>${comments}</span>
|
|
@@ -360,104 +369,90 @@ function apply(ctx, config) {
|
|
|
360
369
|
<html>
|
|
361
370
|
<head>
|
|
362
371
|
<style>
|
|
363
|
-
body { margin: 0; padding: 0; width: ${config.deviceWidth}px; background-color: #f6f4ec; color: #2c2c2c; font-family: ${
|
|
372
|
+
body { margin: 0; padding: 0; width: ${config.deviceWidth}px; background-color: #f6f4ec; color: #2c2c2c; font-family: ${fontSerif}; }
|
|
364
373
|
#source-container { display: none; }
|
|
365
|
-
|
|
366
|
-
/* 使用 Flex 布局,让页脚自然占据底部空间,不覆盖正文 */
|
|
367
374
|
.page {
|
|
368
375
|
width: ${config.deviceWidth}px; height: ${config.deviceHeight}px;
|
|
369
|
-
|
|
376
|
+
/* 增加 padding,减少每页内容量,看起来更舒适 */
|
|
377
|
+
padding: 40px 30px; box-sizing: border-box;
|
|
370
378
|
position: relative; background: #f6f4ec; overflow: hidden;
|
|
379
|
+
/* 关键:CSS多列布局,无需手动分页 */
|
|
371
380
|
display: flex; flex-direction: column;
|
|
372
381
|
}
|
|
373
382
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
383
|
+
/* 自定义 CSS Column 容器 */
|
|
384
|
+
#content-wrapper {
|
|
385
|
+
width: ${config.deviceWidth}px; height: ${config.deviceHeight}px;
|
|
386
|
+
column-width: ${config.deviceWidth}px; column-gap: 0; column-fill: auto;
|
|
387
|
+
overflow-x: hidden; overflow-y: hidden; /* 隐藏滚动条 */
|
|
378
388
|
}
|
|
379
|
-
|
|
380
|
-
/*
|
|
381
|
-
.
|
|
382
|
-
|
|
383
|
-
|
|
389
|
+
|
|
390
|
+
/* 实际内容区 */
|
|
391
|
+
.inner-content {
|
|
392
|
+
padding: 50px 25px 40px 25px; /* 避让页眉页脚 */
|
|
393
|
+
box-sizing: border-box;
|
|
394
|
+
height: 100%;
|
|
395
|
+
font-size: ${config.fontSize}px;
|
|
396
|
+
line-height: 1.8;
|
|
397
|
+
text-align: justify;
|
|
384
398
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
399
|
+
|
|
400
|
+
/* 固定页眉页脚 (Overlay) */
|
|
401
|
+
.fixed-header {
|
|
402
|
+
position: absolute; top: 0; left: 0; width: 100%; height: 40px;
|
|
403
|
+
background: #f6f4ec; z-index: 10;
|
|
404
|
+
border-bottom: 1px solid #d7ccc8;
|
|
405
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
406
|
+
padding: 0 25px; box-sizing: border-box;
|
|
407
|
+
font-size: 12px; color: #8d6e63; font-weight: bold; font-family: ${fontStack};
|
|
392
408
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
409
|
+
.fixed-footer {
|
|
410
|
+
position: absolute; bottom: 0; left: 0; width: 100%; height: 30px;
|
|
411
|
+
background: #f6f4ec; z-index: 10;
|
|
412
|
+
display: flex; align-items: center; justify-content: center;
|
|
413
|
+
font-size: 12px; color: #aaa; font-family: ${fontStack};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/* 排版细节 */
|
|
417
|
+
p { margin: 0 0 0.8em 0; text-indent: 2em; }
|
|
418
|
+
img { max-width: 100%; height: auto; display: block; margin: 15px auto; border-radius: 6px; box-shadow: 0 4px 10px rgba(0,0,0,0.1); break-inside: avoid; }
|
|
419
|
+
h1, h2, h3 { font-size: 1.1em; margin: 0.8em 0; color: #5d4037; text-indent: 0; font-weight: bold; break-after: avoid; }
|
|
397
420
|
</style>
|
|
398
421
|
</head>
|
|
399
422
|
<body>
|
|
400
|
-
<div id="source-container"
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
page.appendChild(header);
|
|
416
|
-
|
|
417
|
-
const content = document.createElement('div'); content.className = 'page-content';
|
|
418
|
-
page.appendChild(content);
|
|
419
|
-
|
|
420
|
-
const footer = document.createElement('div'); footer.className = 'page-footer';
|
|
421
|
-
footer.id = 'footer-' + pageIndex;
|
|
422
|
-
page.appendChild(footer);
|
|
423
|
-
|
|
424
|
-
output.appendChild(page);
|
|
425
|
-
currentPageContent = content;
|
|
426
|
-
return page;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
createNewPage();
|
|
430
|
-
const children = Array.from(source.children);
|
|
431
|
-
|
|
432
|
-
for (const child of children) {
|
|
433
|
-
if (child.className === 'meta-info') continue;
|
|
434
|
-
|
|
435
|
-
currentPageContent.appendChild(child.cloneNode(true));
|
|
436
|
-
|
|
437
|
-
// 核心修复:增加 10px 的安全缓冲,防止 descenders 被切
|
|
438
|
-
if (currentPageContent.scrollHeight > currentPageContent.clientHeight - 10) {
|
|
439
|
-
currentPageContent.removeChild(currentPageContent.lastChild);
|
|
440
|
-
pageIndex++;
|
|
441
|
-
createNewPage();
|
|
442
|
-
currentPageContent.appendChild(child.cloneNode(true));
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
for(let i=1; i<=pageIndex; i++) document.getElementById('footer-'+i).innerText = \`- \${i} / \${pageIndex} -\`;
|
|
447
|
-
return pageIndex;
|
|
448
|
-
}
|
|
449
|
-
</script>
|
|
423
|
+
<div id="source-container"></div>
|
|
424
|
+
|
|
425
|
+
<!-- 使用 CSS Columns 进行分页布局 -->
|
|
426
|
+
<div id="content-wrapper">
|
|
427
|
+
<div class="inner-content">
|
|
428
|
+
${content}
|
|
429
|
+
</div>
|
|
430
|
+
</div>
|
|
431
|
+
|
|
432
|
+
<!-- 覆盖层:页眉页脚,截图时通过 JS 更新 -->
|
|
433
|
+
<div class="fixed-header">
|
|
434
|
+
<span>${info.Title.substring(0, 12) + (info.Title.length > 12 ? "..." : "")}</span>
|
|
435
|
+
<span>${info.UserName}</span>
|
|
436
|
+
</div>
|
|
437
|
+
<div class="fixed-footer" id="page-indicator">- 1 -</div>
|
|
450
438
|
</body></html>`;
|
|
451
439
|
const page = await ctx.puppeteer.page();
|
|
452
440
|
try {
|
|
453
441
|
await injectCookies(page);
|
|
454
442
|
await page.setContent(html);
|
|
455
443
|
await page.setViewport({ width: config.deviceWidth, height: config.deviceHeight, deviceScaleFactor: 2 });
|
|
456
|
-
await page
|
|
444
|
+
const scrollWidth = await page.$eval("#content-wrapper", (el) => el.scrollWidth);
|
|
445
|
+
const totalPages = Math.ceil(scrollWidth / config.deviceWidth);
|
|
457
446
|
const imgs = [];
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
447
|
+
for (let i = 0; i < totalPages; i++) {
|
|
448
|
+
await page.evaluate((x) => {
|
|
449
|
+
document.getElementById("content-wrapper").scrollTo(x, 0);
|
|
450
|
+
}, i * config.deviceWidth);
|
|
451
|
+
await page.evaluate((curr, total) => {
|
|
452
|
+
document.getElementById("page-indicator").innerText = `- ${curr} / ${total} -`;
|
|
453
|
+
}, i + 1, totalPages);
|
|
454
|
+
const img = await page.screenshot({ type: "jpeg", quality: 80 });
|
|
455
|
+
imgs.push(img);
|
|
461
456
|
}
|
|
462
457
|
return imgs;
|
|
463
458
|
} finally {
|