@anyul/koishi-plugin-rss 5.3.4 → 5.3.11
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 -1
- 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 -10
- package/lib/commands/web-monitor.d.ts +2 -0
- package/lib/commands/web-monitor.js +2 -2
- package/lib/config.js +4 -2
- package/lib/core/feeder.js +53 -2
- 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 -0
- 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/index.js +24 -3
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/types.d.ts +51 -23
- package/lib/utils/common.js +5 -5
- package/lib/utils/fetcher.js +1 -1
- package/lib/utils/legacy-config.js +9 -3
- 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/package.json +16 -3
package/lib/utils/common.js
CHANGED
|
@@ -100,7 +100,7 @@ const parseContent = (content, attr = undefined) => {
|
|
|
100
100
|
else if (Object.prototype.toString.call(content) === '[object Object]') {
|
|
101
101
|
return Object.values(content).reduce((t, v) => {
|
|
102
102
|
if (v && (typeof v == 'string' || v?.join)) {
|
|
103
|
-
|
|
103
|
+
const text = v?.join("") || v;
|
|
104
104
|
return (typeof text === 'string' && text.length > t.length) ? text : t;
|
|
105
105
|
}
|
|
106
106
|
else {
|
|
@@ -143,13 +143,13 @@ const parseTemplateContent = (template, item) => {
|
|
|
143
143
|
exports.parseTemplateContent = parseTemplateContent;
|
|
144
144
|
// 快速URL解析函数
|
|
145
145
|
const parseQuickUrl = (url, rssHubUrl, quickList) => {
|
|
146
|
-
|
|
146
|
+
const correntQuickObj = quickList.find(i => new RegExp(`^${i.prefix}:`).test(url));
|
|
147
147
|
if (!correntQuickObj)
|
|
148
148
|
return url;
|
|
149
|
-
|
|
149
|
+
const routeMatch = url.match(new RegExp(`(?<=^${correntQuickObj.prefix}:).*`));
|
|
150
150
|
if (!routeMatch)
|
|
151
151
|
return url;
|
|
152
|
-
|
|
152
|
+
const route = routeMatch[0];
|
|
153
153
|
// 复用模块级 parseTemplateContent,避免内嵌重复实现
|
|
154
154
|
const rUrl = (0, exports.parseTemplateContent)(correntQuickObj.replace, { rsshub: rssHubUrl, route });
|
|
155
155
|
return rUrl;
|
|
@@ -163,7 +163,7 @@ const cleanContent = (htmlContent) => {
|
|
|
163
163
|
$('style').remove();
|
|
164
164
|
$('img').remove();
|
|
165
165
|
$('video').remove();
|
|
166
|
-
|
|
166
|
+
const plainText = $.text().replace(/\s+/g, ' ').trim();
|
|
167
167
|
return plainText;
|
|
168
168
|
};
|
|
169
169
|
exports.cleanContent = cleanContent;
|
package/lib/utils/fetcher.js
CHANGED
|
@@ -99,7 +99,7 @@ const createHttpFunction = (ctx, config, requestManager) => {
|
|
|
99
99
|
const makeRequest = async () => {
|
|
100
100
|
// 基础配置
|
|
101
101
|
requestDebug(`[DEBUG_PROXY] fetcher makeRequest arg.proxyAgent: ${JSON.stringify(arg?.proxyAgent)}`, 'request', 'details');
|
|
102
|
-
|
|
102
|
+
const configObj = {
|
|
103
103
|
timeout: requestTimeout,
|
|
104
104
|
headers: {
|
|
105
105
|
'User-Agent': config.net.userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
@@ -9,8 +9,12 @@ exports.getNextUpdateTime = getNextUpdateTime;
|
|
|
9
9
|
exports.setNextUpdateTime = setNextUpdateTime;
|
|
10
10
|
function normalizeBasicConfig(basic) {
|
|
11
11
|
const normalized = { ...(basic || {}) };
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
// 读取归一:typo(旧拼写)优先。理由——schema 会为声明的规范名注入默认值
|
|
13
|
+
// (如 mergeVideo:false),若用 `correct ?? typo` 会被默认值短路,吞掉老用户
|
|
14
|
+
// 真实意图(margeVideo:true)。typo 存在则必来自历史用户数据,应优先采纳。
|
|
15
|
+
// 写入仍双写两个键(保持运行时兼容,下游直接读 margeVideo 仍可拿到值)。
|
|
16
|
+
const mergeVideo = normalized.margeVideo ?? normalized.mergeVideo;
|
|
17
|
+
const resendUpdatedContent = normalized.resendUpdataContent ?? normalized.resendUpdatedContent;
|
|
14
18
|
if (mergeVideo !== undefined) {
|
|
15
19
|
normalized.mergeVideo = mergeVideo;
|
|
16
20
|
normalized.margeVideo = mergeVideo;
|
|
@@ -47,10 +51,12 @@ function getNextUpdateTime(arg) {
|
|
|
47
51
|
}
|
|
48
52
|
function setNextUpdateTime(target, nextUpdateTime) {
|
|
49
53
|
if (nextUpdateTime === undefined) {
|
|
54
|
+
// 清理时双删:删规范名的同时顺手清掉残留的旧拼写名(老数据可能仍有)
|
|
50
55
|
delete target.nextUpdateTime;
|
|
51
56
|
delete target.nextUpdataTime;
|
|
52
57
|
return;
|
|
53
58
|
}
|
|
59
|
+
// 只写规范名:不再回写 nextUpdataTime,让旧拼写从数据中自然淘汰。
|
|
60
|
+
// 读取侧 getNextUpdateTime 已用 `nextUpdateTime ?? nextUpdataTime` 兜底老数据。
|
|
54
61
|
target.nextUpdateTime = nextUpdateTime;
|
|
55
|
-
target.nextUpdataTime = nextUpdateTime;
|
|
56
62
|
}
|
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/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.11",
|
|
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
|
}
|