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.
Files changed (2) hide show
  1. package/lib/index.js +81 -86
  2. 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 Pro 比例)
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(/<p>\s*&nbsp;\s*<\/p>/gi, "").replace(/<p>\s*<br\s*\/?>\s*<\/p>/gi, "").replace(/<p>\s*<\/p>/gi, "").replace(/(<br\s*\/?>){2,}/gi, "<br>").replace(/margin-bottom:\s*\d+px/gi, "");
86
+ return html.replace(/style="[^"]*"/gi, "").replace(/<p[^>]*>\s*(&nbsp;|<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; overflow: hidden; position: relative; margin-top: 8px;
261
+ flex: 1; position: relative; margin-top: 4px; overflow: hidden;
260
262
  }
261
263
  .summary {
262
- font-size: 13px; color: #666; line-height: 1.6;
263
- max-height: 6.4em;
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
- <div class="summary-box"><div class="summary">${summary}</div></div>
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: ${fontStack}; }
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
- padding: 35px 28px; box-sizing: border-box;
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
- .page-header {
375
- font-size: 12px; color: #8d6e63; border-bottom: 2px solid #d7ccc8;
376
- padding-bottom: 12px; margin-bottom: 15px; flex-shrink: 0;
377
- display: flex; justify-content: space-between; font-weight: bold;
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
- .page-footer {
382
- text-align: center; font-size: 12px; color: #aaa; font-family: sans-serif;
383
- flex-shrink: 0; padding-top: 10px; margin-top: auto;
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
- .page-content {
387
- flex: 1; /* 占据剩余所有空间 */
388
- overflow: hidden; /* 隐藏溢出部分 */
389
- font-size: ${config.fontSize}px;
390
- line-height: 1.7;
391
- text-align: justify;
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
- p { margin: 0 0 0.6em 0; text-indent: 2em; }
395
- img { max-width: 100%; height: auto; display: block; margin: 10px auto; border-radius: 6px; box-shadow: 0 4px 10px rgba(0,0,0,0.1); }
396
- h1, h2, h3 { font-size: 1.1em; margin: 0.5em 0; color: #5d4037; text-indent: 0; font-weight: bold; }
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"><div class="meta-info" data-title="${info.Title}" data-author="${info.UserName}"></div>${content}</div>
401
- <div id="output"></div>
402
- <script>
403
- function paginate() {
404
- const source = document.getElementById('source-container');
405
- const output = document.getElementById('output');
406
- const title = source.querySelector('.meta-info').dataset.title;
407
- const author = source.querySelector('.meta-info').dataset.author;
408
- let pageIndex = 1;
409
- let currentPageContent = null;
410
-
411
- function createNewPage() {
412
- const page = document.createElement('div'); page.className = 'page';
413
- const header = document.createElement('div'); header.className = 'page-header';
414
- header.innerHTML = \`<span>\${title.substring(0, 12) + (title.length>12?'...':'')}</span><span>\${author}</span>\`;
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.evaluate("paginate()");
444
+ const scrollWidth = await page.$eval("#content-wrapper", (el) => el.scrollWidth);
445
+ const totalPages = Math.ceil(scrollWidth / config.deviceWidth);
457
446
  const imgs = [];
458
- const pages = await page.$$(".page");
459
- for (const p of pages) {
460
- imgs.push(await p.screenshot({ type: "jpeg", quality: 80 }));
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 {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-fimtale-api",
3
3
  "description": "Koishi插件,从fimtale搜索/订阅/随机获取小说/解析链接等",
4
- "version": "0.0.8",
4
+ "version": "0.0.95",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [