koishi-plugin-fimtale-api 1.0.7 → 1.0.9

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 CHANGED
@@ -23,6 +23,7 @@ export interface Config {
23
23
  deviceWidth: number;
24
24
  deviceHeight: number;
25
25
  fontSize: number;
26
+ debug: boolean;
26
27
  }
27
28
  export declare const Config: Schema<Config>;
28
29
  export declare function apply(ctx: Context, config: Config): void;
package/lib/index.js CHANGED
@@ -49,9 +49,16 @@ var Config = import_koishi.Schema.object({
49
49
  autoParseLink: import_koishi.Schema.boolean().default(true).description("自动解析链接为预览卡片"),
50
50
  deviceWidth: import_koishi.Schema.number().default(390).description("阅读器渲染宽度(px)"),
51
51
  deviceHeight: import_koishi.Schema.number().default(844).description("阅读器渲染高度(px)"),
52
- fontSize: import_koishi.Schema.number().default(20).description("正文字号(px)")
52
+ fontSize: import_koishi.Schema.number().default(20).description("正文字号(px)"),
53
+ debug: import_koishi.Schema.boolean().default(false).description("开启详细调试日志")
53
54
  });
54
55
  function apply(ctx, config) {
56
+ const logger = ctx.logger("fimtale");
57
+ const debugLog = /* @__PURE__ */ __name((msg, ...args) => {
58
+ if (config.debug) {
59
+ logger.info(`[DEBUG] ${msg}`, ...args);
60
+ }
61
+ }, "debugLog");
55
62
  ctx.model.extend("fimtale_subs", {
56
63
  id: "unsigned",
57
64
  cid: "string",
@@ -109,48 +116,67 @@ function apply(ctx, config) {
109
116
  const fontStack = '"Noto Sans SC", "Microsoft YaHei", "PingFang SC", sans-serif';
110
117
  const fontSerif = '"Noto Serif SC", "Source Han Serif SC", "SimSun", serif';
111
118
  const injectCookies = /* @__PURE__ */ __name(async (page) => {
112
- if (!config.cookies) return;
119
+ if (!config.cookies) {
120
+ debugLog("No cookies configured, skipping injection.");
121
+ return;
122
+ }
113
123
  const cookies = config.cookies.split(";").map((pair) => {
114
124
  const parts = pair.trim().split("=");
115
125
  if (parts.length < 2) return null;
116
126
  return { name: parts[0].trim(), value: parts.slice(1).join("=").trim(), domain: "fimtale.com", path: "/" };
117
127
  }).filter((c) => c !== null);
118
- if (cookies.length) await page.setCookie(...cookies);
128
+ if (cookies.length) {
129
+ debugLog(`Injecting ${cookies.length} cookies into Puppeteer page.`);
130
+ await page.setCookie(...cookies);
131
+ }
119
132
  }, "injectCookies");
120
133
  const fetchThread = /* @__PURE__ */ __name(async (threadId) => {
134
+ const startTime = Date.now();
121
135
  try {
122
136
  const url = `${config.apiUrl}/t/${threadId}`;
137
+ debugLog(`Fetching thread info: ${url}`);
123
138
  const params = { APIKey: config.apiKey, APIPass: config.apiPass };
124
139
  const res = await ctx.http.get(url, { params });
125
- if (res.Status !== 1 || !res.TopicInfo) return { valid: false, msg: res.ErrorMessage || "API Error" };
140
+ debugLog(`Fetch thread ${threadId} completed in ${Date.now() - startTime}ms. Status: ${res.Status}`);
141
+ if (res.Status !== 1 || !res.TopicInfo) {
142
+ debugLog(`Fetch error for ${threadId}: ${res.ErrorMessage}`);
143
+ return { valid: false, msg: res.ErrorMessage || "API Error" };
144
+ }
126
145
  return { valid: true, data: res.TopicInfo, parent: res.ParentInfo, menu: res.Menu || [] };
127
146
  } catch (e) {
147
+ logger.error(`Failed to fetch thread ${threadId}:`, e);
128
148
  return { valid: false, msg: "Request Failed" };
129
149
  }
130
150
  }, "fetchThread");
131
151
  const fetchRandomId = /* @__PURE__ */ __name(async () => {
132
152
  try {
153
+ debugLog("Fetching random thread ID...");
133
154
  const headers = config.cookies ? { Cookie: config.cookies } : {};
134
155
  const html = await ctx.http.get("https://fimtale.com/rand", { responseType: "text", headers });
135
156
  let match = html.match(/FimTale\.topic\.init\((\d+)/) || html.match(/data-clipboard-text=".*?\/t\/(\d+)"/);
136
- return match ? match[1] : null;
157
+ const result = match ? match[1] : null;
158
+ debugLog(`Random ID result: ${result}`);
159
+ return result;
137
160
  } catch (e) {
161
+ logger.error("Failed to fetch random ID:", e);
138
162
  return null;
139
163
  }
140
164
  }, "fetchRandomId");
141
165
  const searchThreads = /* @__PURE__ */ __name(async (keyword) => {
142
166
  let page;
167
+ debugLog(`Starting search for keyword: "${keyword}"`);
143
168
  try {
144
169
  page = await ctx.puppeteer.page();
145
170
  await page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36");
146
171
  await injectCookies(page);
147
172
  const searchUrl = `https://fimtale.com/topics?q=${encodeURIComponent(keyword)}`;
173
+ debugLog(`Navigating to: ${searchUrl}`);
148
174
  await page.goto(searchUrl, { waitUntil: "networkidle2", timeout: 25e3 });
149
175
  try {
150
176
  await page.waitForSelector(".card", { timeout: 5e3 });
151
177
  } catch {
152
178
  }
153
- return await page.evaluate(() => {
179
+ const results = await page.evaluate(() => {
154
180
  const items = [];
155
181
  document.querySelectorAll(".card.topic-card").forEach((card) => {
156
182
  if (items.length >= 6) return;
@@ -184,24 +210,48 @@ function apply(ctx, config) {
184
210
  });
185
211
  return items;
186
212
  });
213
+ debugLog(`Search found ${results.length} items.`);
214
+ return results;
187
215
  } catch (e) {
216
+ logger.error("Search error:", e);
188
217
  return [];
189
218
  } finally {
190
- if (page) await page.close();
219
+ if (page) {
220
+ await page.close();
221
+ debugLog("Search page closed.");
222
+ }
191
223
  }
192
224
  }, "searchThreads");
193
225
  const renderCard = /* @__PURE__ */ __name(async (info, parent) => {
226
+ debugLog(`Rendering Card for ID: ${info.ID}`);
194
227
  const isChapter = info.IsChapter || !!parent && parent.ID !== info.ID;
195
228
  const displayTitle = isChapter && parent ? parent.Title : info.Title;
196
229
  let displayCover = info.Background || extractImage(info.Content);
197
230
  if (!displayCover && parent) {
198
- displayCover = parent.Background || extractImage(parent.Content);
231
+ displayCover = parent.Background;
232
+ if (!displayCover) {
233
+ if (parent.Content) {
234
+ displayCover = extractImage(parent.Content);
235
+ } else {
236
+ try {
237
+ debugLog(`Missing parent content/cover for chapter ${info.ID}, fetching parent ${parent.ID}...`);
238
+ const parentRes = await fetchThread(parent.ID.toString());
239
+ if (parentRes.valid && parentRes.data) {
240
+ displayCover = parentRes.data.Background || extractImage(parentRes.data.Content);
241
+ }
242
+ } catch (e) {
243
+ debugLog("Fetch parent for cover failed (non-critical).");
244
+ }
245
+ }
246
+ }
199
247
  }
200
248
  const displayTagsObj = isChapter && parent ? parent.Tags : info.Tags;
201
249
  const subTitle = isChapter ? info.Title : null;
202
250
  const bgStyle = displayCover ? `background-image: url('${displayCover}');` : `background: ${generateGradient(displayTitle)};`;
203
251
  let summary = stripHtml(info.Content);
204
- if (summary.length < 10 && parent && isChapter) summary = stripHtml(parent.Content);
252
+ if (summary.length < 10 && parent && isChapter) {
253
+ summary = stripHtml(parent.Content);
254
+ }
205
255
  if (summary.length > 150) summary = summary.substring(0, 150) + "...";
206
256
  if (!summary) summary = "暂无简介";
207
257
  const tagsArr = [];
@@ -213,8 +263,6 @@ function apply(ctx, config) {
213
263
  body { margin: 0; padding: 0; font-family: ${fontStack}; background: transparent; }
214
264
  .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
265
  .cover { width: 220px; min-height: 100%; ${bgStyle} background-size: cover; background-position: center; position: relative; flex-shrink: 0; }
216
-
217
- /* ID Badge 分体式:使用 flex 强制垂直居中,消除字体基线差异 */
218
266
  .id-badge-container {
219
267
  position: absolute; top: 15px; left: 15px;
220
268
  display: flex;
@@ -222,7 +270,7 @@ function apply(ctx, config) {
222
270
  border-radius: 6px;
223
271
  overflow: hidden;
224
272
  border: 1px solid rgba(255,255,255,0.3);
225
- height: 28px; /* 强制高度 */
273
+ height: 28px;
226
274
  }
227
275
  .id-label {
228
276
  background: #EE6E73;
@@ -234,7 +282,7 @@ function apply(ctx, config) {
234
282
  text-transform: uppercase;
235
283
  display: flex; align-items: center; justify-content: center;
236
284
  height: 100%;
237
- line-height: 1; margin: 0; /* 修复对齐 */
285
+ line-height: 1; margin: 0;
238
286
  }
239
287
  .id-val {
240
288
  background: #fff;
@@ -245,23 +293,17 @@ function apply(ctx, config) {
245
293
  font-weight: 900;
246
294
  display: flex; align-items: center; justify-content: center;
247
295
  height: 100%;
248
- line-height: 1; margin: 0; /* 修复对齐 */
296
+ line-height: 1; margin: 0;
249
297
  }
250
-
251
298
  .info { flex: 1; padding: 26px; display: flex; flex-direction: column; overflow: hidden; position: relative; }
252
-
253
299
  .header-group { flex-shrink: 0; margin-bottom: 16px; border-bottom: 2px solid #f5f5f5; padding-bottom: 12px; }
254
300
  .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
301
  .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; }
256
-
257
302
  .author { font-size: 14px; color: #78909C; margin-top: 12px; font-weight: 400; display:flex; align-items:center; }
258
-
259
303
  .tags { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; flex-shrink: 0; }
260
304
  .tag { background: #eff2f5; color: #5c6b7f; padding: 3px 9px; border-radius: 4px; font-size: 11px; font-weight: 500; }
261
-
262
305
  .summary-box { flex: 1; position: relative; overflow: hidden; min-height: 0; margin-bottom: 16px; }
263
306
  .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
307
  .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
308
  .stat b { color: #455a64; font-weight: bold; margin-right: 3px;}
267
309
  </style></head><body>
@@ -280,18 +322,24 @@ function apply(ctx, config) {
280
322
  <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
323
  <span class="stat"><b style="color:#4caf50">赞</b>${likes}</span><span class="stat"><b style="color:#8d6e63">字数</b>${info.WordCount || 0}</span>
282
324
  </div></div></div></body></html>`;
325
+ debugLog(`Card HTML prepared (approx ${html.length} chars). Launching Puppeteer...`);
283
326
  const page = await ctx.puppeteer.page();
284
327
  try {
285
328
  await injectCookies(page);
286
329
  await page.setContent(html);
287
330
  await page.setViewport({ width: 660, height: 480, deviceScaleFactor: 3 });
288
331
  const img = await page.$(".card").then((e) => e.screenshot({ type: "jpeg", quality: 100 }));
332
+ debugLog("Card screenshot captured successfully.");
289
333
  return img;
334
+ } catch (e) {
335
+ logger.error("Error rendering card:", e);
336
+ throw e;
290
337
  } finally {
291
338
  await page.close();
292
339
  }
293
340
  }, "renderCard");
294
341
  const renderSearchResults = /* @__PURE__ */ __name(async (keyword, results) => {
342
+ debugLog(`Rendering search results: ${results.length} items`);
295
343
  const html = `<!DOCTYPE html><html><head><style>
296
344
  body { margin: 0; padding: 0; font-family: ${fontStack}; width: 500px; background: transparent; }
297
345
  .container { background: #fff; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 20px rgba(0,0,0,0.15); margin: 10px; }
@@ -304,8 +352,6 @@ function apply(ctx, config) {
304
352
  .content { flex: 1; display: flex; flex-direction: column; justify-content: space-between; height: 100%; min-width: 0; }
305
353
  .top-row { display: flex; justify-content: space-between; align-items: flex-start; }
306
354
  .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;}
307
-
308
- /* ID Badge Search: 迷你分体式,保持与大图风格一致但更紧凑 */
309
355
  .id-badge {
310
356
  display: flex;
311
357
  border-radius: 4px;
@@ -323,7 +369,7 @@ function apply(ctx, config) {
323
369
  font-weight: bold;
324
370
  display: flex; align-items: center; justify-content: center;
325
371
  height: 100%;
326
- line-height: 1; margin: 0; /* 修复对齐 */
372
+ line-height: 1; margin: 0;
327
373
  }
328
374
  .id-val {
329
375
  background: #fff;
@@ -334,9 +380,8 @@ function apply(ctx, config) {
334
380
  font-weight: bold;
335
381
  display: flex; align-items: center; justify-content: center;
336
382
  height: 100%;
337
- line-height: 1; margin: 0; /* 修复对齐 */
383
+ line-height: 1; margin: 0;
338
384
  }
339
-
340
385
  .author { font-size: 12px; color: #78909C; }
341
386
  .tags { display: flex; gap: 4px; flex-wrap: wrap; height: 18px; overflow: hidden; margin-top: 4px; }
342
387
  .tag { background: #f3f3f3; color: #666; padding: 0 5px; border-radius: 3px; font-size: 10px; white-space: nowrap; line-height: 1.6;}
@@ -363,12 +408,14 @@ function apply(ctx, config) {
363
408
  await page.setContent(html);
364
409
  await page.setViewport({ width: 550, height: 800, deviceScaleFactor: 3 });
365
410
  const img = await page.$(".container").then((e) => e.screenshot({ type: "jpeg", quality: 100 }));
411
+ debugLog("Search results screenshot taken.");
366
412
  return img;
367
413
  } finally {
368
414
  await page.close();
369
415
  }
370
416
  }, "renderSearchResults");
371
417
  const renderReadPages = /* @__PURE__ */ __name(async (info) => {
418
+ debugLog(`Rendering read pages for ${info.ID} (${info.Title})`);
372
419
  const content = cleanContent(info.Content);
373
420
  const headerHeight = 40;
374
421
  const footerHeight = 30;
@@ -384,10 +431,8 @@ function apply(ctx, config) {
384
431
  const marginTop = Math.floor((maxContentHeight - optimalContentHeight) / 2) + headerHeight;
385
432
  const html = `<!DOCTYPE html><html><head><style>
386
433
  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;}
387
- /* Header Padding reduced to 12px to align closer to left edge */
388
434
  .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
435
  .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
436
  .fixed-header, .fixed-footer, .header-title, .header-author { text-indent: 0 !important; }
392
437
 
393
438
  #viewport { position: absolute; top: ${marginTop}px; left: ${paddingX}px; width: ${contentWidth}px; height: ${optimalContentHeight}px; overflow: hidden; }
@@ -397,11 +442,8 @@ function apply(ctx, config) {
397
442
  .align-center { text-align: center !important; text-align-last: center !important; text-indent: 0 !important; margin: 0.8em 0; font-weight: bold; color: #5d4037; }
398
443
  .align-right { text-align: right !important; text-indent: 0 !important; margin-top: 0.5em; color: #666; font-style: italic; }
399
444
  .no-indent { text-indent: 0 !important; }
400
-
401
- /* Header Title absolute left */
402
445
  .header-title { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: left; min-width: 0; margin-right: 10px; }
403
446
  .header-author { flex-shrink: 0; color: #78909C; max-width: 35%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: right; }
404
-
405
447
  blockquote { margin: 1em 0.5em; padding-left: 1em; border-left: 4px solid #EE6E73; color: #666; }
406
448
  blockquote p { text-indent: 0; margin: 0.3em 0; }
407
449
  ul, ol { margin: 0.5em 0; padding-left: 1.5em; }
@@ -436,6 +478,7 @@ function apply(ctx, config) {
436
478
  await injectCookies(page);
437
479
  await page.setContent(html);
438
480
  await page.setViewport({ width: config.deviceWidth, height: config.deviceHeight, deviceScaleFactor: 3 });
481
+ debugLog("Waiting for fonts and images...");
439
482
  await page.evaluate(async () => {
440
483
  await document.fonts.ready;
441
484
  await new Promise((resolve) => {
@@ -460,6 +503,7 @@ function apply(ctx, config) {
460
503
  const step = contentWidth + columnGap;
461
504
  const totalPages = Math.floor((scrollWidth + columnGap - 10) / step) + 1;
462
505
  const finalPages = Math.max(1, totalPages);
506
+ debugLog(`Content width calculated: ${scrollWidth}px. Splitting into ${finalPages} pages.`);
463
507
  const imgs = [];
464
508
  for (let i = 0; i < finalPages; i++) {
465
509
  await page.evaluate((idx, stepPx, curr, total) => {
@@ -469,18 +513,24 @@ function apply(ctx, config) {
469
513
  }, i, step, i + 1, finalPages);
470
514
  imgs.push(await page.screenshot({ type: "jpeg", quality: 100 }));
471
515
  }
516
+ debugLog("All pages captured.");
472
517
  return imgs;
518
+ } catch (e) {
519
+ logger.error("Error rendering read pages:", e);
520
+ throw e;
473
521
  } finally {
474
522
  await page.close();
475
523
  }
476
524
  }, "renderReadPages");
477
525
  ctx.command("ft.info <threadId:string>", "预览作品").action(async ({ session }, threadId) => {
526
+ debugLog(`CMD ft.info triggered by ${session.userId} for ID: ${threadId}`);
478
527
  if (!threadId) return "请输入ID";
479
528
  const res = await fetchThread(threadId);
480
529
  if (!res.valid) return `[错误] ${res.msg}`;
481
530
  return session.send(import_koishi.h.image(await renderCard(res.data, res.parent), "image/jpeg"));
482
531
  });
483
532
  ctx.command("ft.read <threadId:string>", "阅读章节").action(async ({ session }, threadId) => {
533
+ debugLog(`CMD ft.read triggered by ${session.userId} for ID: ${threadId}`);
484
534
  if (!threadId) return "请输入ID";
485
535
  const res = await fetchThread(threadId);
486
536
  if (!res.valid) return `[错误] 读取失败: ${res.msg}`;
@@ -501,11 +551,12 @@ function apply(ctx, config) {
501
551
  if (navs.length) nodes.push((0, import_koishi.h)("message", import_koishi.h.text("章节导航:\n" + navs.join("\n"))));
502
552
  return session.send((0, import_koishi.h)("message", { forward: true }, nodes));
503
553
  } catch (e) {
504
- ctx.logger("fimtale").error(e);
554
+ logger.error("ft.read rendering failed:", e);
505
555
  return "[错误] 渲染失败";
506
556
  }
507
557
  });
508
558
  ctx.command("ft.random", "随机作品").action(async ({ session }) => {
559
+ debugLog(`CMD ft.random triggered by ${session.userId}`);
509
560
  const id = await fetchRandomId();
510
561
  if (!id) return "[错误] 获取失败";
511
562
  const res = await fetchThread(id);
@@ -514,6 +565,7 @@ function apply(ctx, config) {
514
565
  return `Tip: 发送 /ft.read ${res.data.ID} 阅读全文`;
515
566
  });
516
567
  ctx.command("ft.search <keyword:text>", "搜索作品").action(async ({ session }, keyword) => {
568
+ debugLog(`CMD ft.search triggered by ${session.userId} for "${keyword}"`);
517
569
  if (!keyword) return "请输入关键词";
518
570
  await session.send("[加载中] 搜索中...");
519
571
  const results = await searchThreads(keyword);
@@ -523,6 +575,7 @@ function apply(ctx, config) {
523
575
  return `Tip: 发送 /ft.read [ID] 阅读 (例: /ft.read ${exampleId})`;
524
576
  });
525
577
  ctx.command("ft.sub <threadId:string>", "订阅").action(async ({ session }, threadId) => {
578
+ debugLog(`CMD ft.sub triggered by ${session.userId} for ID: ${threadId}`);
526
579
  if (!/^\d+$/.test(threadId)) return "ID错误";
527
580
  const exist = await ctx.database.get("fimtale_subs", { cid: session.cid, threadId });
528
581
  if (exist.length) return "已订阅";
@@ -533,6 +586,7 @@ function apply(ctx, config) {
533
586
  return session.send(import_koishi.h.image(await renderCard(res.data, res.parent), "image/jpeg"));
534
587
  });
535
588
  ctx.command("ft.unsub <threadId:string>", "退订").action(async ({ session }, threadId) => {
589
+ debugLog(`CMD ft.unsub triggered by ${session.userId} for ID: ${threadId}`);
536
590
  const res = await ctx.database.remove("fimtale_subs", { cid: session.cid, threadId });
537
591
  return res.matched ? "[成功] 已退订" : "未找到订阅";
538
592
  });
@@ -540,6 +594,7 @@ function apply(ctx, config) {
540
594
  if (!config.autoParseLink) return next();
541
595
  const matches = [...session.content.matchAll(/fimtale\.(?:com|net)\/t\/(\d+)/g)];
542
596
  if (matches.length === 0) return next();
597
+ debugLog(`Middleware matched link in channel ${session.channelId}`);
543
598
  const uniqueIds = [...new Set(matches.map((m) => m[1]))];
544
599
  if (session.userId === session.selfId) return next();
545
600
  const messageNodes = [];
@@ -551,6 +606,7 @@ function apply(ctx, config) {
551
606
  messageNodes.push((0, import_koishi.h)("message", import_koishi.h.image(img, "image/jpeg")));
552
607
  }
553
608
  } catch (e) {
609
+ logger.error(`Middleware failed to process link ${id}:`, e);
554
610
  }
555
611
  }
556
612
  if (messageNodes.length === 0) return next();
@@ -563,12 +619,14 @@ function apply(ctx, config) {
563
619
  ctx.setInterval(async () => {
564
620
  const subs = await ctx.database.get("fimtale_subs", {});
565
621
  if (!subs.length) return;
622
+ debugLog(`Polling check started for ${subs.length} subscriptions.`);
566
623
  const tids = [...new Set(subs.map((s) => s.threadId))];
567
624
  for (const tid of tids) {
568
625
  const res = await fetchThread(tid);
569
626
  if (!res.valid) continue;
570
627
  const targets = subs.filter((s) => s.threadId === tid && s.lastCount < res.data.Comments);
571
628
  if (targets.length) {
629
+ debugLog(`Update found for thread ${tid} (Old: ${targets[0].lastCount}, New: ${res.data.Comments})`);
572
630
  const msg = `[更新] ${res.data.Title} 更新了!
573
631
  回复: ${res.data.Comments}
574
632
  https://fimtale.com/t/${tid}`;
@@ -576,7 +634,8 @@ https://fimtale.com/t/${tid}`;
576
634
  try {
577
635
  await ctx.broadcast([sub.cid], import_koishi.h.parse(msg));
578
636
  await ctx.database.set("fimtale_subs", { id: sub.id }, { lastCount: res.data.Comments });
579
- } catch {
637
+ } catch (e) {
638
+ logger.error(`Broadcast failed for sub ${sub.id}:`, e);
580
639
  }
581
640
  }
582
641
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-fimtale-api",
3
3
  "description": "Koishi插件,从fimtale搜索/订阅/随机获取小说/解析链接等",
4
- "version": "1.0.7",
4
+ "version": "1.0.9",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [