@anyul/koishi-plugin-rss 5.3.2 → 5.3.10

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 (59) hide show
  1. package/lib/commands/index.js +1 -3
  2. package/lib/commands/subscription-create.d.ts +2 -0
  3. package/lib/commands/subscription-create.js +2 -2
  4. package/lib/commands/subscription-edit.d.ts +2 -0
  5. package/lib/commands/subscription-edit.js +8 -12
  6. package/lib/commands/subscription-management.d.ts +2 -0
  7. package/lib/commands/subscription-management.js +10 -12
  8. package/lib/commands/utils.d.ts +0 -5
  9. package/lib/commands/utils.js +0 -16
  10. package/lib/commands/web-monitor.d.ts +2 -0
  11. package/lib/commands/web-monitor.js +2 -2
  12. package/lib/config.js +4 -10
  13. package/lib/core/feeder-arg.js +3 -38
  14. package/lib/core/feeder-runtime.js +0 -5
  15. package/lib/core/feeder.js +53 -12
  16. package/lib/core/item-processor-runtime.d.ts +4 -2
  17. package/lib/core/item-processor-runtime.js +4 -12
  18. package/lib/core/notification-queue-sender.js +0 -5
  19. package/lib/core/notification-queue-store.d.ts +7 -0
  20. package/lib/core/notification-queue-store.js +28 -6
  21. package/lib/core/notification-queue.d.ts +10 -0
  22. package/lib/core/notification-queue.js +18 -7
  23. package/lib/core/parser.js +5 -5
  24. package/lib/core/renderer.js +7 -7
  25. package/lib/core/search-format.d.ts +1 -1
  26. package/lib/core/search-format.js +1 -1
  27. package/lib/core/subscription-store.d.ts +44 -0
  28. package/lib/core/subscription-store.js +60 -0
  29. package/lib/core/telegram-video-restore.js +3 -8
  30. package/lib/index.d.ts +1 -4
  31. package/lib/index.js +20 -15
  32. package/lib/tsconfig.tsbuildinfo +1 -1
  33. package/lib/types.d.ts +51 -32
  34. package/lib/utils/common.d.ts +6 -0
  35. package/lib/utils/common.js +20 -30
  36. package/lib/utils/error-handler.d.ts +49 -0
  37. package/lib/utils/error-handler.js +111 -1
  38. package/lib/utils/fetcher.js +12 -38
  39. package/lib/utils/legacy-config.js +9 -3
  40. package/lib/utils/logger.d.ts +6 -0
  41. package/lib/utils/logger.js +82 -14
  42. package/lib/utils/media.js +21 -21
  43. package/lib/utils/message-cache.d.ts +8 -0
  44. package/lib/utils/message-cache.js +12 -0
  45. package/lib/utils/proxy.d.ts +37 -0
  46. package/lib/utils/proxy.js +60 -0
  47. package/package.json +16 -3
  48. package/lib/commands/error-handler.d.ts +0 -53
  49. package/lib/commands/error-handler.js +0 -120
  50. package/lib/commands/rss-subscribe.d.ts +0 -11
  51. package/lib/commands/rss-subscribe.js +0 -323
  52. package/lib/core/feeder-old.d.ts +0 -15
  53. package/lib/core/feeder-old.js +0 -403
  54. package/lib/utils/error-tracker.d.ts +0 -127
  55. package/lib/utils/error-tracker.js +0 -352
  56. package/lib/utils/metrics.d.ts +0 -184
  57. package/lib/utils/metrics.js +0 -401
  58. package/lib/utils/structured-logger.d.ts +0 -135
  59. package/lib/utils/structured-logger.js +0 -248
@@ -16,4 +16,11 @@ export declare class NotificationQueueStore {
16
16
  getStats(): Promise<QueueStats>;
17
17
  retryFailedTasks(taskId?: number): Promise<number>;
18
18
  cleanupSuccessTasks(olderThanHours?: number): Promise<number>;
19
+ /**
20
+ * 清理旧的 FAILED 任务。
21
+ *
22
+ * 与 SUCCESS 对称:FAILED 任务此前只进不出(无清理命令、无定时器),
23
+ * 长期运行会无限堆积,拖慢 getStats / getPendingTasks 的全表查询。
24
+ */
25
+ cleanupFailedTasks(olderThanHours?: number): Promise<number>;
19
26
  }
@@ -46,13 +46,18 @@ class NotificationQueueStore {
46
46
  };
47
47
  }
48
48
  async getPendingTasks() {
49
- const now = new Date();
49
+ const now = Date.now();
50
50
  const pendingTasks = await this.ctx.database.get(exports.RSS_NOTIFICATION_QUEUE_TABLE, { status: 'PENDING' }, { limit: this.batchSize });
51
51
  const retryTasks = await this.ctx.database.get(exports.RSS_NOTIFICATION_QUEUE_TABLE, { status: 'RETRY' }, { limit: this.batchSize });
52
- const readyRetryTasks = retryTasks.filter(task => task.nextRetryTime && new Date(task.nextRetryTime) <= now);
53
- return [...pendingTasks, ...readyRetryTasks]
54
- .sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())
55
- .slice(0, this.batchSize);
52
+ const readyRetryTasks = retryTasks.filter(task => task.nextRetryTime && new Date(task.nextRetryTime).getTime() <= now);
53
+ // PENDING 优先于 RETRY:避免一个持续高频失败的源因其 RETRY 任务 createdAt 较老
54
+ // 而长期占用 batchSize 槽位,把新订阅的新消息饿死(延迟数分钟才发)。
55
+ // 各组内部仍按 createdAt 升序(先入先出)。
56
+ const byCreatedAsc = (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
57
+ return [
58
+ ...pendingTasks.sort(byCreatedAsc),
59
+ ...readyRetryTasks.sort(byCreatedAsc),
60
+ ].slice(0, this.batchSize);
56
61
  }
57
62
  async markTaskSuccess(taskId) {
58
63
  await this.ctx.database.set(exports.RSS_NOTIFICATION_QUEUE_TABLE, { id: taskId }, {
@@ -90,7 +95,10 @@ class NotificationQueueStore {
90
95
  });
91
96
  }
92
97
  async recoverRetryTasksWithoutNextRetryTime() {
93
- const retryTasks = await this.ctx.database.get(exports.RSS_NOTIFICATION_QUEUE_TABLE, { status: 'RETRY' }, { limit: this.batchSize });
98
+ // 不限制 limit:损坏的 RETRY 任务可能 > batchSize(老版本升级 / DB 手工修改),
99
+ // 若只修前 batchSize 条,剩余的会因 nextRetryTime 永远为空而既不重试也不失败,永久卡住。
100
+ // 本操作幂等(只修 nextRetryTime 为空的),可安全重复执行。
101
+ const retryTasks = await this.ctx.database.get(exports.RSS_NOTIFICATION_QUEUE_TABLE, { status: 'RETRY' });
94
102
  const invalidTasks = retryTasks.filter(task => !task.nextRetryTime);
95
103
  for (const task of invalidTasks) {
96
104
  await this.ctx.database.set(exports.RSS_NOTIFICATION_QUEUE_TABLE, { id: task.id }, {
@@ -133,5 +141,19 @@ class NotificationQueueStore {
133
141
  }
134
142
  return tasks.length;
135
143
  }
144
+ /**
145
+ * 清理旧的 FAILED 任务。
146
+ *
147
+ * 与 SUCCESS 对称:FAILED 任务此前只进不出(无清理命令、无定时器),
148
+ * 长期运行会无限堆积,拖慢 getStats / getPendingTasks 的全表查询。
149
+ */
150
+ async cleanupFailedTasks(olderThanHours = 168) {
151
+ const cutoffTime = new Date(Date.now() - olderThanHours * 60 * 60 * 1000);
152
+ const tasks = await this.ctx.database.get(exports.RSS_NOTIFICATION_QUEUE_TABLE, { status: 'FAILED', updatedAt: { $lt: cutoffTime } });
153
+ for (const task of tasks) {
154
+ await this.ctx.database.remove(exports.RSS_NOTIFICATION_QUEUE_TABLE, { id: task.id });
155
+ }
156
+ return tasks.length;
157
+ }
136
158
  }
137
159
  exports.NotificationQueueStore = NotificationQueueStore;
@@ -57,4 +57,14 @@ export declare class NotificationQueueManager {
57
57
  * 清理旧的成功任务
58
58
  */
59
59
  cleanupSuccessTasks(olderThanHours?: number): Promise<number>;
60
+ /**
61
+ * 自动清理:周期性删除过期的 SUCCESS / FAILED 任务。
62
+ *
63
+ * SUCCESS 用 cleanupHours,FAILED 用 7×cleanupHours(失败记录保留更久便于排查,
64
+ * 但仍需回收,否则永久堆积拖慢全表查询)。供 feeder 定时器调用。
65
+ */
66
+ runAutomaticCleanup(): Promise<{
67
+ success: number;
68
+ failed: number;
69
+ }>;
60
70
  }
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.NotificationQueueManager = void 0;
4
4
  const error_handler_1 = require("../utils/error-handler");
5
- const error_tracker_1 = require("../utils/error-tracker");
6
5
  const logger_1 = require("../utils/logger");
7
6
  const notification_queue_retry_1 = require("./notification-queue-retry");
8
7
  const notification_queue_sender_1 = require("./notification-queue-sender");
@@ -109,7 +108,6 @@ class NotificationQueueManager {
109
108
  catch (err) {
110
109
  const normalizedError = (0, error_handler_1.normalizeError)(err);
111
110
  (0, logger_1.debug)(this.config, `队列处理异常: ${normalizedError.message}`, 'queue', 'error', { processing: true });
112
- (0, error_tracker_1.trackError)(normalizedError, { operation: 'processQueue' });
113
111
  }
114
112
  finally {
115
113
  this.processing = false;
@@ -139,11 +137,6 @@ class NotificationQueueManager {
139
137
  const classification = (0, notification_queue_retry_1.classifyQueueError)(error, task.content);
140
138
  const normalizedError = classification.normalizedError;
141
139
  const errorMsg = normalizedError.message || 'Unknown error';
142
- (0, error_tracker_1.trackError)(normalizedError, {
143
- ...this.buildTaskLogContext(task),
144
- failReason: errorMsg,
145
- queueErrorAction: classification.action,
146
- });
147
140
  if (classification.action === 'FAILED') {
148
141
  await this.store.markTaskFailed(task.id, errorMsg);
149
142
  taskDebug(`✗ 永久性失败,放弃重试: [${task.rssId}] ${task.content.title} - ${errorMsg}`, 'queue', 'error', {
@@ -218,5 +211,23 @@ class NotificationQueueManager {
218
211
  });
219
212
  return cleanupCount;
220
213
  }
214
+ /**
215
+ * 自动清理:周期性删除过期的 SUCCESS / FAILED 任务。
216
+ *
217
+ * SUCCESS 用 cleanupHours,FAILED 用 7×cleanupHours(失败记录保留更久便于排查,
218
+ * 但仍需回收,否则永久堆积拖慢全表查询)。供 feeder 定时器调用。
219
+ */
220
+ async runAutomaticCleanup() {
221
+ const success = await this.store.cleanupSuccessTasks(this.cleanupHours);
222
+ // FAILED 保留时长为 SUCCESS 的 7 倍(默认 7 天),兼顾排查与回收
223
+ const failed = await this.store.cleanupFailedTasks(this.cleanupHours * 7);
224
+ if (success > 0 || failed > 0) {
225
+ (0, logger_1.debug)(this.config, `自动清理:${success} 个成功任务、${failed} 个失败任务`, 'queue', 'info', {
226
+ cleanupSuccess: success,
227
+ cleanupFailed: failed,
228
+ });
229
+ }
230
+ return { success, failed };
231
+ }
221
232
  }
222
233
  exports.NotificationQueueManager = NotificationQueueManager;
@@ -76,7 +76,7 @@ async function getRssData(ctx, config, $http, url, arg) {
76
76
  try {
77
77
  await page.waitForSelector(arg.waitSelector, { timeout: 5000 });
78
78
  }
79
- catch (e) { }
79
+ catch { }
80
80
  }
81
81
  else if (arg.waitFor) {
82
82
  await new Promise(r => setTimeout(r, arg.waitFor));
@@ -93,7 +93,7 @@ async function getRssData(ctx, config, $http, url, arg) {
93
93
  try {
94
94
  await page.close();
95
95
  }
96
- catch (e) { /* 忽略页面已关闭的错误 */ }
96
+ catch { /* 忽略页面已关闭的错误 */ }
97
97
  }
98
98
  }
99
99
  else {
@@ -113,7 +113,7 @@ async function getRssData(ctx, config, $http, url, arg) {
113
113
  rssData = JSON.parse(rssData);
114
114
  isJson = true;
115
115
  }
116
- catch (e) { /* ignore */ }
116
+ catch { /* ignore */ }
117
117
  }
118
118
  if (isJson) {
119
119
  (0, logger_1.debug)(config, rssData, 'JSON Feed Response', 'details');
@@ -178,7 +178,7 @@ async function getRssData(ctx, config, $http, url, arg) {
178
178
  try {
179
179
  link = new URL(link, url).href;
180
180
  }
181
- catch (e) { }
181
+ catch { }
182
182
  }
183
183
  // 3. 提取内容
184
184
  const description = arg.textOnly ? $el.text().trim() : ($el.html() || '').trim();
@@ -217,7 +217,7 @@ async function getRssData(ctx, config, $http, url, arg) {
217
217
  }
218
218
  else if (rssJson.feed) {
219
219
  // Atom
220
- let rss = { channel: {} };
220
+ const rss = { channel: {} };
221
221
  let item = rssJson.feed.entry.map(i => ({
222
222
  ...i,
223
223
  title: (0, common_1.parseContent)(i.title),
@@ -137,7 +137,7 @@ async function preprocessHtmlImages(ctx, config, $http, htmlContent, arg) {
137
137
  return $.html();
138
138
  }
139
139
  async function renderHtml2Image(ctx, config, $http, htmlContent, arg) {
140
- let page = await ctx.puppeteer.page();
140
+ const page = await ctx.puppeteer.page();
141
141
  try {
142
142
  (0, logger_1.debug)(config, htmlContent, 'htmlContent', 'details');
143
143
  const initialViewportHeight = 2000;
@@ -265,15 +265,15 @@ async function renderHtml2Image(ctx, config, $http, htmlContent, arg) {
265
265
  return koishi_1.h.image(image, 'image/png');
266
266
  }
267
267
  let height = actualHeight;
268
- let width = clipWidth;
269
- let x = clipX;
270
- let y = clipY;
268
+ const width = clipWidth;
269
+ const x = clipX;
270
+ const y = clipY;
271
271
  // 保守处理:如果高度异常,使用更小的值
272
272
  if (height > 5000) {
273
273
  (0, logger_1.debug)(config, `检测到异常高度 ${height},进行裁剪`, 'height warn', 'info');
274
274
  height = Math.min(height, 2000);
275
275
  }
276
- let size = 10000;
276
+ const size = 10000;
277
277
  (0, logger_1.debug)(config, [height, width, x, y], 'pptr img size', 'details');
278
278
  const split = Math.ceil(height / size);
279
279
  if (!split) {
@@ -298,7 +298,7 @@ async function renderHtml2Image(ctx, config, $http, htmlContent, arg) {
298
298
  const reduceY = (index) => Math.floor(height / split * index);
299
299
  // 最后一份使用完整高度减去前面所有份的高度,确保覆盖全部内容
300
300
  const reduceHeight = (index) => index === split - 1 ? height - reduceY(index) : Math.floor(height / split);
301
- let imgData = await Promise.all(Array.from({ length: split }, async (v, i) => await page.screenshot({
301
+ const imgData = await Promise.all(Array.from({ length: split }, async (v, i) => await page.screenshot({
302
302
  type: "png",
303
303
  clip: {
304
304
  x,
@@ -329,6 +329,6 @@ async function renderHtml2Image(ctx, config, $http, htmlContent, arg) {
329
329
  try {
330
330
  await page.close();
331
331
  }
332
- catch (e) { /* 忽略页面已关闭的错误 */ }
332
+ catch { /* 忽略页面已关闭的错误 */ }
333
333
  }
334
334
  }
@@ -1,3 +1,3 @@
1
1
  import { SearchResponse } from './search-types';
2
2
  export declare function formatSearchResults(response: SearchResponse): string;
3
- export declare function buildPromptWithSearchContext(originalPrompt: string, searchResults: SearchResponse, searchQuery: string): string;
3
+ export declare function buildPromptWithSearchContext(originalPrompt: string, searchResults: SearchResponse, _searchQuery: string): string;
@@ -21,7 +21,7 @@ function formatSearchResults(response) {
21
21
  });
22
22
  return lines.join('\n');
23
23
  }
24
- function buildPromptWithSearchContext(originalPrompt, searchResults, searchQuery) {
24
+ function buildPromptWithSearchContext(originalPrompt, searchResults, _searchQuery) {
25
25
  if (!searchResults.success || searchResults.results.length === 0) {
26
26
  return originalPrompt;
27
27
  }
@@ -0,0 +1,44 @@
1
+ import { Context } from 'koishi';
2
+ import type { RssOwlRow } from '../types';
3
+ /**
4
+ * 订阅数据访问仓储(Repository)。
5
+ *
6
+ * 设计目的:`rssOwl` 是订阅核心表,原本被命令层(subscription-create /
7
+ * subscription-edit / subscription-management / web-monitor)的 15 处
8
+ * 直接 `ctx.database.*('rssOwl', ...)` 调用散落访问,违反「命令层只复用
9
+ * 核心函数」的分层约定。本类作为该表的唯一封装,命令层通过依赖注入拿到
10
+ * 实例,不再直接触碰 `ctx.database`,与 `NotificationQueueStore` /
11
+ * `MessageCacheManager` 的既有分层保持一致。
12
+ *
13
+ * 注意:feeder(`core/feeder.ts`)也在核心层直接读写 `rssOwl`,那是核心层
14
+ * 对自身数据的合法访问,不强制走本仓储;强制收口会破坏 feeder 作为纯函数
15
+ * 的可测试性(见 5.3.8 的 disposed 评估取舍)。
16
+ */
17
+ export declare class SubscriptionStore {
18
+ private ctx;
19
+ constructor(ctx: Context);
20
+ /**
21
+ * 按群组查询订阅列表(命令层最常用的读取入口)。
22
+ */
23
+ findByGuild(platform: string, guildId: string): Promise<RssOwlRow[]>;
24
+ /**
25
+ * 按 群组 + URL 查询(用于编辑推送目标时判断目标群是否已有同源订阅)。
26
+ */
27
+ findByGuildAndUrl(platform: string, guildId: string, url: string): Promise<RssOwlRow[]>;
28
+ /**
29
+ * 创建一条订阅,返回含生成主键的完整行。
30
+ */
31
+ create(data: Partial<RssOwlRow>): Promise<RssOwlRow>;
32
+ /**
33
+ * 按主键更新任意字段。
34
+ */
35
+ update(id: number, patch: Partial<RssOwlRow>): Promise<void>;
36
+ /**
37
+ * 按主键删除单条订阅。
38
+ */
39
+ remove(id: number): Promise<void>;
40
+ /**
41
+ * 删除某个群组的全部订阅(rsso.remove --all)。
42
+ */
43
+ removeAllByGuild(platform: string, guildId: string): Promise<void>;
44
+ }
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SubscriptionStore = void 0;
4
+ /**
5
+ * 订阅数据访问仓储(Repository)。
6
+ *
7
+ * 设计目的:`rssOwl` 是订阅核心表,原本被命令层(subscription-create /
8
+ * subscription-edit / subscription-management / web-monitor)的 15 处
9
+ * 直接 `ctx.database.*('rssOwl', ...)` 调用散落访问,违反「命令层只复用
10
+ * 核心函数」的分层约定。本类作为该表的唯一封装,命令层通过依赖注入拿到
11
+ * 实例,不再直接触碰 `ctx.database`,与 `NotificationQueueStore` /
12
+ * `MessageCacheManager` 的既有分层保持一致。
13
+ *
14
+ * 注意:feeder(`core/feeder.ts`)也在核心层直接读写 `rssOwl`,那是核心层
15
+ * 对自身数据的合法访问,不强制走本仓储;强制收口会破坏 feeder 作为纯函数
16
+ * 的可测试性(见 5.3.8 的 disposed 评估取舍)。
17
+ */
18
+ class SubscriptionStore {
19
+ ctx;
20
+ constructor(ctx) {
21
+ this.ctx = ctx;
22
+ }
23
+ /**
24
+ * 按群组查询订阅列表(命令层最常用的读取入口)。
25
+ */
26
+ async findByGuild(platform, guildId) {
27
+ return this.ctx.database.get('rssOwl', { platform, guildId });
28
+ }
29
+ /**
30
+ * 按 群组 + URL 查询(用于编辑推送目标时判断目标群是否已有同源订阅)。
31
+ */
32
+ async findByGuildAndUrl(platform, guildId, url) {
33
+ return this.ctx.database.get('rssOwl', { platform, guildId, url });
34
+ }
35
+ /**
36
+ * 创建一条订阅,返回含生成主键的完整行。
37
+ */
38
+ async create(data) {
39
+ return this.ctx.database.create('rssOwl', data);
40
+ }
41
+ /**
42
+ * 按主键更新任意字段。
43
+ */
44
+ async update(id, patch) {
45
+ await this.ctx.database.set('rssOwl', id, patch);
46
+ }
47
+ /**
48
+ * 按主键删除单条订阅。
49
+ */
50
+ async remove(id) {
51
+ await this.ctx.database.remove('rssOwl', { id });
52
+ }
53
+ /**
54
+ * 删除某个群组的全部订阅(rsso.remove --all)。
55
+ */
56
+ async removeAllByGuild(platform, guildId) {
57
+ await this.ctx.database.remove('rssOwl', { platform, guildId });
58
+ }
59
+ }
60
+ exports.SubscriptionStore = SubscriptionStore;
@@ -54,6 +54,7 @@ var __importStar = (this && this.__importStar) || (function () {
54
54
  })();
55
55
  Object.defineProperty(exports, "__esModule", { value: true });
56
56
  exports.restoreTelegramVideos = restoreTelegramVideos;
57
+ const common_1 = require("../utils/common");
57
58
  const logger_1 = require("../utils/logger");
58
59
  const media_1 = require("../utils/media");
59
60
  const tdl_1 = require("../utils/tdl");
@@ -82,7 +83,7 @@ async function restoreTelegramVideos(html, item, arg, config, ffmpegExe) {
82
83
  return false;
83
84
  if (!(0, tdl_1.detectVideoTooBig)(html))
84
85
  return false;
85
- const link = normalizeText(item?.link);
86
+ const link = (0, common_1.normalizeText)(item?.link);
86
87
  const linkInfo = (0, tdl_1.parseTelegramLink)(link);
87
88
  if (!linkInfo) {
88
89
  (0, logger_1.debug)(config, `检测到 Video is too big 但 link 非 Telegram 消息链接,跳过: ${link}`, 'tg-restore', 'info');
@@ -237,10 +238,4 @@ function replaceTooBigBlockquotesIndexed(html, builder, onEach) {
237
238
  function escapeAttr(s) {
238
239
  return String(s || '').replace(/"/g, '&quot;');
239
240
  }
240
- function normalizeText(v) {
241
- if (Array.isArray(v))
242
- return v.join('');
243
- if (v === undefined || v === null)
244
- return '';
245
- return String(v);
246
- }
241
+ // normalizeText 已收敛到 utils/common.ts
package/lib/index.d.ts CHANGED
@@ -4,8 +4,5 @@ export * from './types';
4
4
  export { templateList } from './config';
5
5
  import type { Config } from './types';
6
6
  export declare const name = "@anyul/koishi-plugin-rss";
7
- export declare const inject: {
8
- required: string[];
9
- optional: string[];
10
- };
7
+ export declare const using: string[];
11
8
  export declare function apply(ctx: Context, config: Config): void;
package/lib/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.inject = exports.name = exports.templateList = exports.Config = void 0;
17
+ exports.using = exports.name = exports.templateList = exports.Config = void 0;
18
18
  exports.apply = apply;
19
19
  // Export types and config
20
20
  var config_1 = require("./config");
@@ -25,23 +25,28 @@ Object.defineProperty(exports, "templateList", { enumerable: true, get: function
25
25
  exports.name = '@anyul/koishi-plugin-rss';
26
26
  const fetcher_1 = require("./utils/fetcher");
27
27
  const media_1 = require("./utils/media");
28
- const error_tracker_1 = require("./utils/error-tracker");
29
28
  const tdl_1 = require("./utils/tdl");
30
29
  const logger_1 = require("./utils/logger");
31
30
  const commands_1 = require("./commands");
32
31
  // Import core modules
33
32
  const item_processor_1 = require("./core/item-processor");
34
33
  const feeder_1 = require("./core/feeder");
34
+ const subscription_store_1 = require("./core/subscription-store");
35
35
  const message_cache_1 = require("./utils/message-cache");
36
36
  const message_cache_service_1 = require("./services/message-cache-service");
37
37
  const notification_queue_1 = require("./core/notification-queue");
38
38
  // Import database and constants
39
39
  const database_1 = require("./database");
40
40
  const constants_1 = require("./constants");
41
- exports.inject = { required: ["database"], optional: ["puppeteer", "censor", "assets", "server", "ffmpeg"] };
41
+ // Koishi 4.11+ 约定:必需服务用 `using` 声明(替代旧 `inject`)。
42
+ // 可选服务(puppeteer/censor/assets/server/ffmpeg)不在此声明,代码内统一用可选链访问。
43
+ exports.using = ['database'];
42
44
  function apply(ctx, config) {
43
45
  // Setup database
44
46
  (0, database_1.setupDatabase)(ctx);
47
+ // 把 config.debug 同步到 Koishi 原生日志分级(Logger.levels['rss-owl']),
48
+ // 让 WebUI 的 logger 插件 / 全局 levels 都能控制本插件日志可见性。
49
+ (0, logger_1.applyDebugLevel)(config);
45
50
  // 启用时探测 tdl 外部二进制可用性,并报告 Koishi ffmpeg 服务注入状态,便于用户排查
46
51
  // 探测异步进行、失败仅打日志,不阻塞插件加载
47
52
  // 注意:tdl 的版本命令是 `version` 子命令,不是 `-v` flag
@@ -55,16 +60,6 @@ function apply(ctx, config) {
55
60
  (0, logger_1.debug)(config, `Telegram 大视频工具探测:tdl=${hasTdl ? '✓' + tdlLoc : '✗(请安装 iyear/tdl 并 tdl login)'},ffmpeg=${ffmpegStatus}`, 'tdl', 'info');
56
61
  }).catch(() => { });
57
62
  }
58
- if (config.errorTracking?.enabled) {
59
- (0, error_tracker_1.initErrorTracker)({
60
- enabled: config.errorTracking.enabled ?? false,
61
- dsn: config.errorTracking.dsn || '',
62
- environment: config.errorTracking.environment,
63
- release: config.errorTracking.release,
64
- tracesSampleRate: config.errorTracking.tracesSampleRate,
65
- profilesSampleRate: config.errorTracking.profilesSampleRate,
66
- });
67
- }
68
63
  // Initialize request manager and HTTP function
69
64
  const requestManager = new fetcher_1.RequestManager(3, 2, 10);
70
65
  const $http = (0, fetcher_1.createHttpFunction)(ctx, config, requestManager);
@@ -73,6 +68,8 @@ function apply(ctx, config) {
73
68
  const commandRuntime = (0, commands_1.createCommandRuntimeDeps)(ctx, config, $http, processor);
74
69
  // Initialize notification queue manager
75
70
  const queueManager = new notification_queue_1.NotificationQueueManager(ctx, config);
71
+ // Initialize subscription repository (rssOwl 表的唯一封装)
72
+ const subscriptionStore = new subscription_store_1.SubscriptionStore(ctx);
76
73
  // Initialize message cache
77
74
  if (config.cache?.enabled) {
78
75
  (0, message_cache_1.initMessageCache)(ctx, config, config.cache.maxSize || 100);
@@ -85,8 +82,12 @@ function apply(ctx, config) {
85
82
  });
86
83
  ctx.on('dispose', async () => {
87
84
  (0, feeder_1.stopFeeder)(config);
88
- if (config.basic.imageMode === 'File') {
89
- (0, media_1.delCache)(config);
85
+ // 销毁消息缓存单例:避免热重载后复用持有旧 ctx 的实例(P1-2)
86
+ if (config.cache?.enabled) {
87
+ (0, message_cache_1.disposeMessageCache)();
88
+ }
89
+ if (config.basic?.imageMode === 'File') {
90
+ await (0, media_1.delCache)(config);
90
91
  }
91
92
  });
92
93
  // ============================================
@@ -95,6 +96,7 @@ function apply(ctx, config) {
95
96
  (0, commands_1.registerSubscriptionManagementCommands)({
96
97
  ctx,
97
98
  config,
99
+ store: subscriptionStore,
98
100
  parsePubDate: commandRuntime.parsePubDate,
99
101
  parseQuickUrl: commandRuntime.parseQuickUrl,
100
102
  getRssData: commandRuntime.getRssData,
@@ -104,6 +106,7 @@ function apply(ctx, config) {
104
106
  (0, commands_1.registerSubscriptionCreateCommand)({
105
107
  ctx,
106
108
  config,
109
+ store: subscriptionStore,
107
110
  usage: constants_1.usage,
108
111
  quickList: constants_1.quickList,
109
112
  parseQuickUrl: commandRuntime.parseQuickUrl,
@@ -117,6 +120,7 @@ function apply(ctx, config) {
117
120
  (0, commands_1.registerWebMonitorCommands)({
118
121
  ctx,
119
122
  config,
123
+ store: subscriptionStore,
120
124
  debug: commandRuntime.debug,
121
125
  mixinArg: commandRuntime.mixinArg,
122
126
  getRssData: commandRuntime.getRssData,
@@ -127,6 +131,7 @@ function apply(ctx, config) {
127
131
  (0, commands_1.registerSubscriptionEditCommand)({
128
132
  ctx,
129
133
  config,
134
+ store: subscriptionStore,
130
135
  });
131
136
  (0, commands_1.registerManagementCommands)({
132
137
  ctx,