koishi-plugin-fimtale-api 1.0.8 → 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,13 +210,20 @@ 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);
@@ -201,11 +234,13 @@ function apply(ctx, config) {
201
234
  displayCover = extractImage(parent.Content);
202
235
  } else {
203
236
  try {
237
+ debugLog(`Missing parent content/cover for chapter ${info.ID}, fetching parent ${parent.ID}...`);
204
238
  const parentRes = await fetchThread(parent.ID.toString());
205
239
  if (parentRes.valid && parentRes.data) {
206
240
  displayCover = parentRes.data.Background || extractImage(parentRes.data.Content);
207
241
  }
208
242
  } catch (e) {
243
+ debugLog("Fetch parent for cover failed (non-critical).");
209
244
  }
210
245
  }
211
246
  }
@@ -228,8 +263,6 @@ function apply(ctx, config) {
228
263
  body { margin: 0; padding: 0; font-family: ${fontStack}; background: transparent; }
229
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; }
230
265
  .cover { width: 220px; min-height: 100%; ${bgStyle} background-size: cover; background-position: center; position: relative; flex-shrink: 0; }
231
-
232
- /* ID Badge 分体式:使用 flex 强制垂直居中,消除字体基线差异 */
233
266
  .id-badge-container {
234
267
  position: absolute; top: 15px; left: 15px;
235
268
  display: flex;
@@ -237,7 +270,7 @@ function apply(ctx, config) {
237
270
  border-radius: 6px;
238
271
  overflow: hidden;
239
272
  border: 1px solid rgba(255,255,255,0.3);
240
- height: 28px; /* 强制高度 */
273
+ height: 28px;
241
274
  }
242
275
  .id-label {
243
276
  background: #EE6E73;
@@ -249,7 +282,7 @@ function apply(ctx, config) {
249
282
  text-transform: uppercase;
250
283
  display: flex; align-items: center; justify-content: center;
251
284
  height: 100%;
252
- line-height: 1; margin: 0; /* 修复对齐 */
285
+ line-height: 1; margin: 0;
253
286
  }
254
287
  .id-val {
255
288
  background: #fff;
@@ -260,23 +293,17 @@ function apply(ctx, config) {
260
293
  font-weight: 900;
261
294
  display: flex; align-items: center; justify-content: center;
262
295
  height: 100%;
263
- line-height: 1; margin: 0; /* 修复对齐 */
296
+ line-height: 1; margin: 0;
264
297
  }
265
-
266
298
  .info { flex: 1; padding: 26px; display: flex; flex-direction: column; overflow: hidden; position: relative; }
267
-
268
299
  .header-group { flex-shrink: 0; margin-bottom: 16px; border-bottom: 2px solid #f5f5f5; padding-bottom: 12px; }
269
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; }
270
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; }
271
-
272
302
  .author { font-size: 14px; color: #78909C; margin-top: 12px; font-weight: 400; display:flex; align-items:center; }
273
-
274
303
  .tags { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; flex-shrink: 0; }
275
304
  .tag { background: #eff2f5; color: #5c6b7f; padding: 3px 9px; border-radius: 4px; font-size: 11px; font-weight: 500; }
276
-
277
305
  .summary-box { flex: 1; position: relative; overflow: hidden; min-height: 0; margin-bottom: 16px; }
278
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; }
279
-
280
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; }
281
308
  .stat b { color: #455a64; font-weight: bold; margin-right: 3px;}
282
309
  </style></head><body>
@@ -295,18 +322,24 @@ function apply(ctx, config) {
295
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>
296
323
  <span class="stat"><b style="color:#4caf50">赞</b>${likes}</span><span class="stat"><b style="color:#8d6e63">字数</b>${info.WordCount || 0}</span>
297
324
  </div></div></div></body></html>`;
325
+ debugLog(`Card HTML prepared (approx ${html.length} chars). Launching Puppeteer...`);
298
326
  const page = await ctx.puppeteer.page();
299
327
  try {
300
328
  await injectCookies(page);
301
329
  await page.setContent(html);
302
330
  await page.setViewport({ width: 660, height: 480, deviceScaleFactor: 3 });
303
331
  const img = await page.$(".card").then((e) => e.screenshot({ type: "jpeg", quality: 100 }));
332
+ debugLog("Card screenshot captured successfully.");
304
333
  return img;
334
+ } catch (e) {
335
+ logger.error("Error rendering card:", e);
336
+ throw e;
305
337
  } finally {
306
338
  await page.close();
307
339
  }
308
340
  }, "renderCard");
309
341
  const renderSearchResults = /* @__PURE__ */ __name(async (keyword, results) => {
342
+ debugLog(`Rendering search results: ${results.length} items`);
310
343
  const html = `<!DOCTYPE html><html><head><style>
311
344
  body { margin: 0; padding: 0; font-family: ${fontStack}; width: 500px; background: transparent; }
312
345
  .container { background: #fff; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 20px rgba(0,0,0,0.15); margin: 10px; }
@@ -319,8 +352,6 @@ function apply(ctx, config) {
319
352
  .content { flex: 1; display: flex; flex-direction: column; justify-content: space-between; height: 100%; min-width: 0; }
320
353
  .top-row { display: flex; justify-content: space-between; align-items: flex-start; }
321
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;}
322
-
323
- /* ID Badge Search: 迷你分体式,保持与大图风格一致但更紧凑 */
324
355
  .id-badge {
325
356
  display: flex;
326
357
  border-radius: 4px;
@@ -338,7 +369,7 @@ function apply(ctx, config) {
338
369
  font-weight: bold;
339
370
  display: flex; align-items: center; justify-content: center;
340
371
  height: 100%;
341
- line-height: 1; margin: 0; /* 修复对齐 */
372
+ line-height: 1; margin: 0;
342
373
  }
343
374
  .id-val {
344
375
  background: #fff;
@@ -349,9 +380,8 @@ function apply(ctx, config) {
349
380
  font-weight: bold;
350
381
  display: flex; align-items: center; justify-content: center;
351
382
  height: 100%;
352
- line-height: 1; margin: 0; /* 修复对齐 */
383
+ line-height: 1; margin: 0;
353
384
  }
354
-
355
385
  .author { font-size: 12px; color: #78909C; }
356
386
  .tags { display: flex; gap: 4px; flex-wrap: wrap; height: 18px; overflow: hidden; margin-top: 4px; }
357
387
  .tag { background: #f3f3f3; color: #666; padding: 0 5px; border-radius: 3px; font-size: 10px; white-space: nowrap; line-height: 1.6;}
@@ -378,12 +408,14 @@ function apply(ctx, config) {
378
408
  await page.setContent(html);
379
409
  await page.setViewport({ width: 550, height: 800, deviceScaleFactor: 3 });
380
410
  const img = await page.$(".container").then((e) => e.screenshot({ type: "jpeg", quality: 100 }));
411
+ debugLog("Search results screenshot taken.");
381
412
  return img;
382
413
  } finally {
383
414
  await page.close();
384
415
  }
385
416
  }, "renderSearchResults");
386
417
  const renderReadPages = /* @__PURE__ */ __name(async (info) => {
418
+ debugLog(`Rendering read pages for ${info.ID} (${info.Title})`);
387
419
  const content = cleanContent(info.Content);
388
420
  const headerHeight = 40;
389
421
  const footerHeight = 30;
@@ -399,10 +431,8 @@ function apply(ctx, config) {
399
431
  const marginTop = Math.floor((maxContentHeight - optimalContentHeight) / 2) + headerHeight;
400
432
  const html = `<!DOCTYPE html><html><head><style>
401
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;}
402
- /* Header Padding reduced to 12px to align closer to left edge */
403
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; }
404
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; }
405
- /* 修复1: 强制重置 header 和 footer 的缩进,防止 p, div 全局规则影响 */
406
436
  .fixed-header, .fixed-footer, .header-title, .header-author { text-indent: 0 !important; }
407
437
 
408
438
  #viewport { position: absolute; top: ${marginTop}px; left: ${paddingX}px; width: ${contentWidth}px; height: ${optimalContentHeight}px; overflow: hidden; }
@@ -412,11 +442,8 @@ function apply(ctx, config) {
412
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; }
413
443
  .align-right { text-align: right !important; text-indent: 0 !important; margin-top: 0.5em; color: #666; font-style: italic; }
414
444
  .no-indent { text-indent: 0 !important; }
415
-
416
- /* Header Title absolute left */
417
445
  .header-title { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: left; min-width: 0; margin-right: 10px; }
418
446
  .header-author { flex-shrink: 0; color: #78909C; max-width: 35%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: right; }
419
-
420
447
  blockquote { margin: 1em 0.5em; padding-left: 1em; border-left: 4px solid #EE6E73; color: #666; }
421
448
  blockquote p { text-indent: 0; margin: 0.3em 0; }
422
449
  ul, ol { margin: 0.5em 0; padding-left: 1.5em; }
@@ -451,6 +478,7 @@ function apply(ctx, config) {
451
478
  await injectCookies(page);
452
479
  await page.setContent(html);
453
480
  await page.setViewport({ width: config.deviceWidth, height: config.deviceHeight, deviceScaleFactor: 3 });
481
+ debugLog("Waiting for fonts and images...");
454
482
  await page.evaluate(async () => {
455
483
  await document.fonts.ready;
456
484
  await new Promise((resolve) => {
@@ -475,6 +503,7 @@ function apply(ctx, config) {
475
503
  const step = contentWidth + columnGap;
476
504
  const totalPages = Math.floor((scrollWidth + columnGap - 10) / step) + 1;
477
505
  const finalPages = Math.max(1, totalPages);
506
+ debugLog(`Content width calculated: ${scrollWidth}px. Splitting into ${finalPages} pages.`);
478
507
  const imgs = [];
479
508
  for (let i = 0; i < finalPages; i++) {
480
509
  await page.evaluate((idx, stepPx, curr, total) => {
@@ -484,18 +513,24 @@ function apply(ctx, config) {
484
513
  }, i, step, i + 1, finalPages);
485
514
  imgs.push(await page.screenshot({ type: "jpeg", quality: 100 }));
486
515
  }
516
+ debugLog("All pages captured.");
487
517
  return imgs;
518
+ } catch (e) {
519
+ logger.error("Error rendering read pages:", e);
520
+ throw e;
488
521
  } finally {
489
522
  await page.close();
490
523
  }
491
524
  }, "renderReadPages");
492
525
  ctx.command("ft.info <threadId:string>", "预览作品").action(async ({ session }, threadId) => {
526
+ debugLog(`CMD ft.info triggered by ${session.userId} for ID: ${threadId}`);
493
527
  if (!threadId) return "请输入ID";
494
528
  const res = await fetchThread(threadId);
495
529
  if (!res.valid) return `[错误] ${res.msg}`;
496
530
  return session.send(import_koishi.h.image(await renderCard(res.data, res.parent), "image/jpeg"));
497
531
  });
498
532
  ctx.command("ft.read <threadId:string>", "阅读章节").action(async ({ session }, threadId) => {
533
+ debugLog(`CMD ft.read triggered by ${session.userId} for ID: ${threadId}`);
499
534
  if (!threadId) return "请输入ID";
500
535
  const res = await fetchThread(threadId);
501
536
  if (!res.valid) return `[错误] 读取失败: ${res.msg}`;
@@ -516,11 +551,12 @@ function apply(ctx, config) {
516
551
  if (navs.length) nodes.push((0, import_koishi.h)("message", import_koishi.h.text("章节导航:\n" + navs.join("\n"))));
517
552
  return session.send((0, import_koishi.h)("message", { forward: true }, nodes));
518
553
  } catch (e) {
519
- ctx.logger("fimtale").error(e);
554
+ logger.error("ft.read rendering failed:", e);
520
555
  return "[错误] 渲染失败";
521
556
  }
522
557
  });
523
558
  ctx.command("ft.random", "随机作品").action(async ({ session }) => {
559
+ debugLog(`CMD ft.random triggered by ${session.userId}`);
524
560
  const id = await fetchRandomId();
525
561
  if (!id) return "[错误] 获取失败";
526
562
  const res = await fetchThread(id);
@@ -529,6 +565,7 @@ function apply(ctx, config) {
529
565
  return `Tip: 发送 /ft.read ${res.data.ID} 阅读全文`;
530
566
  });
531
567
  ctx.command("ft.search <keyword:text>", "搜索作品").action(async ({ session }, keyword) => {
568
+ debugLog(`CMD ft.search triggered by ${session.userId} for "${keyword}"`);
532
569
  if (!keyword) return "请输入关键词";
533
570
  await session.send("[加载中] 搜索中...");
534
571
  const results = await searchThreads(keyword);
@@ -538,6 +575,7 @@ function apply(ctx, config) {
538
575
  return `Tip: 发送 /ft.read [ID] 阅读 (例: /ft.read ${exampleId})`;
539
576
  });
540
577
  ctx.command("ft.sub <threadId:string>", "订阅").action(async ({ session }, threadId) => {
578
+ debugLog(`CMD ft.sub triggered by ${session.userId} for ID: ${threadId}`);
541
579
  if (!/^\d+$/.test(threadId)) return "ID错误";
542
580
  const exist = await ctx.database.get("fimtale_subs", { cid: session.cid, threadId });
543
581
  if (exist.length) return "已订阅";
@@ -548,6 +586,7 @@ function apply(ctx, config) {
548
586
  return session.send(import_koishi.h.image(await renderCard(res.data, res.parent), "image/jpeg"));
549
587
  });
550
588
  ctx.command("ft.unsub <threadId:string>", "退订").action(async ({ session }, threadId) => {
589
+ debugLog(`CMD ft.unsub triggered by ${session.userId} for ID: ${threadId}`);
551
590
  const res = await ctx.database.remove("fimtale_subs", { cid: session.cid, threadId });
552
591
  return res.matched ? "[成功] 已退订" : "未找到订阅";
553
592
  });
@@ -555,6 +594,7 @@ function apply(ctx, config) {
555
594
  if (!config.autoParseLink) return next();
556
595
  const matches = [...session.content.matchAll(/fimtale\.(?:com|net)\/t\/(\d+)/g)];
557
596
  if (matches.length === 0) return next();
597
+ debugLog(`Middleware matched link in channel ${session.channelId}`);
558
598
  const uniqueIds = [...new Set(matches.map((m) => m[1]))];
559
599
  if (session.userId === session.selfId) return next();
560
600
  const messageNodes = [];
@@ -566,6 +606,7 @@ function apply(ctx, config) {
566
606
  messageNodes.push((0, import_koishi.h)("message", import_koishi.h.image(img, "image/jpeg")));
567
607
  }
568
608
  } catch (e) {
609
+ logger.error(`Middleware failed to process link ${id}:`, e);
569
610
  }
570
611
  }
571
612
  if (messageNodes.length === 0) return next();
@@ -578,12 +619,14 @@ function apply(ctx, config) {
578
619
  ctx.setInterval(async () => {
579
620
  const subs = await ctx.database.get("fimtale_subs", {});
580
621
  if (!subs.length) return;
622
+ debugLog(`Polling check started for ${subs.length} subscriptions.`);
581
623
  const tids = [...new Set(subs.map((s) => s.threadId))];
582
624
  for (const tid of tids) {
583
625
  const res = await fetchThread(tid);
584
626
  if (!res.valid) continue;
585
627
  const targets = subs.filter((s) => s.threadId === tid && s.lastCount < res.data.Comments);
586
628
  if (targets.length) {
629
+ debugLog(`Update found for thread ${tid} (Old: ${targets[0].lastCount}, New: ${res.data.Comments})`);
587
630
  const msg = `[更新] ${res.data.Title} 更新了!
588
631
  回复: ${res.data.Comments}
589
632
  https://fimtale.com/t/${tid}`;
@@ -591,7 +634,8 @@ https://fimtale.com/t/${tid}`;
591
634
  try {
592
635
  await ctx.broadcast([sub.cid], import_koishi.h.parse(msg));
593
636
  await ctx.database.set("fimtale_subs", { id: sub.id }, { lastCount: res.data.Comments });
594
- } catch {
637
+ } catch (e) {
638
+ logger.error(`Broadcast failed for sub ${sub.id}:`, e);
595
639
  }
596
640
  }
597
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.8",
4
+ "version": "1.0.9",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [