@anyul/koishi-plugin-rss 5.2.2 → 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 (90) hide show
  1. package/README.md +92 -37
  2. package/lib/commands/error-handler.js +13 -4
  3. package/lib/commands/index.d.ts +20 -1
  4. package/lib/commands/index.js +394 -2
  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/subscription-edit.d.ts +7 -0
  10. package/lib/commands/subscription-edit.js +177 -0
  11. package/lib/commands/subscription-management.d.ts +12 -0
  12. package/lib/commands/subscription-management.js +176 -0
  13. package/lib/commands/utils.d.ts +13 -1
  14. package/lib/commands/utils.js +43 -2
  15. package/lib/commands/web-monitor.d.ts +15 -0
  16. package/lib/commands/web-monitor.js +222 -0
  17. package/lib/config.js +25 -0
  18. package/lib/constants.d.ts +1 -1
  19. package/lib/constants.js +46 -83
  20. package/lib/core/ai-cache.d.ts +27 -0
  21. package/lib/core/ai-cache.js +169 -0
  22. package/lib/core/ai-client.d.ts +12 -0
  23. package/lib/core/ai-client.js +65 -0
  24. package/lib/core/ai-selector.d.ts +2 -0
  25. package/lib/core/ai-selector.js +80 -0
  26. package/lib/core/ai-summary.d.ts +10 -0
  27. package/lib/core/ai-summary.js +73 -0
  28. package/lib/core/ai-utils.d.ts +10 -0
  29. package/lib/core/ai-utils.js +104 -0
  30. package/lib/core/ai.d.ts +3 -77
  31. package/lib/core/ai.js +13 -455
  32. package/lib/core/feeder-arg.d.ts +17 -0
  33. package/lib/core/feeder-arg.js +234 -0
  34. package/lib/core/feeder-runtime.d.ts +96 -0
  35. package/lib/core/feeder-runtime.js +233 -0
  36. package/lib/core/feeder.d.ts +4 -6
  37. package/lib/core/feeder.js +120 -304
  38. package/lib/core/item-processor-runtime.d.ts +46 -0
  39. package/lib/core/item-processor-runtime.js +215 -0
  40. package/lib/core/item-processor-template.d.ts +16 -0
  41. package/lib/core/item-processor-template.js +158 -0
  42. package/lib/core/item-processor.d.ts +1 -10
  43. package/lib/core/item-processor.js +48 -393
  44. package/lib/core/notification-queue-retry.d.ts +25 -0
  45. package/lib/core/notification-queue-retry.js +78 -0
  46. package/lib/core/notification-queue-sender.d.ts +20 -0
  47. package/lib/core/notification-queue-sender.js +118 -0
  48. package/lib/core/notification-queue-store.d.ts +19 -0
  49. package/lib/core/notification-queue-store.js +137 -0
  50. package/lib/core/notification-queue-types.d.ts +49 -0
  51. package/lib/core/notification-queue-types.js +2 -0
  52. package/lib/core/notification-queue.d.ts +13 -72
  53. package/lib/core/notification-queue.js +132 -262
  54. package/lib/core/parser.js +12 -0
  55. package/lib/core/renderer.d.ts +15 -0
  56. package/lib/core/renderer.js +91 -23
  57. package/lib/core/search-format.d.ts +3 -0
  58. package/lib/core/search-format.js +36 -0
  59. package/lib/core/search-providers.d.ts +13 -0
  60. package/lib/core/search-providers.js +175 -0
  61. package/lib/core/search-rotation.d.ts +4 -0
  62. package/lib/core/search-rotation.js +55 -0
  63. package/lib/core/search-service.d.ts +3 -0
  64. package/lib/core/search-service.js +100 -0
  65. package/lib/core/search-types.d.ts +39 -0
  66. package/lib/core/search-types.js +2 -0
  67. package/lib/core/search.d.ts +4 -101
  68. package/lib/core/search.js +10 -508
  69. package/lib/index.js +50 -1160
  70. package/lib/tsconfig.tsbuildinfo +1 -1
  71. package/lib/types.d.ts +51 -6
  72. package/lib/utils/common.js +52 -3
  73. package/lib/utils/error-handler.d.ts +8 -0
  74. package/lib/utils/error-handler.js +27 -0
  75. package/lib/utils/error-tracker.js +24 -8
  76. package/lib/utils/fetcher.js +68 -9
  77. package/lib/utils/legacy-config.d.ts +12 -0
  78. package/lib/utils/legacy-config.js +56 -0
  79. package/lib/utils/logger.d.ts +4 -2
  80. package/lib/utils/logger.js +193 -34
  81. package/lib/utils/media.js +3 -6
  82. package/lib/utils/proxy.d.ts +3 -0
  83. package/lib/utils/proxy.js +14 -0
  84. package/lib/utils/sanitizer.d.ts +58 -0
  85. package/lib/utils/sanitizer.js +227 -0
  86. package/lib/utils/security.d.ts +75 -0
  87. package/lib/utils/security.js +312 -0
  88. package/lib/utils/structured-logger.d.ts +7 -3
  89. package/lib/utils/structured-logger.js +29 -39
  90. package/package.json +2 -1
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.NotificationQueueSender = void 0;
37
+ exports.downgradeQueueMessage = downgradeQueueMessage;
38
+ const error_handler_1 = require("../utils/error-handler");
39
+ const error_tracker_1 = require("../utils/error-tracker");
40
+ const notification_queue_retry_1 = require("./notification-queue-retry");
41
+ function downgradeQueueMessage(content) {
42
+ if (content.isDowngraded) {
43
+ return {
44
+ ...content,
45
+ isDowngraded: true,
46
+ };
47
+ }
48
+ const downgradedMessage = content.message.replace(/<video[^>]*>.*?<\/video>/gis, (match) => {
49
+ const srcMatch = match.match(/src=["']([^"']+)["']/);
50
+ if (srcMatch) {
51
+ return `\n🎬 视频: ${srcMatch[1]}\n`;
52
+ }
53
+ return '\n[视频不支持]\n';
54
+ });
55
+ return {
56
+ ...content,
57
+ message: downgradedMessage,
58
+ isDowngraded: true,
59
+ };
60
+ }
61
+ class NotificationQueueSender {
62
+ deps;
63
+ constructor(deps) {
64
+ this.deps = deps;
65
+ }
66
+ async sendMessage(task) {
67
+ const { guildId, platform, content } = task;
68
+ const target = `${platform}:${guildId}`;
69
+ const taskDebug = this.deps.createTaskDebug(task);
70
+ try {
71
+ await this.deps.ctx.broadcast([target], content.message);
72
+ taskDebug(`消息发送成功: ${target}`, 'queue', 'details');
73
+ }
74
+ catch (sendError) {
75
+ if ((0, notification_queue_retry_1.isQueueDowngradeError)(sendError) && !content.isDowngraded) {
76
+ taskDebug('检测到 OneBot 1200 错误,尝试降级处理', 'queue', 'info', { errorCode: '1200' });
77
+ }
78
+ throw sendError;
79
+ }
80
+ }
81
+ async downgradeMessage(content) {
82
+ return downgradeQueueMessage(content);
83
+ }
84
+ async cacheMessage(task) {
85
+ if (!this.deps.config.cache?.enabled) {
86
+ return;
87
+ }
88
+ const taskDebug = this.deps.createTaskDebug(task);
89
+ const { getMessageCache } = await Promise.resolve().then(() => __importStar(require('../utils/message-cache')));
90
+ const cache = getMessageCache();
91
+ if (!cache) {
92
+ return;
93
+ }
94
+ try {
95
+ await cache.addMessage({
96
+ rssId: task.rssId,
97
+ guildId: task.guildId,
98
+ platform: task.platform,
99
+ title: task.content.title || '',
100
+ content: task.content.description || '',
101
+ link: task.content.link || '',
102
+ pubDate: task.content.pubDate || new Date(),
103
+ imageUrl: task.content.imageUrl || '',
104
+ videoUrl: '',
105
+ finalMessage: task.content.message,
106
+ });
107
+ }
108
+ catch (err) {
109
+ const normalizedError = (0, error_handler_1.normalizeError)(err);
110
+ taskDebug(`缓存消息失败: ${normalizedError.message}`, 'cache', 'info');
111
+ (0, error_tracker_1.trackError)(normalizedError, {
112
+ ...this.deps.buildTaskLogContext(task),
113
+ operation: 'cacheMessage',
114
+ });
115
+ }
116
+ }
117
+ }
118
+ exports.NotificationQueueSender = NotificationQueueSender;
@@ -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,30 +9,30 @@ 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);
20
+ private buildTaskLogContext;
21
+ private createTaskDebug;
50
22
  /**
51
23
  * 添加任务到队列
52
24
  */
53
- addTask(task: Omit<QueueTask, 'id' | 'status' | 'retryCount' | 'createdAt' | 'updatedAt'>): Promise<QueueTask>;
25
+ addTask(task: NewQueueTask): Promise<QueueTask>;
26
+ isProcessing(): boolean;
27
+ private ensureRecovered;
54
28
  /**
55
29
  * 处理队列中的任务
56
30
  */
57
31
  processQueue(): Promise<void>;
58
- /**
59
- * 获取待处理任务
60
- */
61
- private getPendingTasks;
62
32
  /**
63
33
  * 处理单个任务
64
34
  */
65
35
  private processTask;
66
- /**
67
- * 发送消息(带降级机制)
68
- */
69
- private sendMessage;
70
36
  /**
71
37
  * 处理发送错误
72
38
  */
@@ -79,35 +45,10 @@ export declare class NotificationQueueManager {
79
45
  * 降级消息(移除媒体元素)
80
46
  */
81
47
  private downgradeMessage;
82
- /**
83
- * 标记任务为成功
84
- */
85
- private markTaskSuccess;
86
- /**
87
- * 标记任务为重试
88
- */
89
- private markTaskRetry;
90
- /**
91
- * 更新任务为降级重试
92
- */
93
- private updateTaskForDowngrade;
94
- /**
95
- * 标记任务为失败
96
- */
97
- private markTaskFailed;
98
- /**
99
- * 缓存成功发送的消息
100
- */
101
- private cacheMessage;
102
48
  /**
103
49
  * 获取队列统计信息
104
50
  */
105
- getStats(): Promise<{
106
- pending: number;
107
- retry: number;
108
- failed: number;
109
- success: number;
110
- }>;
51
+ getStats(): Promise<QueueStats>;
111
52
  /**
112
53
  * 重试失败的任务
113
54
  */