@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
package/lib/utils/logger.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.logger = void 0;
|
|
4
|
+
exports.applyDebugLevel = applyDebugLevel;
|
|
4
5
|
exports.shouldLog = shouldLog;
|
|
5
6
|
exports.debug = debug;
|
|
6
7
|
exports.debugError = debugError;
|
|
@@ -10,6 +11,47 @@ const koishi_1 = require("koishi");
|
|
|
10
11
|
const types_1 = require("../types");
|
|
11
12
|
const logger = new koishi_1.Logger('rss-owl');
|
|
12
13
|
exports.logger = logger;
|
|
14
|
+
/**
|
|
15
|
+
* 把 config.debug 映射到 Koishi/reggol 的原生日志级别数值。
|
|
16
|
+
*
|
|
17
|
+
* reggol 级别(见 reggol/index.d.ts):
|
|
18
|
+
* SILENT=0, SUCCESS/ERROR=1, INFO/WARN=2, DEBUG=3
|
|
19
|
+
*
|
|
20
|
+
* 本插件的 debug 字段语义:
|
|
21
|
+
* disable → 0(SILENT,什么也不输出)
|
|
22
|
+
* error → 1(仅 error)
|
|
23
|
+
* info → 2(error + info/warn)
|
|
24
|
+
* details → 3(含 debug 全量)
|
|
25
|
+
*/
|
|
26
|
+
function debugLevelToReggol(debug) {
|
|
27
|
+
switch (debug) {
|
|
28
|
+
case 'error': return koishi_1.Logger.ERROR;
|
|
29
|
+
case 'info': return koishi_1.Logger.INFO;
|
|
30
|
+
case 'details': return koishi_1.Logger.DEBUG;
|
|
31
|
+
default: return koishi_1.Logger.SILENT;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 把 config.debug 同步到 Koishi 原生日志分级 `Logger.levels['rss-owl']`。
|
|
36
|
+
*
|
|
37
|
+
* 这样本插件的日志可见性既受自身 debug 字段控制,也能被 Koishi 全局 levels
|
|
38
|
+
* (WebUI 的 logger 插件、配置文件中的 levels.base / levels['rss-owl'])覆盖/调节。
|
|
39
|
+
* 应在 apply() 启动时调用一次;值未变化时不重复写入。
|
|
40
|
+
*/
|
|
41
|
+
let lastAppliedLevel;
|
|
42
|
+
function applyDebugLevel(config) {
|
|
43
|
+
const target = debugLevelToReggol(config.debug);
|
|
44
|
+
if (lastAppliedLevel === target)
|
|
45
|
+
return;
|
|
46
|
+
lastAppliedLevel = target;
|
|
47
|
+
koishi_1.Logger.levels = koishi_1.Logger.levels || { base: 2 };
|
|
48
|
+
koishi_1.Logger.levels['rss-owl'] = target;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 按 config.debug 判定某级是否应输出。
|
|
52
|
+
* 在 debug() 中作为性能预检门控(避免对注定被丢弃的日志做脱敏/格式化),
|
|
53
|
+
* 与 applyDebugLevel 同步到 Koishi levels 的语义保持一致;最终输出与否仍由 reggol levels 决定。
|
|
54
|
+
*/
|
|
13
55
|
function shouldLog(config, type) {
|
|
14
56
|
const typeLevel = types_1.debugLevel.findIndex(i => i === type);
|
|
15
57
|
if (typeLevel < 1)
|
|
@@ -19,6 +61,22 @@ function shouldLog(config, type) {
|
|
|
19
61
|
return false;
|
|
20
62
|
return typeLevel <= configLevel;
|
|
21
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* 子命名空间 logger 缓存,避免每条日志都 extend。
|
|
66
|
+
* 用 logger.extend('feeder') 产出 rss-owl:feeder,命名空间天然带模块名,
|
|
67
|
+
* 取代此前 formatTextLog 里手工拼 [name] 前缀的做法(与 Koishi Logger 命名空间重复)。
|
|
68
|
+
*/
|
|
69
|
+
const subLoggers = new Map();
|
|
70
|
+
function getSubLogger(name) {
|
|
71
|
+
if (!name)
|
|
72
|
+
return logger;
|
|
73
|
+
let sub = subLoggers.get(name);
|
|
74
|
+
if (!sub) {
|
|
75
|
+
sub = logger.extend(name);
|
|
76
|
+
subLoggers.set(name, sub);
|
|
77
|
+
}
|
|
78
|
+
return sub;
|
|
79
|
+
}
|
|
22
80
|
/**
|
|
23
81
|
* 敏感信息模式定义
|
|
24
82
|
*/
|
|
@@ -129,12 +187,21 @@ function sanitizeObject(obj, depth = 0) {
|
|
|
129
187
|
}
|
|
130
188
|
return obj;
|
|
131
189
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
190
|
+
/**
|
|
191
|
+
* 按 Koishi 原生日志级别输出。
|
|
192
|
+
*
|
|
193
|
+
* 关键纠错:'details' 之前被错压成 logger.info(),现归到 logger.debug(),
|
|
194
|
+
* 让 reggol 的 levels(SILENT=0 / ERROR=1 / INFO=2 / DEBUG=3)能正确分级过滤。
|
|
195
|
+
* 模块名 name 通过子命名空间 logger.extend(name) 体现(如 rss-owl:feeder),
|
|
196
|
+
* 不再手工拼 [name] 前缀。
|
|
197
|
+
*/
|
|
198
|
+
function emitLog(type, content, name = '') {
|
|
199
|
+
const target = getSubLogger(name);
|
|
200
|
+
if (type === 'error')
|
|
201
|
+
return target.error(content);
|
|
202
|
+
if (type === 'info')
|
|
203
|
+
return target.info(content);
|
|
204
|
+
return target.debug(content); // 'details'
|
|
138
205
|
}
|
|
139
206
|
function filterContextFields(context, contextFields) {
|
|
140
207
|
if (!contextFields?.length) {
|
|
@@ -163,12 +230,10 @@ function formatContextValue(value) {
|
|
|
163
230
|
return String(value);
|
|
164
231
|
}
|
|
165
232
|
function formatTextLog(message, name, context, loggingConfig) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
parts.push(message);
|
|
171
|
-
const textOutput = parts.filter(Boolean).join(' ').trim();
|
|
233
|
+
// 模块名不再手工拼前缀:已由子命名空间 logger(rss-owl:<name>)体现,
|
|
234
|
+
// 避免与 Koishi Logger 命名空间重复输出。
|
|
235
|
+
void name;
|
|
236
|
+
const textOutput = message.trim();
|
|
172
237
|
if (!context || Object.keys(context).length === 0) {
|
|
173
238
|
return textOutput;
|
|
174
239
|
}
|
|
@@ -192,6 +257,9 @@ function formatTextLog(message, name, context, loggingConfig) {
|
|
|
192
257
|
* @param context - 额外的上下文信息
|
|
193
258
|
*/
|
|
194
259
|
function debug(config, message, name = '', type = 'details', context) {
|
|
260
|
+
// 性能预检:按 config.debug 快速跳过注定被 Koishi levels 丢弃的日志,
|
|
261
|
+
// 避免无谓的脱敏/格式化开销。最终是否真正输出由 reggol levels 决定
|
|
262
|
+
// (applyDebugLevel 已把 config.debug 同步到 Logger.levels['rss-owl'])。
|
|
195
263
|
if (!shouldLog(config, type))
|
|
196
264
|
return;
|
|
197
265
|
// 检查是否启用日志脱敏(默认启用)
|
|
@@ -258,10 +326,10 @@ function debug(config, message, name = '', type = 'details', context) {
|
|
|
258
326
|
}
|
|
259
327
|
}
|
|
260
328
|
// 输出结构化日志
|
|
261
|
-
emitLog(type, JSON.stringify(logEntry));
|
|
329
|
+
emitLog(type, JSON.stringify(logEntry), name);
|
|
262
330
|
}
|
|
263
331
|
else {
|
|
264
|
-
emitLog(type, formatTextLog(formattedMessage, name, context, loggingConfig));
|
|
332
|
+
emitLog(type, formatTextLog(formattedMessage, name, context, loggingConfig), name);
|
|
265
333
|
}
|
|
266
334
|
}
|
|
267
335
|
/**
|
package/lib/utils/media.js
CHANGED
|
@@ -50,10 +50,10 @@ async function tryUnlink(filePath) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
const getCacheDir = (config) => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
const dir = config.basic.cacheDir ? path.resolve('./', config.basic.cacheDir || "") : `${__dirname}/cache`;
|
|
54
|
+
const mkdir = (path, deep = 2) => {
|
|
55
|
+
const dir = path.split("\\").splice(0, deep).join("\\");
|
|
56
|
+
const dirDeep = path.split("\\").length;
|
|
57
57
|
if (!fs.existsSync(dir)) {
|
|
58
58
|
fs.mkdirSync(dir);
|
|
59
59
|
}
|
|
@@ -68,11 +68,11 @@ exports.getCacheDir = getCacheDir;
|
|
|
68
68
|
const writeCacheFile = async (fileUrl, config) => {
|
|
69
69
|
const cacheDir = (0, exports.getCacheDir)(config);
|
|
70
70
|
(0, logger_1.debug)(config, cacheDir, 'cacheDir', 'details');
|
|
71
|
-
|
|
71
|
+
const suffix = /(?<=^data:.+?\/).+?(?=;base64)/.exec(fileUrl)?.[0] || 'bin';
|
|
72
72
|
// 使用时间戳 + 随机数生成唯一文件名,避免竞态条件
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
const fileName = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}.${suffix}`;
|
|
74
|
+
const base64Data = fileUrl.replace(/^data:.+?;base64,/, "");
|
|
75
|
+
const filePath = `${cacheDir}/${fileName}`;
|
|
76
76
|
fs.writeFileSync(filePath, base64Data, 'base64');
|
|
77
77
|
if (config.basic.replaceDir) {
|
|
78
78
|
return `file:///${config.basic.replaceDir}/${fileName}`;
|
|
@@ -120,10 +120,10 @@ const getImageUrl = async (ctx, config, $http, url, arg, useBase64Mode = false)
|
|
|
120
120
|
(0, logger_1.debug)(config, `图片请求失败: ${error}`, 'img error', 'error');
|
|
121
121
|
return '';
|
|
122
122
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
123
|
+
const contentType = res.headers["content-type"] || 'image/jpeg';
|
|
124
|
+
const suffix = contentType?.split('/')[1] || 'jpg';
|
|
125
|
+
const base64Prefix = `data:${contentType};base64,`;
|
|
126
|
+
const base64Data = base64Prefix + Buffer.from(res.data, 'binary').toString('base64');
|
|
127
127
|
// 根据发送模式处理
|
|
128
128
|
const imageMode = config.basic.imageMode;
|
|
129
129
|
// base64 模式:直接返回 base64
|
|
@@ -132,13 +132,13 @@ const getImageUrl = async (ctx, config, $http, url, arg, useBase64Mode = false)
|
|
|
132
132
|
}
|
|
133
133
|
// File 模式:下载到本地,返回 file:// URL
|
|
134
134
|
if (imageMode == 'File') {
|
|
135
|
-
|
|
135
|
+
const fileUrl = await (0, exports.writeCacheFile)(base64Data, config);
|
|
136
136
|
return fileUrl;
|
|
137
137
|
}
|
|
138
138
|
// assets 模式:下载到本地,上传到 assets,返回 assets URL
|
|
139
139
|
if (imageMode === 'assets' && ctx.assets) {
|
|
140
140
|
try {
|
|
141
|
-
|
|
141
|
+
const assetUrl = await ctx.assets.upload(base64Data, `rss-img-${Date.now()}.${suffix}`);
|
|
142
142
|
(0, logger_1.debug)(config, `图片 Assets 上传成功: ${assetUrl}`, 'assets', 'info');
|
|
143
143
|
return assetUrl;
|
|
144
144
|
}
|
|
@@ -152,7 +152,7 @@ const getImageUrl = async (ctx, config, $http, url, arg, useBase64Mode = false)
|
|
|
152
152
|
};
|
|
153
153
|
exports.getImageUrl = getImageUrl;
|
|
154
154
|
const getVideoUrl = async (ctx, config, $http, url, arg, useBase64Mode = false, dom) => {
|
|
155
|
-
|
|
155
|
+
const src = dom.attribs.src || dom.children["0"].attribs.src;
|
|
156
156
|
// 根据发送模式处理
|
|
157
157
|
const videoMode = config.basic.videoMode;
|
|
158
158
|
// filter 模式:过滤掉所有视频
|
|
@@ -222,23 +222,23 @@ const getVideoUrl = async (ctx, config, $http, url, arg, useBase64Mode = false,
|
|
|
222
222
|
// file:// 已读入内存(bufferData),后续 base64/File/assets 都基于内存数据,
|
|
223
223
|
// 源文件不再需要 —— 立即删,避免几十 MB 视频在缓存目录堆积
|
|
224
224
|
await tryUnlink(localSourcePath);
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
225
|
+
const suffix = contentType?.split('/')[1] || 'mp4';
|
|
226
|
+
const base64Prefix = `data:${contentType};base64,`;
|
|
227
|
+
const base64Data = base64Prefix + bufferData.toString('base64');
|
|
228
228
|
// base64 模式:直接返回 base64(注意:视频 base64 可能非常长)
|
|
229
229
|
if (videoMode === 'base64' || useBase64Mode) {
|
|
230
230
|
return base64Data;
|
|
231
231
|
}
|
|
232
232
|
// File 模式:下载到本地,返回 file:// URL
|
|
233
233
|
if (videoMode === 'File') {
|
|
234
|
-
|
|
234
|
+
const fileUrl = await (0, exports.writeCacheFile)(base64Data, config);
|
|
235
235
|
return fileUrl;
|
|
236
236
|
}
|
|
237
237
|
// assets 模式:下载到本地,上传到 assets,返回 assets URL
|
|
238
238
|
if (videoMode === 'assets' && ctx.assets) {
|
|
239
239
|
try {
|
|
240
240
|
// 注意:大型视频的 base64 字符串可能很长,某些 assets 插件可能处理较慢
|
|
241
|
-
|
|
241
|
+
const assetUrl = await ctx.assets.upload(base64Data, `rss-video-${Date.now()}.${suffix}`);
|
|
242
242
|
(0, logger_1.debug)(config, `视频 Assets 上传成功: ${assetUrl}`, 'assets', 'info');
|
|
243
243
|
return assetUrl;
|
|
244
244
|
}
|
|
@@ -254,7 +254,7 @@ exports.getVideoUrl = getVideoUrl;
|
|
|
254
254
|
const puppeteerToFile = async (ctx, config, puppeteer) => {
|
|
255
255
|
// puppeteer.render() 返回 Element 字符串,格式如: <img src="data:image/png;base64,..."/> 或 <img src="https://..."/>
|
|
256
256
|
// 提取 src 属性
|
|
257
|
-
|
|
257
|
+
const base64 = /(?<=src=").+?(?=")/.exec(puppeteer)?.[0];
|
|
258
258
|
if (!base64) {
|
|
259
259
|
(0, logger_1.debug)(config, `puppeteer render 返回值格式异常: ${puppeteer}`, 'puppeteerToFile', 'error');
|
|
260
260
|
return puppeteer;
|
|
@@ -124,3 +124,11 @@ export declare function initMessageCache(ctx: Context, config: Config, maxSize?:
|
|
|
124
124
|
* 获取全局消息缓存管理器
|
|
125
125
|
*/
|
|
126
126
|
export declare function getMessageCache(): MessageCacheManager | null;
|
|
127
|
+
/**
|
|
128
|
+
* 销毁全局消息缓存管理器
|
|
129
|
+
*
|
|
130
|
+
* 必须在插件 dispose 时调用:MessageCacheManager 构造时持有了 ctx,
|
|
131
|
+
* 若热重载后仍复用旧实例,会对已 dispose 的 ctx 执行数据库操作而报错/静默丢失。
|
|
132
|
+
* 置 null 后,下次 initMessageCache 会用新 ctx 重建实例。
|
|
133
|
+
*/
|
|
134
|
+
export declare function disposeMessageCache(): void;
|
|
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
exports.MessageCacheManager = void 0;
|
|
8
8
|
exports.initMessageCache = initMessageCache;
|
|
9
9
|
exports.getMessageCache = getMessageCache;
|
|
10
|
+
exports.disposeMessageCache = disposeMessageCache;
|
|
10
11
|
const koishi_1 = require("koishi");
|
|
11
12
|
const logger_1 = require("./logger");
|
|
12
13
|
const logger = new koishi_1.Logger('rss-owl:cache');
|
|
@@ -253,3 +254,14 @@ function initMessageCache(ctx, config, maxSize = 100) {
|
|
|
253
254
|
function getMessageCache() {
|
|
254
255
|
return globalMessageCache;
|
|
255
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* 销毁全局消息缓存管理器
|
|
259
|
+
*
|
|
260
|
+
* 必须在插件 dispose 时调用:MessageCacheManager 构造时持有了 ctx,
|
|
261
|
+
* 若热重载后仍复用旧实例,会对已 dispose 的 ctx 执行数据库操作而报错/静默丢失。
|
|
262
|
+
* 置 null 后,下次 initMessageCache 会用新 ctx 重建实例。
|
|
263
|
+
*/
|
|
264
|
+
function disposeMessageCache() {
|
|
265
|
+
globalMessageCache = null;
|
|
266
|
+
logger.info('消息缓存管理器已销毁');
|
|
267
|
+
}
|
package/lib/utils/proxy.d.ts
CHANGED
|
@@ -1,3 +1,40 @@
|
|
|
1
1
|
import type { AxiosRequestConfig } from 'axios';
|
|
2
2
|
import type { Config } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* 代理配置片段(订阅级 / 全局级通用形状)。
|
|
5
|
+
*/
|
|
6
|
+
export interface ProxyAgentShape {
|
|
7
|
+
enabled?: boolean;
|
|
8
|
+
protocol?: string;
|
|
9
|
+
host?: string;
|
|
10
|
+
port?: number | string;
|
|
11
|
+
auth?: {
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
user?: string;
|
|
14
|
+
pass?: string;
|
|
15
|
+
} & Record<string, any>;
|
|
16
|
+
[key: string]: any;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 合并订阅级代理配置 (argProxy) 与全局代理配置 (configProxy)。
|
|
20
|
+
*
|
|
21
|
+
* 优先级:
|
|
22
|
+
* 1. argProxy.enabled === false → 显式禁用
|
|
23
|
+
* 2. argProxy.enabled === true 且有 host → 用订阅配置
|
|
24
|
+
* 3. argProxy 缺失 / 未设置 enabled → 回落全局配置
|
|
25
|
+
* 4. argProxy.enabled === true 但缺 host → 用全局补全
|
|
26
|
+
* 5. 其余 → 禁用
|
|
27
|
+
*
|
|
28
|
+
* 返回值与 `config.net.proxyAgent` 同构,可直接喂给 `buildAxiosProxyConfig`。
|
|
29
|
+
*
|
|
30
|
+
* 本函数原位于 `core/feeder-arg.ts`,提升到此处以统一代理 helper 归属,
|
|
31
|
+
* 供 fetcher / feeder 等模块复用,避免重复实现合并逻辑。
|
|
32
|
+
*/
|
|
33
|
+
export declare function mergeProxyAgent(argProxy: any, configProxy: any, config: Config): ProxyAgentShape;
|
|
34
|
+
/**
|
|
35
|
+
* 构造 axios 的代理配置(httpsAgent + 关闭 axios 原生 proxy)。
|
|
36
|
+
*
|
|
37
|
+
* 接受完整的 Config(读取 config.net.proxyAgent)。对于需要按订阅合并代理的场景,
|
|
38
|
+
* 先用 `mergeProxyAgent` 得到统一片段,再包成 `{ net: { proxyAgent } }` 传入。
|
|
39
|
+
*/
|
|
3
40
|
export declare function buildAxiosProxyConfig(config: Config): Pick<AxiosRequestConfig, 'httpsAgent' | 'proxy'>;
|
package/lib/utils/proxy.js
CHANGED
|
@@ -1,7 +1,67 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mergeProxyAgent = mergeProxyAgent;
|
|
3
4
|
exports.buildAxiosProxyConfig = buildAxiosProxyConfig;
|
|
4
5
|
const https_proxy_agent_1 = require("https-proxy-agent");
|
|
6
|
+
const logger_1 = require("./logger");
|
|
7
|
+
/**
|
|
8
|
+
* 合并订阅级代理配置 (argProxy) 与全局代理配置 (configProxy)。
|
|
9
|
+
*
|
|
10
|
+
* 优先级:
|
|
11
|
+
* 1. argProxy.enabled === false → 显式禁用
|
|
12
|
+
* 2. argProxy.enabled === true 且有 host → 用订阅配置
|
|
13
|
+
* 3. argProxy 缺失 / 未设置 enabled → 回落全局配置
|
|
14
|
+
* 4. argProxy.enabled === true 但缺 host → 用全局补全
|
|
15
|
+
* 5. 其余 → 禁用
|
|
16
|
+
*
|
|
17
|
+
* 返回值与 `config.net.proxyAgent` 同构,可直接喂给 `buildAxiosProxyConfig`。
|
|
18
|
+
*
|
|
19
|
+
* 本函数原位于 `core/feeder-arg.ts`,提升到此处以统一代理 helper 归属,
|
|
20
|
+
* 供 fetcher / feeder 等模块复用,避免重复实现合并逻辑。
|
|
21
|
+
*/
|
|
22
|
+
function mergeProxyAgent(argProxy, configProxy, config) {
|
|
23
|
+
(0, logger_1.debug)(config, `合并代理配置 - argProxy: ${JSON.stringify(argProxy)}, configProxy.enabled: ${configProxy?.enabled}`, 'proxy merge debug', 'details');
|
|
24
|
+
if (argProxy?.enabled === false) {
|
|
25
|
+
(0, logger_1.debug)(config, '订阅明确禁用代理', 'proxy merge', 'details');
|
|
26
|
+
return { enabled: false };
|
|
27
|
+
}
|
|
28
|
+
if (argProxy?.enabled === true && argProxy?.host) {
|
|
29
|
+
(0, logger_1.debug)(config, '使用订阅的代理配置', 'proxy merge', 'details');
|
|
30
|
+
return argProxy;
|
|
31
|
+
}
|
|
32
|
+
const shouldUseConfigProxy = !argProxy || Object.keys(argProxy || {}).length === 0 || argProxy?.enabled === undefined || argProxy?.enabled === null;
|
|
33
|
+
if (shouldUseConfigProxy) {
|
|
34
|
+
if (configProxy?.enabled) {
|
|
35
|
+
const result = {
|
|
36
|
+
enabled: true,
|
|
37
|
+
protocol: configProxy.protocol,
|
|
38
|
+
host: configProxy.host,
|
|
39
|
+
port: configProxy.port,
|
|
40
|
+
auth: configProxy.auth?.enabled ? configProxy.auth : undefined,
|
|
41
|
+
};
|
|
42
|
+
(0, logger_1.debug)(config, `使用全局代理: ${result.protocol}://${result.host}:${result.port}`, 'proxy merge', 'info');
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
(0, logger_1.debug)(config, '全局代理未启用', 'proxy merge', 'details');
|
|
46
|
+
}
|
|
47
|
+
if (argProxy?.enabled === true && !argProxy?.host) {
|
|
48
|
+
const result = {
|
|
49
|
+
...configProxy,
|
|
50
|
+
...argProxy,
|
|
51
|
+
auth: configProxy?.auth?.enabled ? configProxy.auth : undefined,
|
|
52
|
+
};
|
|
53
|
+
(0, logger_1.debug)(config, '订阅代理配置不完整,补充全局配置', 'proxy merge', 'details');
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
(0, logger_1.debug)(config, '代理未配置,使用默认(禁用)', 'proxy merge', 'details');
|
|
57
|
+
return { enabled: false };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 构造 axios 的代理配置(httpsAgent + 关闭 axios 原生 proxy)。
|
|
61
|
+
*
|
|
62
|
+
* 接受完整的 Config(读取 config.net.proxyAgent)。对于需要按订阅合并代理的场景,
|
|
63
|
+
* 先用 `mergeProxyAgent` 得到统一片段,再包成 `{ net: { proxyAgent } }` 传入。
|
|
64
|
+
*/
|
|
5
65
|
function buildAxiosProxyConfig(config) {
|
|
6
66
|
if (!config.net?.proxyAgent?.enabled) {
|
|
7
67
|
return {};
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anyul/koishi-plugin-rss",
|
|
3
3
|
"description": "Koishi RSS订阅器,支持多种RSS源、图片渲染、AI摘要等高级功能",
|
|
4
|
-
"version": "5.3.
|
|
4
|
+
"version": "5.3.10",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
|
+
"packageManager": "pnpm@10.12.1",
|
|
7
8
|
"author": "Anyuluo <anyul@email.com>",
|
|
8
9
|
"contributors": [],
|
|
9
10
|
"repository": {
|
|
@@ -43,16 +44,28 @@
|
|
|
43
44
|
"x2js": "^3.4.4"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
47
|
+
"@eslint/js": "^8.57.1",
|
|
46
48
|
"@jest/globals": "^30.2.0",
|
|
47
49
|
"@types/jest": "^30.0.0",
|
|
50
|
+
"@types/node": "^26.0.1",
|
|
51
|
+
"eslint": "^9.39.4",
|
|
52
|
+
"globals": "^15.15.0",
|
|
48
53
|
"jest": "^30.2.0",
|
|
49
54
|
"ts-jest": "^29.4.6",
|
|
50
|
-
"typescript": "^5.9.3"
|
|
55
|
+
"typescript": "^5.9.3",
|
|
56
|
+
"typescript-eslint": "^8.62.0"
|
|
51
57
|
},
|
|
52
58
|
"scripts": {
|
|
53
59
|
"test": "jest",
|
|
54
60
|
"test:watch": "jest --watch",
|
|
55
61
|
"test:coverage": "jest --coverage",
|
|
56
|
-
"
|
|
62
|
+
"test:ci": "jest --ci --reporters=default",
|
|
63
|
+
"build": "tsc",
|
|
64
|
+
"typecheck": "tsc --noEmit",
|
|
65
|
+
"lint": "eslint src tests",
|
|
66
|
+
"lint:fix": "eslint src tests --fix"
|
|
67
|
+
},
|
|
68
|
+
"engines": {
|
|
69
|
+
"node": ">=20"
|
|
57
70
|
}
|
|
58
71
|
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 命令错误处理中间件
|
|
3
|
-
* 提供统一的错误处理和用户友好的错误消息
|
|
4
|
-
*/
|
|
5
|
-
import { Context } from 'koishi';
|
|
6
|
-
import { Config } from '../types';
|
|
7
|
-
/**
|
|
8
|
-
* 命令执行结果
|
|
9
|
-
*/
|
|
10
|
-
export interface CommandResult {
|
|
11
|
-
success: boolean;
|
|
12
|
-
message: string;
|
|
13
|
-
error?: any;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* 命令错误类型
|
|
17
|
-
*/
|
|
18
|
-
export declare enum CommandErrorType {
|
|
19
|
-
PERMISSION_DENIED = "PERMISSION_DENIED",
|
|
20
|
-
INVALID_ARGUMENT = "INVALID_ARGUMENT",
|
|
21
|
-
NOT_FOUND = "NOT_FOUND",
|
|
22
|
-
ALREADY_EXISTS = "ALREADY_EXISTS",
|
|
23
|
-
NETWORK_ERROR = "NETWORK_ERROR",
|
|
24
|
-
INTERNAL_ERROR = "INTERNAL_ERROR"
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* 命令错误类
|
|
28
|
-
*/
|
|
29
|
-
export declare class CommandError extends Error {
|
|
30
|
-
type: CommandErrorType;
|
|
31
|
-
details?: any;
|
|
32
|
-
constructor(type: CommandErrorType, message: string, details?: any);
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* 包装命令执行,提供统一的错误处理
|
|
36
|
-
*/
|
|
37
|
-
export declare function executeCommand(ctx: Context, config: Config, operationName: string, handler: () => Promise<string>): Promise<string>;
|
|
38
|
-
/**
|
|
39
|
-
* 创建权限检查错误
|
|
40
|
-
*/
|
|
41
|
-
export declare function permissionDenied(customMessage?: string): CommandError;
|
|
42
|
-
/**
|
|
43
|
-
* 创建参数错误
|
|
44
|
-
*/
|
|
45
|
-
export declare function invalidArgument(message: string): CommandError;
|
|
46
|
-
/**
|
|
47
|
-
* 创建未找到错误
|
|
48
|
-
*/
|
|
49
|
-
export declare function notFound(resource: string): CommandError;
|
|
50
|
-
/**
|
|
51
|
-
* 创建已存在错误
|
|
52
|
-
*/
|
|
53
|
-
export declare function alreadyExists(resource: string): CommandError;
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* 命令错误处理中间件
|
|
4
|
-
* 提供统一的错误处理和用户友好的错误消息
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.CommandError = exports.CommandErrorType = void 0;
|
|
8
|
-
exports.executeCommand = executeCommand;
|
|
9
|
-
exports.permissionDenied = permissionDenied;
|
|
10
|
-
exports.invalidArgument = invalidArgument;
|
|
11
|
-
exports.notFound = notFound;
|
|
12
|
-
exports.alreadyExists = alreadyExists;
|
|
13
|
-
const error_handler_1 = require("../utils/error-handler");
|
|
14
|
-
const error_tracker_1 = require("../utils/error-tracker");
|
|
15
|
-
const logger_1 = require("../utils/logger");
|
|
16
|
-
/**
|
|
17
|
-
* 命令错误类型
|
|
18
|
-
*/
|
|
19
|
-
var CommandErrorType;
|
|
20
|
-
(function (CommandErrorType) {
|
|
21
|
-
CommandErrorType["PERMISSION_DENIED"] = "PERMISSION_DENIED";
|
|
22
|
-
CommandErrorType["INVALID_ARGUMENT"] = "INVALID_ARGUMENT";
|
|
23
|
-
CommandErrorType["NOT_FOUND"] = "NOT_FOUND";
|
|
24
|
-
CommandErrorType["ALREADY_EXISTS"] = "ALREADY_EXISTS";
|
|
25
|
-
CommandErrorType["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
26
|
-
CommandErrorType["INTERNAL_ERROR"] = "INTERNAL_ERROR";
|
|
27
|
-
})(CommandErrorType || (exports.CommandErrorType = CommandErrorType = {}));
|
|
28
|
-
/**
|
|
29
|
-
* 命令错误类
|
|
30
|
-
*/
|
|
31
|
-
class CommandError extends Error {
|
|
32
|
-
type;
|
|
33
|
-
details;
|
|
34
|
-
constructor(type, message, details) {
|
|
35
|
-
super(message);
|
|
36
|
-
this.type = type;
|
|
37
|
-
this.details = details;
|
|
38
|
-
this.name = 'CommandError';
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
exports.CommandError = CommandError;
|
|
42
|
-
/**
|
|
43
|
-
* 包装命令执行,提供统一的错误处理
|
|
44
|
-
*/
|
|
45
|
-
async function executeCommand(ctx, config, operationName, handler) {
|
|
46
|
-
try {
|
|
47
|
-
return await handler();
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
// 如果是 CommandError,使用自定义消息
|
|
51
|
-
if (error instanceof CommandError) {
|
|
52
|
-
logError(config, operationName, error);
|
|
53
|
-
return formatCommandError(error);
|
|
54
|
-
}
|
|
55
|
-
// 其他错误使用友好错误消息
|
|
56
|
-
logError(config, operationName, error);
|
|
57
|
-
const friendlyMessage = (0, error_handler_1.getFriendlyErrorMessage)(error, operationName);
|
|
58
|
-
return `${operationName}失败: ${friendlyMessage}`;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* 记录命令错误
|
|
63
|
-
*/
|
|
64
|
-
function logError(config, operation, error) {
|
|
65
|
-
const normalizedError = (0, error_handler_1.normalizeError)(error, `${operation} failed`);
|
|
66
|
-
const context = {
|
|
67
|
-
command: operation,
|
|
68
|
-
};
|
|
69
|
-
if (error instanceof CommandError) {
|
|
70
|
-
context.commandErrorType = error.type;
|
|
71
|
-
}
|
|
72
|
-
if (normalizedError && typeof normalizedError.code === 'string') {
|
|
73
|
-
context.errorCode = normalizedError.code;
|
|
74
|
-
}
|
|
75
|
-
(0, logger_1.debugError)(config, normalizedError, `${operation} error`, context);
|
|
76
|
-
(0, error_tracker_1.trackError)(normalizedError, context);
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* 格式化命令错误消息
|
|
80
|
-
*/
|
|
81
|
-
function formatCommandError(error) {
|
|
82
|
-
switch (error.type) {
|
|
83
|
-
case CommandErrorType.PERMISSION_DENIED:
|
|
84
|
-
return error.message;
|
|
85
|
-
case CommandErrorType.INVALID_ARGUMENT:
|
|
86
|
-
return `参数错误: ${error.message}`;
|
|
87
|
-
case CommandErrorType.NOT_FOUND:
|
|
88
|
-
return `未找到: ${error.message}`;
|
|
89
|
-
case CommandErrorType.ALREADY_EXISTS:
|
|
90
|
-
return `已存在: ${error.message}`;
|
|
91
|
-
case CommandErrorType.NETWORK_ERROR:
|
|
92
|
-
return `网络错误: ${error.message}`;
|
|
93
|
-
default:
|
|
94
|
-
return error.message || '操作失败,请稍后重试';
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* 创建权限检查错误
|
|
99
|
-
*/
|
|
100
|
-
function permissionDenied(customMessage) {
|
|
101
|
-
return new CommandError(CommandErrorType.PERMISSION_DENIED, customMessage || '权限不足');
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* 创建参数错误
|
|
105
|
-
*/
|
|
106
|
-
function invalidArgument(message) {
|
|
107
|
-
return new CommandError(CommandErrorType.INVALID_ARGUMENT, message);
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* 创建未找到错误
|
|
111
|
-
*/
|
|
112
|
-
function notFound(resource) {
|
|
113
|
-
return new CommandError(CommandErrorType.NOT_FOUND, resource);
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* 创建已存在错误
|
|
117
|
-
*/
|
|
118
|
-
function alreadyExists(resource) {
|
|
119
|
-
return new CommandError(CommandErrorType.ALREADY_EXISTS, resource);
|
|
120
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RSS 订阅命令
|
|
3
|
-
* 处理 RSS 订阅的添加、删除、列表、关注等操作
|
|
4
|
-
*/
|
|
5
|
-
import { Context } from 'koishi';
|
|
6
|
-
import { Config } from '../types';
|
|
7
|
-
import { RssItemProcessor } from '../core/item-processor';
|
|
8
|
-
/**
|
|
9
|
-
* 注册 RSS 订阅命令
|
|
10
|
-
*/
|
|
11
|
-
export declare function registerRssSubscribeCommand(ctx: Context, config: Config, processor: RssItemProcessor, getRssDataLocal: (url: string, arg: any) => Promise<any[]>, parseRssItem: (item: any, arg: any, authorId: string | number) => Promise<string>, parseQuickUrlLocal: (url: string) => string, parsePubDateLocal: (pubDate: any) => Date): void;
|