koishi-plugin-fimtale-api 1.0.3 → 1.0.5
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 +57 -43
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -166,7 +166,6 @@ function apply(ctx, config) {
|
|
|
166
166
|
const t = c.textContent?.trim();
|
|
167
167
|
if (t && !["连载中", "已完结", "已弃坑"].includes(t) && !t.includes("展开")) tags.push(t);
|
|
168
168
|
});
|
|
169
|
-
const status = Array.from(card.querySelectorAll(".chip")).find((c) => ["连载中", "已完结", "已弃坑"].includes(c.textContent?.trim() || ""))?.textContent?.trim() || "";
|
|
170
169
|
const stats = { views: "0", comments: "0", likes: "0", words: "0" };
|
|
171
170
|
card.querySelectorAll(".card-action > div span[title]").forEach((s) => {
|
|
172
171
|
const t = s.getAttribute("title") || "";
|
|
@@ -176,6 +175,7 @@ function apply(ctx, config) {
|
|
|
176
175
|
if (t.includes("评论")) stats.comments = v;
|
|
177
176
|
});
|
|
178
177
|
stats.likes = card.querySelector(".left.green-text")?.textContent?.replace(/[^0-9]/g, "") || "0";
|
|
178
|
+
const status = Array.from(card.querySelectorAll(".chip")).find((c) => ["连载中", "已完结", "已弃坑"].includes(c.textContent?.trim() || ""))?.textContent?.trim() || "";
|
|
179
179
|
let updateTime = "";
|
|
180
180
|
const timeTxt = card.querySelector('div[style*="margin: 3px 0;"] span.grey-text')?.textContent || "";
|
|
181
181
|
const dateMatch = timeTxt.match(/(\d{4}\s*年\s*\d{1,2}\s*月\s*\d{1,2}\s*日)/) || timeTxt.match(/(\d+\s*(?:小时|分钟|天)前)/) || timeTxt.match(/(\d{1,2}\s*月\s*\d{1,2}\s*日)/);
|
|
@@ -211,16 +211,23 @@ function apply(ctx, config) {
|
|
|
211
211
|
const likes = isChapter && parent ? parent.Upvotes || 0 : info.Upvotes || 0;
|
|
212
212
|
const html = `<!DOCTYPE html><html><head><style>
|
|
213
213
|
body { margin: 0; padding: 0; font-family: ${fontStack}; background: transparent; }
|
|
214
|
-
|
|
214
|
+
/* 增加卡片高度到 400px 以容纳副标题,防止内容挤压 */
|
|
215
|
+
.card { width: 620px; height: 400px; background: #fff; border-radius: 16px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); display: flex; overflow: hidden; }
|
|
215
216
|
.cover { width: 220px; height: 100%; ${bgStyle} background-size: cover; background-position: center; position: relative; flex-shrink: 0; }
|
|
216
|
-
|
|
217
|
+
/* 更改 ID 字体 */
|
|
218
|
+
.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: "Impact", "Arial Black", sans-serif; letter-spacing: 1px; }
|
|
217
219
|
.info { flex: 1; padding: 24px; display: flex; flex-direction: column; overflow: hidden; position: relative; }
|
|
220
|
+
|
|
221
|
+
.header-group { flex-shrink: 0; margin-bottom: 16px; border-bottom: 1px dashed #f0f0f0; padding-bottom: 12px; }
|
|
218
222
|
.title { font-size: 22px; font-weight: 700; color: #333; line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin-bottom: 4px; }
|
|
219
223
|
.subtitle { font-size: 15px; color: #555; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-left: 10px; border-left: 3px solid #e91e63; margin-top: 4px; }
|
|
220
|
-
.author { font-size:
|
|
224
|
+
.author { font-size: 14px; color: #777; margin-top: 10px; font-weight: 400; display:flex; align-items:center; }
|
|
225
|
+
|
|
221
226
|
.tags { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; flex-shrink: 0; }
|
|
222
227
|
.tag { background: #eff2f5; color: #5c6b7f; padding: 3px 9px; border-radius: 4px; font-size: 11px; font-weight: 500; }
|
|
223
|
-
|
|
228
|
+
|
|
229
|
+
/* 增加底部边距,防止贴到底部线条 */
|
|
230
|
+
.summary-box { flex: 1; position: relative; overflow: hidden; min-height: 0; margin-bottom: 12px; }
|
|
224
231
|
.summary { font-size: 13px; color: #666; line-height: 1.6; display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; padding-bottom: 3px; }
|
|
225
232
|
.footer { border-top: 1px solid #eee; padding-top: 14px; display: flex; justify-content: space-between; font-size: 12px; color: #888; margin-top: auto; flex-shrink: 0; }
|
|
226
233
|
.stat b { color: #555; font-weight: bold; margin-right: 2px;}
|
|
@@ -235,12 +242,15 @@ function apply(ctx, config) {
|
|
|
235
242
|
<span class="stat"><b style="color:#4caf50">赞</b>${likes}</span><span class="stat"><b style="color:#795548">字数</b>${info.WordCount || 0}</span>
|
|
236
243
|
</div></div></div></body></html>`;
|
|
237
244
|
const page = await ctx.puppeteer.page();
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
245
|
+
try {
|
|
246
|
+
await injectCookies(page);
|
|
247
|
+
await page.setContent(html);
|
|
248
|
+
await page.setViewport({ width: 660, height: 400, deviceScaleFactor: 3 });
|
|
249
|
+
const img = await page.$(".card").then((e) => e.screenshot({ type: "jpeg", quality: 100 }));
|
|
250
|
+
return img;
|
|
251
|
+
} finally {
|
|
252
|
+
await page.close();
|
|
253
|
+
}
|
|
244
254
|
}, "renderCard");
|
|
245
255
|
const renderSearchResults = /* @__PURE__ */ __name(async (keyword, results) => {
|
|
246
256
|
const html = `<!DOCTYPE html><html><head><style>
|
|
@@ -248,6 +258,7 @@ function apply(ctx, config) {
|
|
|
248
258
|
.container { background: #fff; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 20px rgba(0,0,0,0.15); margin: 10px; }
|
|
249
259
|
.header { background: #fafafa; padding: 15px 20px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; }
|
|
250
260
|
.header-title { font-size: 16px; font-weight: bold; color: #333; }
|
|
261
|
+
.list { padding: 0; }
|
|
251
262
|
.item { display: flex; padding: 15px; border-bottom: 1px solid #f5f5f5; height: 110px; align-items: flex-start; }
|
|
252
263
|
.cover-box { width: 75px; height: 100%; border-radius: 6px; overflow: hidden; flex-shrink: 0; margin-right: 15px; background: #eee; }
|
|
253
264
|
.cover-img { width: 100%; height: 100%; object-fit: cover; }
|
|
@@ -272,11 +283,14 @@ function apply(ctx, config) {
|
|
|
272
283
|
}).join("")}
|
|
273
284
|
</div></div></body></html>`;
|
|
274
285
|
const page = await ctx.puppeteer.page();
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
286
|
+
try {
|
|
287
|
+
await page.setContent(html);
|
|
288
|
+
await page.setViewport({ width: 550, height: 800, deviceScaleFactor: 3 });
|
|
289
|
+
const img = await page.$(".container").then((e) => e.screenshot({ type: "jpeg", quality: 100 }));
|
|
290
|
+
return img;
|
|
291
|
+
} finally {
|
|
292
|
+
await page.close();
|
|
293
|
+
}
|
|
280
294
|
}, "renderSearchResults");
|
|
281
295
|
const renderReadPages = /* @__PURE__ */ __name(async (info) => {
|
|
282
296
|
const content = cleanContent(info.Content);
|
|
@@ -298,44 +312,31 @@ function apply(ctx, config) {
|
|
|
298
312
|
.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: #aaa; background: #f6f4ec; z-index: 5; }
|
|
299
313
|
#viewport { position: absolute; top: ${marginTop}px; left: ${paddingX}px; width: ${contentWidth}px; height: ${optimalContentHeight}px; overflow: hidden; }
|
|
300
314
|
#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; }
|
|
301
|
-
|
|
302
315
|
p, div { margin: 0 0 0.2em 0; text-indent: 2em; word-wrap: break-word; overflow-wrap: break-word; }
|
|
303
|
-
|
|
304
316
|
.align-center { text-align: center !important; text-align-last: center !important; text-indent: 0 !important; margin: 0.8em 0; font-weight: bold; color: #5d4037; }
|
|
305
317
|
.align-right { text-align: right !important; text-indent: 0 !important; margin-top: 0.5em; color: #666; font-style: italic; }
|
|
306
318
|
.no-indent { text-indent: 0 !important; }
|
|
307
|
-
|
|
308
319
|
blockquote { margin: 1em 0.5em; padding-left: 1em; border-left: 4px solid #d7ccc8; color: #666; }
|
|
309
320
|
blockquote p { text-indent: 0; margin: 0.3em 0; }
|
|
310
|
-
|
|
311
321
|
ul, ol { margin: 0.5em 0; padding-left: 1.5em; }
|
|
312
322
|
li { margin-bottom: 0.2em; }
|
|
313
|
-
|
|
314
323
|
hr { border: 0; height: 1px; background: #d7ccc8; margin: 1.5em 0; }
|
|
315
|
-
|
|
316
324
|
table { width: 100%; border-collapse: collapse; margin: 1em 0; font-size: 0.9em; }
|
|
317
325
|
th, td { border: 1px solid #ccc; padding: 4px; text-align: left; }
|
|
318
326
|
th { background: #eee; font-weight: bold; }
|
|
319
|
-
|
|
320
327
|
pre { background: #eee; padding: 0.5em; overflow-x: auto; border-radius: 4px; margin: 0.5em 0; }
|
|
321
328
|
code { font-family: monospace; background: #f0f0f0; padding: 2px 4px; border-radius: 3px; }
|
|
322
|
-
|
|
323
329
|
s, strike, del { text-decoration: line-through; color: #888; }
|
|
324
330
|
u { text-decoration: underline; }
|
|
325
331
|
sup, sub { font-size: 0.75em; line-height: 0; position: relative; vertical-align: baseline; }
|
|
326
332
|
sup { top: -0.5em; }
|
|
327
333
|
sub { bottom: -0.25em; }
|
|
328
|
-
|
|
329
334
|
a { color: #0277bd; text-decoration: none; }
|
|
330
|
-
|
|
331
335
|
figure.img-box { display: flex; justify-content: center; align-items: center; margin: 0.5em 0; width: 100%; }
|
|
332
336
|
img { max-width: 100%; height: auto; display: block; border-radius: 6px; box-shadow: 0 2px 6px rgba(0,0,0,0.1); }
|
|
333
|
-
|
|
334
337
|
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; }
|
|
335
|
-
|
|
336
338
|
strong, b { font-weight: 900; color: #3e2723; }
|
|
337
339
|
em, i { font-style: italic; }
|
|
338
|
-
|
|
339
340
|
p:last-child { margin-bottom: 0; }
|
|
340
341
|
</style></head><body>
|
|
341
342
|
<div class="fixed-header"><span>${info.Title.substring(0, 12) + (info.Title.length > 12 ? "..." : "")}</span><span>${info.UserName}</span></div>
|
|
@@ -345,7 +346,7 @@ function apply(ctx, config) {
|
|
|
345
346
|
try {
|
|
346
347
|
await injectCookies(page);
|
|
347
348
|
await page.setContent(html);
|
|
348
|
-
await page.setViewport({ width: config.deviceWidth, height: config.deviceHeight, deviceScaleFactor:
|
|
349
|
+
await page.setViewport({ width: config.deviceWidth, height: config.deviceHeight, deviceScaleFactor: 3 });
|
|
349
350
|
await page.evaluate(async () => {
|
|
350
351
|
await document.fonts.ready;
|
|
351
352
|
await new Promise((resolve) => {
|
|
@@ -377,7 +378,7 @@ function apply(ctx, config) {
|
|
|
377
378
|
document.getElementById("content-scroller").style.transform = `translateX(${offset}px)`;
|
|
378
379
|
document.getElementById("page-indicator").innerText = `- ${curr} / ${total} -`;
|
|
379
380
|
}, i, step, i + 1, finalPages);
|
|
380
|
-
imgs.push(await page.screenshot({ type: "jpeg", quality:
|
|
381
|
+
imgs.push(await page.screenshot({ type: "jpeg", quality: 100 }));
|
|
381
382
|
}
|
|
382
383
|
return imgs;
|
|
383
384
|
} finally {
|
|
@@ -388,7 +389,7 @@ function apply(ctx, config) {
|
|
|
388
389
|
if (!threadId) return "请输入ID";
|
|
389
390
|
const res = await fetchThread(threadId);
|
|
390
391
|
if (!res.valid) return `[错误] ${res.msg}`;
|
|
391
|
-
return session.send(import_koishi.h.image(await renderCard(res.data, res.parent), "image/
|
|
392
|
+
return session.send(import_koishi.h.image(await renderCard(res.data, res.parent), "image/jpeg"));
|
|
392
393
|
});
|
|
393
394
|
ctx.command("ft.read <threadId:string>", "阅读章节").action(async ({ session }, threadId) => {
|
|
394
395
|
if (!threadId) return "请输入ID";
|
|
@@ -397,12 +398,12 @@ function apply(ctx, config) {
|
|
|
397
398
|
await session.send(`[加载中] ${res.data.Title}...`);
|
|
398
399
|
try {
|
|
399
400
|
const cardImg = await renderCard(res.data, res.parent);
|
|
400
|
-
await session.send(import_koishi.h.image(cardImg, "image/
|
|
401
|
+
await session.send(import_koishi.h.image(cardImg, "image/jpeg"));
|
|
401
402
|
const pages = await renderReadPages(res.data);
|
|
402
403
|
const nodes = pages.map((buf) => (0, import_koishi.h)("message", import_koishi.h.image(buf, "image/jpeg")));
|
|
403
404
|
const navs = [];
|
|
404
405
|
const mainId = res.parent ? res.parent.ID : res.data.ID;
|
|
405
|
-
navs.push(`[
|
|
406
|
+
navs.push(`[首页] /ft.read ${mainId}`);
|
|
406
407
|
if (res.menu?.length) {
|
|
407
408
|
const idx = res.menu.findIndex((m) => m.ID.toString() === threadId);
|
|
408
409
|
if (idx > 0) navs.push(`[上一章] /ft.read ${res.menu[idx - 1].ID}`);
|
|
@@ -420,7 +421,7 @@ function apply(ctx, config) {
|
|
|
420
421
|
if (!id) return "[错误] 获取失败";
|
|
421
422
|
const res = await fetchThread(id);
|
|
422
423
|
if (!res.valid) return `[错误] ID:${id} 读取失败`;
|
|
423
|
-
await session.send(import_koishi.h.image(await renderCard(res.data, res.parent), "image/
|
|
424
|
+
await session.send(import_koishi.h.image(await renderCard(res.data, res.parent), "image/jpeg"));
|
|
424
425
|
return `Tip: 发送 /ft.read ${res.data.ID} 阅读全文`;
|
|
425
426
|
});
|
|
426
427
|
ctx.command("ft.search <keyword:text>", "搜索作品").action(async ({ session }, keyword) => {
|
|
@@ -428,7 +429,7 @@ function apply(ctx, config) {
|
|
|
428
429
|
await session.send("[加载中] 搜索中...");
|
|
429
430
|
const results = await searchThreads(keyword);
|
|
430
431
|
if (!results.length) return "未找到结果。";
|
|
431
|
-
await session.send(import_koishi.h.image(await renderSearchResults(keyword, results), "image/
|
|
432
|
+
await session.send(import_koishi.h.image(await renderSearchResults(keyword, results), "image/jpeg"));
|
|
432
433
|
const exampleId = results[0]?.id || "12345";
|
|
433
434
|
return `Tip: 发送 /ft.read [ID] 阅读 (例: /ft.read ${exampleId})`;
|
|
434
435
|
});
|
|
@@ -440,7 +441,7 @@ function apply(ctx, config) {
|
|
|
440
441
|
if (!res.valid) return "帖子不存在";
|
|
441
442
|
await ctx.database.create("fimtale_subs", { cid: session.cid, threadId, lastCount: res.data.Comments, lastCheck: Date.now() });
|
|
442
443
|
await session.send("[成功] 订阅成功");
|
|
443
|
-
return session.send(import_koishi.h.image(await renderCard(res.data, res.parent), "image/
|
|
444
|
+
return session.send(import_koishi.h.image(await renderCard(res.data, res.parent), "image/jpeg"));
|
|
444
445
|
});
|
|
445
446
|
ctx.command("ft.unsub <threadId:string>", "退订").action(async ({ session }, threadId) => {
|
|
446
447
|
const res = await ctx.database.remove("fimtale_subs", { cid: session.cid, threadId });
|
|
@@ -448,14 +449,27 @@ function apply(ctx, config) {
|
|
|
448
449
|
});
|
|
449
450
|
ctx.middleware(async (session, next) => {
|
|
450
451
|
if (!config.autoParseLink) return next();
|
|
451
|
-
const
|
|
452
|
-
if (
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
452
|
+
const matches = [...session.content.matchAll(/fimtale\.com\/t\/(\d+)/g)];
|
|
453
|
+
if (matches.length === 0) return next();
|
|
454
|
+
const uniqueIds = [...new Set(matches.map((m) => m[1]))];
|
|
455
|
+
if (session.userId === session.selfId) return next();
|
|
456
|
+
const messageNodes = [];
|
|
457
|
+
for (const id of uniqueIds) {
|
|
458
|
+
try {
|
|
459
|
+
const res = await fetchThread(id);
|
|
460
|
+
if (res.valid) {
|
|
461
|
+
const img = await renderCard(res.data, res.parent);
|
|
462
|
+
messageNodes.push((0, import_koishi.h)("message", import_koishi.h.image(img, "image/jpeg")));
|
|
463
|
+
}
|
|
464
|
+
} catch (e) {
|
|
456
465
|
}
|
|
457
466
|
}
|
|
458
|
-
return next();
|
|
467
|
+
if (messageNodes.length === 0) return next();
|
|
468
|
+
if (messageNodes.length === 1) {
|
|
469
|
+
return session.send(messageNodes[0].children);
|
|
470
|
+
} else {
|
|
471
|
+
return session.send((0, import_koishi.h)("message", { forward: true }, messageNodes));
|
|
472
|
+
}
|
|
459
473
|
});
|
|
460
474
|
ctx.setInterval(async () => {
|
|
461
475
|
const subs = await ctx.database.get("fimtale_subs", {});
|