koishi-plugin-fimtale-api 0.0.6 → 0.0.8

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 +49 -20
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -47,6 +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
51
  deviceWidth: import_koishi.Schema.number().default(390).description("阅读器渲染宽度(px)"),
51
52
  deviceHeight: import_koishi.Schema.number().default(844).description("阅读器渲染高度(px)"),
52
53
  fontSize: import_koishi.Schema.number().default(20).description("正文字号(px)")
@@ -203,11 +204,11 @@ function apply(ctx, config) {
203
204
  const isChapter = info.IsChapter || !!parent && parent.ID !== info.ID;
204
205
  const displayTitle = isChapter && parent ? parent.Title : info.Title;
205
206
  let displayCover = null;
206
- if (isChapter) {
207
- displayCover = extractImage(info.Content);
207
+ if (isChapter && parent) {
208
+ displayCover = parent.Background || extractImage(parent.Content);
208
209
  }
209
210
  if (!displayCover) {
210
- displayCover = isChapter && parent ? parent.Background || extractImage(parent.Content) : info.Background || extractImage(info.Content);
211
+ displayCover = info.Background || extractImage(info.Content);
211
212
  }
212
213
  const displayTagsObj = isChapter && parent ? parent.Tags : info.Tags;
213
214
  const subTitle = isChapter ? info.Title : null;
@@ -233,11 +234,12 @@ function apply(ctx, config) {
233
234
  <head>
234
235
  <style>
235
236
  body { margin: 0; padding: 0; font-family: ${fontStack}; background: transparent; }
236
- .card { width: 620px; height: 340px; background: #fff; border-radius: 16px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); display: flex; overflow: hidden; }
237
+ .card { width: 620px; height: 360px; background: #fff; border-radius: 16px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); display: flex; overflow: hidden; }
237
238
  .cover { width: 220px; height: 100%; ${bgStyle} background-size: cover; background-position: center; position: relative; flex-shrink: 0; }
238
239
  .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; }
239
- .info { flex: 1; padding: 22px 26px; display: flex; flex-direction: column; justify-content: space-between; overflow: hidden; }
240
+ .info { flex: 1; padding: 24px; display: flex; flex-direction: column; justify-content: space-between; overflow: hidden; }
240
241
 
242
+ .header-group { display: flex; flex-direction: column; }
241
243
  .title {
242
244
  font-size: 22px; font-weight: 700; color: #333; line-height: 1.35;
243
245
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
@@ -247,19 +249,22 @@ function apply(ctx, config) {
247
249
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
248
250
  padding-left: 10px; border-left: 3px solid #e91e63;
249
251
  }
252
+ .author { font-size: 13px; color: #888; margin-top: 8px; font-weight: 400; }
250
253
 
251
- .author { font-size: 13px; color: #888; margin-top: 6px; font-weight: 400; }
252
-
253
- .tags { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0; max-height: 56px; overflow: hidden; align-content: flex-start; }
254
+ .tags { display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0; max-height: 56px; overflow: hidden; flex-shrink: 0;}
254
255
  .tag { background: #eff2f5; color: #5c6b7f; padding: 3px 9px; border-radius: 4px; font-size: 11px; font-weight: 500; }
255
256
  .tag-imp { background: #e3f2fd; color: #1565c0; }
256
257
 
258
+ .summary-box {
259
+ flex: 1; overflow: hidden; position: relative; margin-top: 8px;
260
+ }
257
261
  .summary {
258
262
  font-size: 13px; color: #666; line-height: 1.6;
263
+ max-height: 6.4em;
259
264
  display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;
260
- margin-top: auto;
261
265
  }
262
- .footer { border-top: 1px solid #eee; padding-top: 14px; display: flex; justify-content: space-between; font-size: 12px; color: #888; margin-top: 12px; }
266
+
267
+ .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;}
263
268
  .stat b { color: #555; font-weight: bold; margin-right: 2px;}
264
269
  </style>
265
270
  </head>
@@ -267,13 +272,13 @@ function apply(ctx, config) {
267
272
  <div class="card">
268
273
  <div class="cover"><div class="id-tag">ID: ${info.ID}</div></div>
269
274
  <div class="info">
270
- <div>
275
+ <div class="header-group">
271
276
  <div class="title">${displayTitle}</div>
272
277
  ${subTitle ? `<div class="subtitle">${subTitle}</div>` : ""}
273
278
  <div class="author">@${info.UserName}</div>
274
- <div class="tags">${displayTags.map((t) => `<span class="tag ${["文", "译", "R"].includes(t) ? "tag-imp" : ""}">${t}</span>`).join("")}</div>
275
279
  </div>
276
- <div class="summary">${summary}</div>
280
+ <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>
277
282
  <div class="footer">
278
283
  <span class="stat"><b style="color:#009688">热度</b>${views}</span>
279
284
  <span class="stat"><b style="color:#673ab7">评论</b>${comments}</span>
@@ -286,7 +291,7 @@ function apply(ctx, config) {
286
291
  const page = await ctx.puppeteer.page();
287
292
  await injectCookies(page);
288
293
  await page.setContent(html);
289
- await page.setViewport({ width: 660, height: 460, deviceScaleFactor: 2 });
294
+ await page.setViewport({ width: 660, height: 480, deviceScaleFactor: 2 });
290
295
  const el = await page.$(".card");
291
296
  const img = await el.screenshot({ type: "png" });
292
297
  await page.close();
@@ -333,7 +338,7 @@ function apply(ctx, config) {
333
338
  <div class="top-row"><div class="title">${r.title}</div><div class="id-badge">ID: ${r.id}</div></div>
334
339
  <div class="author">By ${r.author} ${r.status ? ` · ${r.status}` : ""}</div>
335
340
  <div class="tags">${r.tags.map((t) => `<span class="tag">${t}</span>`).join("")}</div>
336
- <div class="meta-row">${stats || "暂无数据"}</div>
341
+ <div class="meta-row">${stats || "No Data"}</div>
337
342
  </div></div>`;
338
343
  }).join("")}
339
344
  </div>
@@ -357,22 +362,35 @@ function apply(ctx, config) {
357
362
  <style>
358
363
  body { margin: 0; padding: 0; width: ${config.deviceWidth}px; background-color: #f6f4ec; color: #2c2c2c; font-family: ${fontStack}; }
359
364
  #source-container { display: none; }
365
+
366
+ /* 使用 Flex 布局,让页脚自然占据底部空间,不覆盖正文 */
360
367
  .page {
361
368
  width: ${config.deviceWidth}px; height: ${config.deviceHeight}px;
362
369
  padding: 35px 28px; box-sizing: border-box;
363
370
  position: relative; background: #f6f4ec; overflow: hidden;
364
371
  display: flex; flex-direction: column;
365
372
  }
373
+
366
374
  .page-header {
367
375
  font-size: 12px; color: #8d6e63; border-bottom: 2px solid #d7ccc8;
368
376
  padding-bottom: 12px; margin-bottom: 15px; flex-shrink: 0;
369
377
  display: flex; justify-content: space-between; font-weight: bold;
370
378
  }
379
+
380
+ /* 页脚改为相对定位,并给予上边距 */
371
381
  .page-footer {
372
- position: absolute; bottom: 15px; left: 0; right: 0; text-align: center;
373
- font-size: 12px; color: #aaa; font-family: sans-serif;
382
+ text-align: center; font-size: 12px; color: #aaa; font-family: sans-serif;
383
+ flex-shrink: 0; padding-top: 10px; margin-top: auto;
384
+ }
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;
374
392
  }
375
- .page-content { flex: 1; overflow: hidden; font-size: ${config.fontSize}px; line-height: 1.7; text-align: justify; }
393
+
376
394
  p { margin: 0 0 0.6em 0; text-indent: 2em; }
377
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); }
378
396
  h1, h2, h3 { font-size: 1.1em; margin: 0.5em 0; color: #5d4037; text-indent: 0; font-weight: bold; }
@@ -389,32 +407,42 @@ function apply(ctx, config) {
389
407
  const author = source.querySelector('.meta-info').dataset.author;
390
408
  let pageIndex = 1;
391
409
  let currentPageContent = null;
410
+
392
411
  function createNewPage() {
393
412
  const page = document.createElement('div'); page.className = 'page';
394
413
  const header = document.createElement('div'); header.className = 'page-header';
395
414
  header.innerHTML = \`<span>\${title.substring(0, 12) + (title.length>12?'...':'')}</span><span>\${author}</span>\`;
396
415
  page.appendChild(header);
416
+
397
417
  const content = document.createElement('div'); content.className = 'page-content';
398
418
  page.appendChild(content);
419
+
399
420
  const footer = document.createElement('div'); footer.className = 'page-footer';
400
421
  footer.id = 'footer-' + pageIndex;
401
422
  page.appendChild(footer);
423
+
402
424
  output.appendChild(page);
403
425
  currentPageContent = content;
404
426
  return page;
405
427
  }
428
+
406
429
  createNewPage();
407
430
  const children = Array.from(source.children);
431
+
408
432
  for (const child of children) {
409
433
  if (child.className === 'meta-info') continue;
434
+
410
435
  currentPageContent.appendChild(child.cloneNode(true));
411
- if (currentPageContent.scrollHeight > currentPageContent.clientHeight) {
436
+
437
+ // 核心修复:增加 10px 的安全缓冲,防止 descenders 被切
438
+ if (currentPageContent.scrollHeight > currentPageContent.clientHeight - 10) {
412
439
  currentPageContent.removeChild(currentPageContent.lastChild);
413
440
  pageIndex++;
414
441
  createNewPage();
415
442
  currentPageContent.appendChild(child.cloneNode(true));
416
443
  }
417
444
  }
445
+
418
446
  for(let i=1; i<=pageIndex; i++) document.getElementById('footer-'+i).innerText = \`- \${i} / \${pageIndex} -\`;
419
447
  return pageIndex;
420
448
  }
@@ -482,7 +510,8 @@ function apply(ctx, config) {
482
510
  if (!results.length) return "未找到结果。";
483
511
  const img = await renderSearchResults(keyword, results);
484
512
  await session.send(import_koishi.h.image(img, "image/png"));
485
- return "Tip: 发送 /ft.read <ID> 阅读";
513
+ const exampleId = results[0]?.id || "12345";
514
+ return `Tip: 发送 /ft.read [ID] 阅读 (例: /ft.read ${exampleId})`;
486
515
  });
487
516
  ctx.command("ft.sub <threadId:string>", "订阅").action(async ({ session }, threadId) => {
488
517
  if (!/^\d+$/.test(threadId)) return "ID错误";
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.6",
4
+ "version": "0.0.8",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [