@anyul/koishi-plugin-rss 5.2.3 → 5.2.4

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 (70) hide show
  1. package/README.md +92 -37
  2. package/lib/commands/error-handler.js +13 -1
  3. package/lib/commands/index.d.ts +3 -0
  4. package/lib/commands/index.js +7 -1
  5. package/lib/commands/runtime.d.ts +17 -0
  6. package/lib/commands/runtime.js +27 -0
  7. package/lib/commands/subscription-create.d.ts +23 -0
  8. package/lib/commands/subscription-create.js +145 -0
  9. package/lib/commands/web-monitor.d.ts +15 -0
  10. package/lib/commands/web-monitor.js +222 -0
  11. package/lib/config.js +7 -1
  12. package/lib/constants.d.ts +1 -1
  13. package/lib/constants.js +46 -83
  14. package/lib/core/ai-cache.d.ts +27 -0
  15. package/lib/core/ai-cache.js +169 -0
  16. package/lib/core/ai-client.d.ts +12 -0
  17. package/lib/core/ai-client.js +65 -0
  18. package/lib/core/ai-selector.d.ts +2 -0
  19. package/lib/core/ai-selector.js +80 -0
  20. package/lib/core/ai-summary.d.ts +10 -0
  21. package/lib/core/ai-summary.js +73 -0
  22. package/lib/core/ai-utils.d.ts +10 -0
  23. package/lib/core/ai-utils.js +104 -0
  24. package/lib/core/ai.d.ts +3 -91
  25. package/lib/core/ai.js +13 -522
  26. package/lib/core/feeder-arg.d.ts +17 -0
  27. package/lib/core/feeder-arg.js +234 -0
  28. package/lib/core/feeder-runtime.d.ts +96 -0
  29. package/lib/core/feeder-runtime.js +233 -0
  30. package/lib/core/feeder.d.ts +3 -5
  31. package/lib/core/feeder.js +61 -358
  32. package/lib/core/item-processor-runtime.d.ts +46 -0
  33. package/lib/core/item-processor-runtime.js +215 -0
  34. package/lib/core/item-processor-template.d.ts +16 -0
  35. package/lib/core/item-processor-template.js +158 -0
  36. package/lib/core/item-processor.d.ts +1 -15
  37. package/lib/core/item-processor.js +44 -319
  38. package/lib/core/notification-queue-retry.d.ts +25 -0
  39. package/lib/core/notification-queue-retry.js +78 -0
  40. package/lib/core/notification-queue-sender.d.ts +20 -0
  41. package/lib/core/notification-queue-sender.js +118 -0
  42. package/lib/core/notification-queue-store.d.ts +19 -0
  43. package/lib/core/notification-queue-store.js +137 -0
  44. package/lib/core/notification-queue-types.d.ts +49 -0
  45. package/lib/core/notification-queue-types.js +2 -0
  46. package/lib/core/notification-queue.d.ts +11 -72
  47. package/lib/core/notification-queue.js +81 -258
  48. package/lib/core/search-format.d.ts +3 -0
  49. package/lib/core/search-format.js +36 -0
  50. package/lib/core/search-providers.d.ts +13 -0
  51. package/lib/core/search-providers.js +175 -0
  52. package/lib/core/search-rotation.d.ts +4 -0
  53. package/lib/core/search-rotation.js +55 -0
  54. package/lib/core/search-service.d.ts +3 -0
  55. package/lib/core/search-service.js +100 -0
  56. package/lib/core/search-types.d.ts +39 -0
  57. package/lib/core/search-types.js +2 -0
  58. package/lib/core/search.d.ts +4 -101
  59. package/lib/core/search.js +10 -508
  60. package/lib/index.js +27 -381
  61. package/lib/tsconfig.tsbuildinfo +1 -1
  62. package/lib/types.d.ts +27 -6
  63. package/lib/utils/legacy-config.d.ts +12 -0
  64. package/lib/utils/legacy-config.js +56 -0
  65. package/lib/utils/logger.js +50 -29
  66. package/lib/utils/proxy.d.ts +3 -0
  67. package/lib/utils/proxy.js +14 -0
  68. package/lib/utils/structured-logger.d.ts +7 -3
  69. package/lib/utils/structured-logger.js +26 -19
  70. package/package.json +1 -1
@@ -0,0 +1,19 @@
1
+ import { Context } from 'koishi';
2
+ import { NewQueueTask, QueueCreateResult, QueueStats, QueueTask, QueueTaskContent, QueueTaskIdentity } from './notification-queue-types';
3
+ export declare const RSS_NOTIFICATION_QUEUE_TABLE = "rss_notification_queue";
4
+ export declare class NotificationQueueStore {
5
+ private ctx;
6
+ private batchSize;
7
+ constructor(ctx: Context, batchSize: number);
8
+ findTaskByIdentity(identity: QueueTaskIdentity): Promise<QueueTask | null>;
9
+ createTask(task: NewQueueTask): Promise<QueueCreateResult>;
10
+ getPendingTasks(): Promise<QueueTask[]>;
11
+ markTaskSuccess(taskId: number): Promise<void>;
12
+ markTaskRetry(task: QueueTask, nextTime: Date, reason: string): Promise<void>;
13
+ updateTaskForDowngrade(task: QueueTask, content: QueueTaskContent): Promise<void>;
14
+ markTaskFailed(taskId: number, reason: string): Promise<void>;
15
+ recoverRetryTasksWithoutNextRetryTime(): Promise<number>;
16
+ getStats(): Promise<QueueStats>;
17
+ retryFailedTasks(taskId?: number): Promise<number>;
18
+ cleanupSuccessTasks(olderThanHours?: number): Promise<number>;
19
+ }
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NotificationQueueStore = exports.RSS_NOTIFICATION_QUEUE_TABLE = void 0;
4
+ exports.RSS_NOTIFICATION_QUEUE_TABLE = 'rss_notification_queue';
5
+ class NotificationQueueStore {
6
+ ctx;
7
+ batchSize;
8
+ constructor(ctx, batchSize) {
9
+ this.ctx = ctx;
10
+ this.batchSize = batchSize;
11
+ }
12
+ async findTaskByIdentity(identity) {
13
+ const tasks = await this.ctx.database.get(exports.RSS_NOTIFICATION_QUEUE_TABLE, identity);
14
+ if (!tasks.length) {
15
+ return null;
16
+ }
17
+ return [...tasks].sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())[0];
18
+ }
19
+ async createTask(task) {
20
+ const existingTask = await this.findTaskByIdentity({
21
+ subscribeId: task.subscribeId,
22
+ uid: task.uid,
23
+ guildId: task.guildId,
24
+ platform: task.platform,
25
+ });
26
+ if (existingTask) {
27
+ return {
28
+ task: existingTask,
29
+ created: false,
30
+ };
31
+ }
32
+ const queueTask = {
33
+ ...task,
34
+ status: 'PENDING',
35
+ retryCount: 0,
36
+ createdAt: new Date(),
37
+ updatedAt: new Date(),
38
+ };
39
+ const createdTask = await this.ctx.database.create(exports.RSS_NOTIFICATION_QUEUE_TABLE, queueTask);
40
+ return {
41
+ task: {
42
+ ...queueTask,
43
+ ...createdTask,
44
+ },
45
+ created: true,
46
+ };
47
+ }
48
+ async getPendingTasks() {
49
+ const now = new Date();
50
+ const pendingTasks = await this.ctx.database.get(exports.RSS_NOTIFICATION_QUEUE_TABLE, { status: 'PENDING' }, { limit: this.batchSize });
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);
56
+ }
57
+ async markTaskSuccess(taskId) {
58
+ await this.ctx.database.set(exports.RSS_NOTIFICATION_QUEUE_TABLE, { id: taskId }, {
59
+ status: 'SUCCESS',
60
+ nextRetryTime: null,
61
+ failReason: null,
62
+ updatedAt: new Date(),
63
+ });
64
+ }
65
+ async markTaskRetry(task, nextTime, reason) {
66
+ await this.ctx.database.set(exports.RSS_NOTIFICATION_QUEUE_TABLE, { id: task.id }, {
67
+ status: 'RETRY',
68
+ nextRetryTime: nextTime,
69
+ retryCount: (task.retryCount || 0) + 1,
70
+ failReason: reason,
71
+ updatedAt: new Date(),
72
+ });
73
+ }
74
+ async updateTaskForDowngrade(task, content) {
75
+ await this.ctx.database.set(exports.RSS_NOTIFICATION_QUEUE_TABLE, { id: task.id }, {
76
+ content,
77
+ status: 'RETRY',
78
+ nextRetryTime: new Date(),
79
+ retryCount: (task.retryCount || 0) + 1,
80
+ failReason: null,
81
+ updatedAt: new Date(),
82
+ });
83
+ }
84
+ async markTaskFailed(taskId, reason) {
85
+ await this.ctx.database.set(exports.RSS_NOTIFICATION_QUEUE_TABLE, { id: taskId }, {
86
+ status: 'FAILED',
87
+ nextRetryTime: null,
88
+ failReason: reason,
89
+ updatedAt: new Date(),
90
+ });
91
+ }
92
+ async recoverRetryTasksWithoutNextRetryTime() {
93
+ const retryTasks = await this.ctx.database.get(exports.RSS_NOTIFICATION_QUEUE_TABLE, { status: 'RETRY' }, { limit: this.batchSize });
94
+ const invalidTasks = retryTasks.filter(task => !task.nextRetryTime);
95
+ for (const task of invalidTasks) {
96
+ await this.ctx.database.set(exports.RSS_NOTIFICATION_QUEUE_TABLE, { id: task.id }, {
97
+ status: 'RETRY',
98
+ nextRetryTime: new Date(),
99
+ updatedAt: new Date(),
100
+ });
101
+ }
102
+ return invalidTasks.length;
103
+ }
104
+ async getStats() {
105
+ const allTasks = await this.ctx.database.get(exports.RSS_NOTIFICATION_QUEUE_TABLE, {});
106
+ return {
107
+ pending: allTasks.filter((task) => task.status === 'PENDING').length,
108
+ retry: allTasks.filter((task) => task.status === 'RETRY').length,
109
+ failed: allTasks.filter((task) => task.status === 'FAILED').length,
110
+ success: allTasks.filter((task) => task.status === 'SUCCESS').length,
111
+ };
112
+ }
113
+ async retryFailedTasks(taskId) {
114
+ const where = taskId ? { id: taskId } : { status: 'FAILED' };
115
+ const tasks = await this.ctx.database.get(exports.RSS_NOTIFICATION_QUEUE_TABLE, where);
116
+ const failedTasks = tasks.filter(task => task.status === 'FAILED');
117
+ for (const task of failedTasks) {
118
+ await this.ctx.database.set(exports.RSS_NOTIFICATION_QUEUE_TABLE, { id: task.id }, {
119
+ status: 'PENDING',
120
+ retryCount: 0,
121
+ nextRetryTime: null,
122
+ failReason: null,
123
+ updatedAt: new Date(),
124
+ });
125
+ }
126
+ return failedTasks.length;
127
+ }
128
+ async cleanupSuccessTasks(olderThanHours = 24) {
129
+ const cutoffTime = new Date(Date.now() - olderThanHours * 60 * 60 * 1000);
130
+ const tasks = await this.ctx.database.get(exports.RSS_NOTIFICATION_QUEUE_TABLE, { status: 'SUCCESS', updatedAt: { $lt: cutoffTime } });
131
+ for (const task of tasks) {
132
+ await this.ctx.database.remove(exports.RSS_NOTIFICATION_QUEUE_TABLE, { id: task.id });
133
+ }
134
+ return tasks.length;
135
+ }
136
+ }
137
+ exports.NotificationQueueStore = NotificationQueueStore;
@@ -0,0 +1,49 @@
1
+ export type QueueStatus = 'PENDING' | 'RETRY' | 'FAILED' | 'SUCCESS';
2
+ /**
3
+ * 队列任务内容
4
+ */
5
+ export interface QueueTaskContent {
6
+ message: string;
7
+ originalItem?: any;
8
+ isDowngraded?: boolean;
9
+ title?: string;
10
+ description?: string;
11
+ link?: string;
12
+ pubDate?: Date;
13
+ imageUrl?: string;
14
+ }
15
+ /**
16
+ * 队列任务接口
17
+ */
18
+ export interface QueueTask {
19
+ id?: number;
20
+ subscribeId: string;
21
+ rssId: string;
22
+ uid: string;
23
+ guildId: string;
24
+ platform: string;
25
+ content: QueueTaskContent;
26
+ status: QueueStatus;
27
+ retryCount: number;
28
+ nextRetryTime?: Date;
29
+ createdAt: Date;
30
+ updatedAt: Date;
31
+ failReason?: string | null;
32
+ }
33
+ export type NewQueueTask = Omit<QueueTask, 'id' | 'status' | 'retryCount' | 'createdAt' | 'updatedAt'>;
34
+ export interface QueueTaskIdentity {
35
+ subscribeId: string;
36
+ uid: string;
37
+ guildId: string;
38
+ platform: string;
39
+ }
40
+ export interface QueueCreateResult {
41
+ task: QueueTask;
42
+ created: boolean;
43
+ }
44
+ export interface QueueStats {
45
+ pending: number;
46
+ retry: number;
47
+ failed: number;
48
+ success: number;
49
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,41 +1,7 @@
1
- /**
2
- * 消息发送队列管理器
3
- * 实现可靠的消息推送,支持重试、降级和错误处理
4
- */
5
1
  import { Context } from 'koishi';
6
2
  import { Config } from '../types';
7
- export type QueueStatus = 'PENDING' | 'RETRY' | 'FAILED' | 'SUCCESS';
8
- /**
9
- * 队列任务接口
10
- */
11
- export interface QueueTask {
12
- id?: number;
13
- subscribeId: string;
14
- rssId: string;
15
- uid: string;
16
- guildId: string;
17
- platform: string;
18
- content: QueueTaskContent;
19
- status: QueueStatus;
20
- retryCount: number;
21
- nextRetryTime?: Date;
22
- createdAt: Date;
23
- updatedAt: Date;
24
- failReason?: string;
25
- }
26
- /**
27
- * 队列任务内容
28
- */
29
- export interface QueueTaskContent {
30
- message: string;
31
- originalItem?: any;
32
- isDowngraded?: boolean;
33
- title?: string;
34
- description?: string;
35
- link?: string;
36
- pubDate?: Date;
37
- imageUrl?: string;
38
- }
3
+ import { NewQueueTask, QueueStats, QueueTask } from './notification-queue-types';
4
+ export type { QueueStatus, QueueTask, QueueTaskContent, QueueTaskIdentity, QueueCreateResult } from './notification-queue-types';
39
5
  /**
40
6
  * 消息发送队列管理器
41
7
  */
@@ -43,8 +9,12 @@ export declare class NotificationQueueManager {
43
9
  private ctx;
44
10
  private config;
45
11
  private processing;
46
- private maxRetries;
12
+ private recovered;
47
13
  private batchSize;
14
+ private maxRetries;
15
+ private cleanupHours;
16
+ private store;
17
+ private sender;
48
18
  private backoffDelays;
49
19
  constructor(ctx: Context, config: Config);
50
20
  private buildTaskLogContext;
@@ -52,23 +22,17 @@ export declare class NotificationQueueManager {
52
22
  /**
53
23
  * 添加任务到队列
54
24
  */
55
- addTask(task: Omit<QueueTask, 'id' | 'status' | 'retryCount' | 'createdAt' | 'updatedAt'>): Promise<QueueTask>;
25
+ addTask(task: NewQueueTask): Promise<QueueTask>;
26
+ isProcessing(): boolean;
27
+ private ensureRecovered;
56
28
  /**
57
29
  * 处理队列中的任务
58
30
  */
59
31
  processQueue(): Promise<void>;
60
- /**
61
- * 获取待处理任务
62
- */
63
- private getPendingTasks;
64
32
  /**
65
33
  * 处理单个任务
66
34
  */
67
35
  private processTask;
68
- /**
69
- * 发送消息(带降级机制)
70
- */
71
- private sendMessage;
72
36
  /**
73
37
  * 处理发送错误
74
38
  */
@@ -81,35 +45,10 @@ export declare class NotificationQueueManager {
81
45
  * 降级消息(移除媒体元素)
82
46
  */
83
47
  private downgradeMessage;
84
- /**
85
- * 标记任务为成功
86
- */
87
- private markTaskSuccess;
88
- /**
89
- * 标记任务为重试
90
- */
91
- private markTaskRetry;
92
- /**
93
- * 更新任务为降级重试
94
- */
95
- private updateTaskForDowngrade;
96
- /**
97
- * 标记任务为失败
98
- */
99
- private markTaskFailed;
100
- /**
101
- * 缓存成功发送的消息
102
- */
103
- private cacheMessage;
104
48
  /**
105
49
  * 获取队列统计信息
106
50
  */
107
- getStats(): Promise<{
108
- pending: number;
109
- retry: number;
110
- failed: number;
111
- success: number;
112
- }>;
51
+ getStats(): Promise<QueueStats>;
113
52
  /**
114
53
  * 重试失败的任务
115
54
  */