@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.
- package/lib/commands/index.js +1 -3
- package/lib/commands/subscription-create.d.ts +2 -0
- package/lib/commands/subscription-create.js +2 -2
- package/lib/commands/subscription-edit.d.ts +2 -0
- package/lib/commands/subscription-edit.js +8 -12
- package/lib/commands/subscription-management.d.ts +2 -0
- package/lib/commands/subscription-management.js +10 -12
- package/lib/commands/utils.d.ts +0 -5
- package/lib/commands/utils.js +0 -16
- package/lib/commands/web-monitor.d.ts +2 -0
- package/lib/commands/web-monitor.js +2 -2
- package/lib/config.js +4 -10
- package/lib/core/feeder-arg.js +3 -38
- package/lib/core/feeder-runtime.js +0 -5
- package/lib/core/feeder.js +53 -12
- package/lib/core/item-processor-runtime.d.ts +4 -2
- package/lib/core/item-processor-runtime.js +4 -12
- package/lib/core/notification-queue-sender.js +0 -5
- package/lib/core/notification-queue-store.d.ts +7 -0
- package/lib/core/notification-queue-store.js +28 -6
- package/lib/core/notification-queue.d.ts +10 -0
- package/lib/core/notification-queue.js +18 -7
- package/lib/core/parser.js +5 -5
- package/lib/core/renderer.js +7 -7
- package/lib/core/search-format.d.ts +1 -1
- package/lib/core/search-format.js +1 -1
- package/lib/core/subscription-store.d.ts +44 -0
- package/lib/core/subscription-store.js +60 -0
- package/lib/core/telegram-video-restore.js +3 -8
- package/lib/index.d.ts +1 -4
- package/lib/index.js +20 -15
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/types.d.ts +51 -32
- package/lib/utils/common.d.ts +6 -0
- package/lib/utils/common.js +20 -30
- package/lib/utils/error-handler.d.ts +49 -0
- package/lib/utils/error-handler.js +111 -1
- package/lib/utils/fetcher.js +12 -38
- package/lib/utils/legacy-config.js +9 -3
- package/lib/utils/logger.d.ts +6 -0
- package/lib/utils/logger.js +82 -14
- package/lib/utils/media.js +21 -21
- package/lib/utils/message-cache.d.ts +8 -0
- package/lib/utils/message-cache.js +12 -0
- package/lib/utils/proxy.d.ts +37 -0
- package/lib/utils/proxy.js +60 -0
- package/package.json +16 -3
- package/lib/commands/error-handler.d.ts +0 -53
- package/lib/commands/error-handler.js +0 -120
- package/lib/commands/rss-subscribe.d.ts +0 -11
- package/lib/commands/rss-subscribe.js +0 -323
- package/lib/core/feeder-old.d.ts +0 -15
- package/lib/core/feeder-old.js +0 -403
- package/lib/utils/error-tracker.d.ts +0 -127
- package/lib/utils/error-tracker.js +0 -352
- package/lib/utils/metrics.d.ts +0 -184
- package/lib/utils/metrics.js +0 -401
- package/lib/utils/structured-logger.d.ts +0 -135
- 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 =
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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;
|
package/lib/core/parser.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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),
|
package/lib/core/renderer.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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, '"');
|
|
239
240
|
}
|
|
240
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
89
|
-
|
|
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,
|