koishi-plugin-fimtale-api 1.0.4 → 1.0.6
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.d.ts +1 -1
- package/lib/index.js +119 -51
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -38,7 +38,7 @@ __export(src_exports, {
|
|
|
38
38
|
module.exports = __toCommonJS(src_exports);
|
|
39
39
|
var import_koishi = require("koishi");
|
|
40
40
|
var import_crypto = __toESM(require("crypto"));
|
|
41
|
-
var name = "fimtale-
|
|
41
|
+
var name = "fimtale-api";
|
|
42
42
|
var inject = ["puppeteer", "database", "http"];
|
|
43
43
|
var Config = import_koishi.Schema.object({
|
|
44
44
|
apiUrl: import_koishi.Schema.string().default("https://fimtale.com/api/v1").description("Fimtale API 基础路径"),
|
|
@@ -47,7 +47,6 @@ 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
|
-
// 渲染配置 (标准手机比例)
|
|
51
50
|
deviceWidth: import_koishi.Schema.number().default(390).description("阅读器渲染宽度(px)"),
|
|
52
51
|
deviceHeight: import_koishi.Schema.number().default(844).description("阅读器渲染高度(px)"),
|
|
53
52
|
fontSize: import_koishi.Schema.number().default(20).description("正文字号(px)")
|
|
@@ -85,7 +84,7 @@ function apply(ctx, config) {
|
|
|
85
84
|
processed = processed.replace(/<div class="card-panel[\s\S]*?<\/div>/i, "");
|
|
86
85
|
processed = processed.replace(/<script\b[^>]*>([\s\S]*?)<\/script>/gmi, "");
|
|
87
86
|
processed = processed.replace(/<style\b[^>]*>([\s\S]*?)<\/style>/gmi, "");
|
|
88
|
-
processed = processed.replace(/<iframe[^>]*>.*?<\/iframe>/gmi, '<p class="align-center" style="color:#
|
|
87
|
+
processed = processed.replace(/<iframe[^>]*>.*?<\/iframe>/gmi, '<p class="align-center" style="color:#78909C;font-size:0.8em;">[多媒体内容]</p>');
|
|
89
88
|
processed = processed.replace(
|
|
90
89
|
/<div class="material-placeholder">([\s\S]*?)<\/div>/gi,
|
|
91
90
|
(match, content) => {
|
|
@@ -212,34 +211,74 @@ function apply(ctx, config) {
|
|
|
212
211
|
const likes = isChapter && parent ? parent.Upvotes || 0 : info.Upvotes || 0;
|
|
213
212
|
const html = `<!DOCTYPE html><html><head><style>
|
|
214
213
|
body { margin: 0; padding: 0; font-family: ${fontStack}; background: transparent; }
|
|
215
|
-
.card { width: 620px; height:
|
|
216
|
-
.cover { width: 220px; height: 100%; ${bgStyle} background-size: cover; background-position: center; position: relative; flex-shrink: 0; }
|
|
217
|
-
.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; }
|
|
218
|
-
.info { flex: 1; padding: 24px; display: flex; flex-direction: column; overflow: hidden; position: relative; }
|
|
214
|
+
.card { width: 620px; min-height: 420px; background: #fff; border-radius: 16px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); display: flex; overflow: hidden; }
|
|
215
|
+
.cover { width: 220px; min-height: 100%; ${bgStyle} background-size: cover; background-position: center; position: relative; flex-shrink: 0; }
|
|
219
216
|
|
|
220
|
-
/*
|
|
221
|
-
.
|
|
222
|
-
|
|
223
|
-
|
|
217
|
+
/* ID Badge 分体式:使用 flex 强制垂直居中,消除字体基线差异 */
|
|
218
|
+
.id-badge-container {
|
|
219
|
+
position: absolute; top: 15px; left: 15px;
|
|
220
|
+
display: flex;
|
|
221
|
+
box-shadow: 0 4px 12px rgba(238,110,115, 0.3);
|
|
222
|
+
border-radius: 6px;
|
|
223
|
+
overflow: hidden;
|
|
224
|
+
border: 1px solid rgba(255,255,255,0.3);
|
|
225
|
+
height: 28px; /* 强制高度 */
|
|
226
|
+
}
|
|
227
|
+
.id-label {
|
|
228
|
+
background: #EE6E73;
|
|
229
|
+
color: #fff;
|
|
230
|
+
padding: 0 10px;
|
|
231
|
+
font-size: 12px;
|
|
232
|
+
font-weight: bold;
|
|
233
|
+
font-family: sans-serif;
|
|
234
|
+
text-transform: uppercase;
|
|
235
|
+
display: flex; align-items: center; justify-content: center;
|
|
236
|
+
height: 100%;
|
|
237
|
+
line-height: 1; margin: 0; /* 修复对齐 */
|
|
238
|
+
}
|
|
239
|
+
.id-val {
|
|
240
|
+
background: #fff;
|
|
241
|
+
color: #EE6E73;
|
|
242
|
+
padding: 0 12px;
|
|
243
|
+
font-family: "Consolas", "Monaco", monospace;
|
|
244
|
+
font-size: 15px;
|
|
245
|
+
font-weight: 900;
|
|
246
|
+
display: flex; align-items: center; justify-content: center;
|
|
247
|
+
height: 100%;
|
|
248
|
+
line-height: 1; margin: 0; /* 修复对齐 */
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.info { flex: 1; padding: 26px; display: flex; flex-direction: column; overflow: hidden; position: relative; }
|
|
224
252
|
|
|
225
|
-
|
|
226
|
-
.
|
|
253
|
+
.header-group { flex-shrink: 0; margin-bottom: 16px; border-bottom: 2px solid #f5f5f5; padding-bottom: 12px; }
|
|
254
|
+
.title { font-size: 24px; font-weight: 700; color: #333; line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin-bottom: 6px; }
|
|
255
|
+
.subtitle { font-size: 16px; color: #78909C; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-left: 12px; border-left: 4px solid #EE6E73; margin-top: 6px; }
|
|
227
256
|
|
|
228
|
-
.
|
|
257
|
+
.author { font-size: 14px; color: #78909C; margin-top: 12px; font-weight: 400; display:flex; align-items:center; }
|
|
258
|
+
|
|
259
|
+
.tags { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; flex-shrink: 0; }
|
|
229
260
|
.tag { background: #eff2f5; color: #5c6b7f; padding: 3px 9px; border-radius: 4px; font-size: 11px; font-weight: 500; }
|
|
230
|
-
|
|
231
|
-
.summary {
|
|
232
|
-
.
|
|
233
|
-
|
|
261
|
+
|
|
262
|
+
.summary-box { flex: 1; position: relative; overflow: hidden; min-height: 0; margin-bottom: 16px; }
|
|
263
|
+
.summary { font-size: 14px; color: #546e7a; line-height: 1.7; display: -webkit-box; -webkit-line-clamp: 6; -webkit-box-orient: vertical; overflow: hidden; text-align: justify; }
|
|
264
|
+
|
|
265
|
+
.footer { border-top: 1px solid #eee; padding-top: 14px; display: flex; justify-content: space-between; font-size: 13px; color: #78909C; margin-top: auto; flex-shrink: 0; }
|
|
266
|
+
.stat b { color: #455a64; font-weight: bold; margin-right: 3px;}
|
|
234
267
|
</style></head><body>
|
|
235
|
-
<div class="card"
|
|
268
|
+
<div class="card">
|
|
269
|
+
<div class="cover">
|
|
270
|
+
<div class="id-badge-container">
|
|
271
|
+
<div class="id-label">ID</div>
|
|
272
|
+
<div class="id-val">${info.ID}</div>
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
236
275
|
<div class="info">
|
|
237
276
|
<div class="header-group"><div class="title">${displayTitle}</div>${subTitle ? `<div class="subtitle">${subTitle}</div>` : ""}<div class="author">@${info.UserName}</div></div>
|
|
238
277
|
<div class="tags">${tagsArr.slice(0, 10).map((t) => `<span class="tag">${t}</span>`).join("")}</div>
|
|
239
278
|
<div class="summary-box"><div class="summary">${summary}</div></div>
|
|
240
279
|
<div class="footer">
|
|
241
|
-
<span class="stat"><b style="color:#009688">热度</b>${info.Views || 0}</span><span class="stat"><b style="color:#
|
|
242
|
-
<span class="stat"><b style="color:#4caf50">赞</b>${likes}</span><span class="stat"><b style="color:#
|
|
280
|
+
<span class="stat"><b style="color:#009688">热度</b>${info.Views || 0}</span><span class="stat"><b style="color:#7e57c2">评论</b>${info.Comments || 0}</span>
|
|
281
|
+
<span class="stat"><b style="color:#4caf50">赞</b>${likes}</span><span class="stat"><b style="color:#8d6e63">字数</b>${info.WordCount || 0}</span>
|
|
243
282
|
</div></div></div></body></html>`;
|
|
244
283
|
const page = await ctx.puppeteer.page();
|
|
245
284
|
try {
|
|
@@ -265,8 +304,40 @@ function apply(ctx, config) {
|
|
|
265
304
|
.content { flex: 1; display: flex; flex-direction: column; justify-content: space-between; height: 100%; min-width: 0; }
|
|
266
305
|
.top-row { display: flex; justify-content: space-between; align-items: flex-start; }
|
|
267
306
|
.title { font-size: 16px; font-weight: bold; color: #222; line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; flex:1; margin-right: 8px;}
|
|
268
|
-
|
|
269
|
-
|
|
307
|
+
|
|
308
|
+
/* ID Badge Search: 迷你分体式,保持与大图风格一致但更紧凑 */
|
|
309
|
+
.id-badge {
|
|
310
|
+
display: flex;
|
|
311
|
+
border-radius: 4px;
|
|
312
|
+
overflow: hidden;
|
|
313
|
+
border: 1px solid #EE6E73;
|
|
314
|
+
flex-shrink: 0;
|
|
315
|
+
height: 18px;
|
|
316
|
+
}
|
|
317
|
+
.id-label {
|
|
318
|
+
background: #EE6E73;
|
|
319
|
+
color: #fff;
|
|
320
|
+
padding: 0 4px;
|
|
321
|
+
font-family: sans-serif;
|
|
322
|
+
font-size: 10px;
|
|
323
|
+
font-weight: bold;
|
|
324
|
+
display: flex; align-items: center; justify-content: center;
|
|
325
|
+
height: 100%;
|
|
326
|
+
line-height: 1; margin: 0; /* 修复对齐 */
|
|
327
|
+
}
|
|
328
|
+
.id-val {
|
|
329
|
+
background: #fff;
|
|
330
|
+
color: #EE6E73;
|
|
331
|
+
padding: 0 6px;
|
|
332
|
+
font-family: "Consolas", monospace;
|
|
333
|
+
font-size: 11px;
|
|
334
|
+
font-weight: bold;
|
|
335
|
+
display: flex; align-items: center; justify-content: center;
|
|
336
|
+
height: 100%;
|
|
337
|
+
line-height: 1; margin: 0; /* 修复对齐 */
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.author { font-size: 12px; color: #78909C; }
|
|
270
341
|
.tags { display: flex; gap: 4px; flex-wrap: wrap; height: 18px; overflow: hidden; margin-top: 4px; }
|
|
271
342
|
.tag { background: #f3f3f3; color: #666; padding: 0 5px; border-radius: 3px; font-size: 10px; white-space: nowrap; line-height: 1.6;}
|
|
272
343
|
.meta-row { display: flex; gap: 10px; font-size: 11px; color: #999; margin-top: auto; border-top: 1px dashed #eee; padding-top: 5px; }
|
|
@@ -275,10 +346,15 @@ function apply(ctx, config) {
|
|
|
275
346
|
${results.map((r) => {
|
|
276
347
|
const bg = r.cover ? `<img class="cover-img" src="${r.cover}"/>` : `<div style="width:100%;height:100%;background:${generateGradient(r.title)}"></div>`;
|
|
277
348
|
return `<div class="item"><div class="cover-box">${bg}</div><div class="content">
|
|
278
|
-
<div class="top-row"><div class="title">${r.title}</div
|
|
349
|
+
<div class="top-row"><div class="title">${r.title}</div>
|
|
350
|
+
<div class="id-badge">
|
|
351
|
+
<div class="id-label">ID</div>
|
|
352
|
+
<div class="id-val">${r.id}</div>
|
|
353
|
+
</div>
|
|
354
|
+
</div>
|
|
279
355
|
<div class="author">By ${r.author} ${r.status ? ` · ${r.status}` : ""}</div>
|
|
280
356
|
<div class="tags">${r.tags.map((t) => `<span class="tag">${t}</span>`).join("")}</div>
|
|
281
|
-
<div class="meta-row"><span style="color:#009688"><b>热</b>${r.stats.views}</span><span style="color:#
|
|
357
|
+
<div class="meta-row"><span style="color:#009688"><b>热</b>${r.stats.views}</span><span style="color:#7e57c2"><b>评</b>${r.stats.comments}</span><span style="color:#4caf50"><b>赞</b>${r.stats.likes}</span><span style="margin-left:auto;color:#757575">${r.updateTime}</span></div>
|
|
282
358
|
</div></div>`;
|
|
283
359
|
}).join("")}
|
|
284
360
|
</div></div></body></html>`;
|
|
@@ -308,59 +384,51 @@ function apply(ctx, config) {
|
|
|
308
384
|
const marginTop = Math.floor((maxContentHeight - optimalContentHeight) / 2) + headerHeight;
|
|
309
385
|
const html = `<!DOCTYPE html><html><head><style>
|
|
310
386
|
body { margin: 0; padding: 0; width: ${config.deviceWidth}px; height: ${config.deviceHeight}px; background-color: #f6f4ec; color: #2c2c2c; font-family: ${fontSerif}; overflow: hidden; position: relative;}
|
|
311
|
-
|
|
312
|
-
.fixed-
|
|
387
|
+
/* Header Padding reduced to 12px to align closer to left edge */
|
|
388
|
+
.fixed-header { position: absolute; top: 0; left: 0; width: 100%; height: ${headerHeight}px; border-bottom: 2px solid #EE6E73; box-sizing: border-box; padding: 0 12px; display: flex; align-items: center; justify-content: space-between; font-size: 12px; color: #EE6E73; background: #f6f4ec; z-index: 5; font-weight: bold; }
|
|
389
|
+
.fixed-footer { position: absolute; bottom: 0; left: 0; width: 100%; height: ${footerHeight}px; display: flex; align-items: center; justify-content: center; font-size: 12px; color: #78909C; background: #f6f4ec; z-index: 5; }
|
|
390
|
+
/* 修复1: 强制重置 header 和 footer 的缩进,防止 p, div 全局规则影响 */
|
|
391
|
+
.fixed-header, .fixed-footer, .header-title, .header-author { text-indent: 0 !important; }
|
|
392
|
+
|
|
313
393
|
#viewport { position: absolute; top: ${marginTop}px; left: ${paddingX}px; width: ${contentWidth}px; height: ${optimalContentHeight}px; overflow: hidden; }
|
|
314
|
-
|
|
315
|
-
#content-scroller {
|
|
316
|
-
height: 100%; width: 100%;
|
|
317
|
-
column-width: ${contentWidth}px; column-gap: ${columnGap}px; column-fill: auto;
|
|
318
|
-
padding: ${paddingY}px 0; box-sizing: border-box;
|
|
319
|
-
font-size: ${config.fontSize}px; line-height: ${lineHeightRatio};
|
|
320
|
-
text-align: left; /* 关键:左对齐,解决长空格 */
|
|
321
|
-
transform: translateX(0); transition: none;
|
|
322
|
-
}
|
|
394
|
+
#content-scroller { height: 100%; width: 100%; column-width: ${contentWidth}px; column-gap: ${columnGap}px; column-fill: auto; padding: ${paddingY}px 0; box-sizing: border-box; font-size: ${config.fontSize}px; line-height: ${lineHeightRatio}; text-align: left; transform: translateX(0); transition: none; }
|
|
323
395
|
|
|
324
396
|
p, div { margin: 0 0 0.2em 0; text-indent: 2em; word-wrap: break-word; overflow-wrap: break-word; }
|
|
325
|
-
|
|
326
397
|
.align-center { text-align: center !important; text-align-last: center !important; text-indent: 0 !important; margin: 0.8em 0; font-weight: bold; color: #5d4037; }
|
|
327
398
|
.align-right { text-align: right !important; text-indent: 0 !important; margin-top: 0.5em; color: #666; font-style: italic; }
|
|
328
399
|
.no-indent { text-indent: 0 !important; }
|
|
329
400
|
|
|
330
|
-
|
|
401
|
+
/* Header Title absolute left */
|
|
402
|
+
.header-title { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: left; min-width: 0; margin-right: 10px; }
|
|
403
|
+
.header-author { flex-shrink: 0; color: #78909C; max-width: 35%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: right; }
|
|
404
|
+
|
|
405
|
+
blockquote { margin: 1em 0.5em; padding-left: 1em; border-left: 4px solid #EE6E73; color: #666; }
|
|
331
406
|
blockquote p { text-indent: 0; margin: 0.3em 0; }
|
|
332
|
-
|
|
333
407
|
ul, ol { margin: 0.5em 0; padding-left: 1.5em; }
|
|
334
408
|
li { margin-bottom: 0.2em; }
|
|
335
|
-
|
|
336
409
|
hr { border: 0; height: 1px; background: #d7ccc8; margin: 1.5em 0; }
|
|
337
|
-
|
|
338
410
|
table { width: 100%; border-collapse: collapse; margin: 1em 0; font-size: 0.9em; }
|
|
339
411
|
th, td { border: 1px solid #ccc; padding: 4px; text-align: left; }
|
|
340
412
|
th { background: #eee; font-weight: bold; }
|
|
341
|
-
|
|
342
413
|
pre { background: #eee; padding: 0.5em; overflow-x: auto; border-radius: 4px; margin: 0.5em 0; }
|
|
343
414
|
code { font-family: monospace; background: #f0f0f0; padding: 2px 4px; border-radius: 3px; }
|
|
344
|
-
|
|
345
415
|
s, strike, del { text-decoration: line-through; color: #888; }
|
|
346
416
|
u { text-decoration: underline; }
|
|
347
417
|
sup, sub { font-size: 0.75em; line-height: 0; position: relative; vertical-align: baseline; }
|
|
348
418
|
sup { top: -0.5em; }
|
|
349
419
|
sub { bottom: -0.25em; }
|
|
350
|
-
|
|
351
|
-
a { color: #0277bd; text-decoration: none; }
|
|
352
|
-
|
|
420
|
+
a { color: #EE6E73; text-decoration: none; }
|
|
353
421
|
figure.img-box { display: flex; justify-content: center; align-items: center; margin: 0.5em 0; width: 100%; }
|
|
354
422
|
img { max-width: 100%; height: auto; display: block; border-radius: 6px; box-shadow: 0 2px 6px rgba(0,0,0,0.1); }
|
|
355
|
-
|
|
356
423
|
h1, h2, h3 { font-size: 1.1em; margin: 0.8em 0; color: #5d4037; text-indent: 0; font-weight: bold; text-align: center; text-align-last: center; break-after: avoid; }
|
|
357
|
-
|
|
358
424
|
strong, b { font-weight: 900; color: #3e2723; }
|
|
359
425
|
em, i { font-style: italic; }
|
|
360
|
-
|
|
361
426
|
p:last-child { margin-bottom: 0; }
|
|
362
427
|
</style></head><body>
|
|
363
|
-
<div class="fixed-header"
|
|
428
|
+
<div class="fixed-header">
|
|
429
|
+
<div class="header-title">${info.Title}</div>
|
|
430
|
+
<div class="header-author">${info.UserName}</div>
|
|
431
|
+
</div>
|
|
364
432
|
<div id="viewport"><div id="content-scroller">${content}</div></div>
|
|
365
433
|
<div class="fixed-footer" id="page-indicator">- 1 -</div></body></html>`;
|
|
366
434
|
const page = await ctx.puppeteer.page();
|
|
@@ -424,7 +492,7 @@ function apply(ctx, config) {
|
|
|
424
492
|
const nodes = pages.map((buf) => (0, import_koishi.h)("message", import_koishi.h.image(buf, "image/jpeg")));
|
|
425
493
|
const navs = [];
|
|
426
494
|
const mainId = res.parent ? res.parent.ID : res.data.ID;
|
|
427
|
-
navs.push(`[
|
|
495
|
+
navs.push(`[首页] /ft.read ${mainId}`);
|
|
428
496
|
if (res.menu?.length) {
|
|
429
497
|
const idx = res.menu.findIndex((m) => m.ID.toString() === threadId);
|
|
430
498
|
if (idx > 0) navs.push(`[上一章] /ft.read ${res.menu[idx - 1].ID}`);
|