@anyul/koishi-plugin-rss 5.2.1 → 5.2.3
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/error-handler.js +2 -5
- package/lib/commands/index.d.ts +17 -1
- package/lib/commands/index.js +388 -2
- package/lib/commands/subscription-edit.d.ts +7 -0
- package/lib/commands/subscription-edit.js +177 -0
- package/lib/commands/subscription-management.d.ts +12 -0
- package/lib/commands/subscription-management.js +176 -0
- package/lib/commands/utils.d.ts +13 -1
- package/lib/commands/utils.js +43 -2
- package/lib/config.js +19 -0
- package/lib/core/ai.d.ts +16 -2
- package/lib/core/ai.js +73 -6
- package/lib/core/feeder.d.ts +1 -1
- package/lib/core/feeder.js +238 -125
- package/lib/core/item-processor.d.ts +5 -0
- package/lib/core/item-processor.js +66 -136
- package/lib/core/notification-queue.d.ts +2 -0
- package/lib/core/notification-queue.js +80 -33
- package/lib/core/parser.js +12 -0
- package/lib/core/renderer.d.ts +15 -0
- package/lib/core/renderer.js +105 -16
- package/lib/index.js +28 -784
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/types.d.ts +24 -0
- package/lib/utils/common.js +52 -3
- package/lib/utils/error-handler.d.ts +8 -0
- package/lib/utils/error-handler.js +27 -0
- package/lib/utils/error-tracker.js +24 -8
- package/lib/utils/fetcher.js +68 -9
- package/lib/utils/logger.d.ts +4 -2
- package/lib/utils/logger.js +144 -6
- package/lib/utils/media.js +3 -6
- package/lib/utils/sanitizer.d.ts +58 -0
- package/lib/utils/sanitizer.js +227 -0
- package/lib/utils/security.d.ts +75 -0
- package/lib/utils/security.js +312 -0
- package/lib/utils/structured-logger.js +3 -20
- package/package.json +2 -1
package/lib/core/renderer.js
CHANGED
|
@@ -33,12 +33,31 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.calculateContentHeight = calculateContentHeight;
|
|
36
37
|
exports.preprocessHtmlImages = preprocessHtmlImages;
|
|
37
38
|
exports.renderHtml2Image = renderHtml2Image;
|
|
38
39
|
const koishi_1 = require("koishi");
|
|
39
40
|
const cheerio = __importStar(require("cheerio"));
|
|
40
41
|
const logger_1 = require("../utils/logger");
|
|
41
42
|
const media_1 = require("../utils/media");
|
|
43
|
+
function calculateContentHeight(metrics) {
|
|
44
|
+
const intrinsicHeight = Math.ceil(Math.max(metrics.contentRangeHeight, metrics.maxElementBottom, 0)
|
|
45
|
+
+ metrics.paddingTop
|
|
46
|
+
+ metrics.paddingBottom
|
|
47
|
+
+ metrics.marginTop
|
|
48
|
+
+ metrics.marginBottom);
|
|
49
|
+
const domMeasuredHeight = Math.max(metrics.bodyScrollHeight, metrics.bodyOffsetHeight, metrics.documentScrollHeight, 0);
|
|
50
|
+
if (intrinsicHeight > 0) {
|
|
51
|
+
if (domMeasuredHeight > intrinsicHeight && domMeasuredHeight - intrinsicHeight <= 64) {
|
|
52
|
+
return Math.max(domMeasuredHeight, 100);
|
|
53
|
+
}
|
|
54
|
+
return Math.max(intrinsicHeight, 100);
|
|
55
|
+
}
|
|
56
|
+
if (metrics.viewportHeight > 0 && domMeasuredHeight >= metrics.viewportHeight) {
|
|
57
|
+
return 100;
|
|
58
|
+
}
|
|
59
|
+
return Math.max(domMeasuredHeight, 100);
|
|
60
|
+
}
|
|
42
61
|
// 预处理 HTML:下载所有图片并替换为 data URL,避免 Puppeteer 截图时加载外部图片超时
|
|
43
62
|
async function preprocessHtmlImages(ctx, config, $http, htmlContent, arg) {
|
|
44
63
|
const $ = cheerio.load(htmlContent);
|
|
@@ -121,20 +140,22 @@ async function renderHtml2Image(ctx, config, $http, htmlContent, arg) {
|
|
|
121
140
|
let page = await ctx.puppeteer.page();
|
|
122
141
|
try {
|
|
123
142
|
(0, logger_1.debug)(config, htmlContent, 'htmlContent', 'details');
|
|
143
|
+
const initialViewportHeight = 2000;
|
|
124
144
|
// 预处理:下载所有图片并替换为 data URL,避免加载外部图片超时
|
|
125
145
|
htmlContent = await preprocessHtmlImages(ctx, config, $http, htmlContent, arg);
|
|
126
146
|
// 设置 deviceScaleFactor 以控制截图清晰度(必须在 setContent 之前)
|
|
127
147
|
// 保持 viewport 宽度与 bodyWidth 匹配,避免排版错乱
|
|
128
|
-
|
|
129
|
-
const
|
|
148
|
+
// 优先使用 arg 参数,其次使用全局配置
|
|
149
|
+
const bodyWidth = arg?.bodyWidth ?? config.template.bodyWidth ?? 600;
|
|
150
|
+
const bodyPadding = arg?.bodyPadding ?? config.template.bodyPadding ?? 20;
|
|
130
151
|
const viewportWidth = bodyWidth + bodyPadding * 2 + 100; // 预留额外空间
|
|
131
152
|
// 先用较大的初始 viewport 加载页面(高度设大一些确保内容能完整渲染)
|
|
132
153
|
await page.setViewport({
|
|
133
154
|
width: viewportWidth,
|
|
134
|
-
height:
|
|
155
|
+
height: initialViewportHeight,
|
|
135
156
|
deviceScaleFactor: config.template.deviceScaleFactor
|
|
136
157
|
});
|
|
137
|
-
(0, logger_1.debug)(config, `设置截图清晰度: ${config.template.deviceScaleFactor}x, 初始 viewport: ${viewportWidth}
|
|
158
|
+
(0, logger_1.debug)(config, `设置截图清晰度: ${config.template.deviceScaleFactor}x, 初始 viewport: ${viewportWidth}x${initialViewportHeight}`, 'deviceScaleFactor', 'info');
|
|
138
159
|
// 拦截视频请求,避免加载视频导致超时
|
|
139
160
|
await page.setRequestInterception(true);
|
|
140
161
|
page.on('request', (req) => {
|
|
@@ -148,12 +169,76 @@ async function renderHtml2Image(ctx, config, $http, htmlContent, arg) {
|
|
|
148
169
|
});
|
|
149
170
|
// 使用 domcontentloaded 避免等待视频等慢速资源
|
|
150
171
|
await page.setContent(htmlContent, { waitUntil: 'domcontentloaded', timeout: 15000 });
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
172
|
+
// 等待一小段时间让 CSS 和内容完全渲染
|
|
173
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
174
|
+
// 获取实际内容尺寸,根据渲染内容边界动态调整 viewport
|
|
175
|
+
let contentMetrics = {
|
|
176
|
+
bodyScrollHeight: 0,
|
|
177
|
+
bodyOffsetHeight: 0,
|
|
178
|
+
documentScrollHeight: 0,
|
|
179
|
+
contentRangeHeight: 0,
|
|
180
|
+
maxElementBottom: 0,
|
|
181
|
+
paddingTop: 0,
|
|
182
|
+
paddingBottom: 0,
|
|
183
|
+
marginTop: 0,
|
|
184
|
+
marginBottom: 0,
|
|
185
|
+
marginLeft: 0,
|
|
186
|
+
bodyWidth: 0,
|
|
187
|
+
viewportHeight: initialViewportHeight,
|
|
188
|
+
};
|
|
189
|
+
try {
|
|
190
|
+
contentMetrics = await page.evaluate(() => {
|
|
191
|
+
const body = document.body;
|
|
192
|
+
const documentElement = document.documentElement;
|
|
193
|
+
if (!body || !documentElement) {
|
|
194
|
+
return {
|
|
195
|
+
bodyScrollHeight: 0,
|
|
196
|
+
bodyOffsetHeight: 0,
|
|
197
|
+
documentScrollHeight: 0,
|
|
198
|
+
contentRangeHeight: 0,
|
|
199
|
+
maxElementBottom: 0,
|
|
200
|
+
paddingTop: 0,
|
|
201
|
+
paddingBottom: 0,
|
|
202
|
+
marginTop: 0,
|
|
203
|
+
marginBottom: 0,
|
|
204
|
+
marginLeft: 0,
|
|
205
|
+
bodyWidth: 0,
|
|
206
|
+
viewportHeight: window.innerHeight || 0,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const computedStyle = document.defaultView?.getComputedStyle(body);
|
|
210
|
+
const bodyRect = body.getBoundingClientRect();
|
|
211
|
+
const range = document.createRange();
|
|
212
|
+
range.selectNodeContents(body);
|
|
213
|
+
const rangeRect = range.getBoundingClientRect();
|
|
214
|
+
const maxElementBottom = Array.from(body.querySelectorAll('*')).reduce((max, element) => {
|
|
215
|
+
const rect = element.getBoundingClientRect();
|
|
216
|
+
return Math.max(max, rect.bottom - bodyRect.top);
|
|
217
|
+
}, 0);
|
|
218
|
+
return {
|
|
219
|
+
bodyScrollHeight: body.scrollHeight || 0,
|
|
220
|
+
bodyOffsetHeight: body.offsetHeight || 0,
|
|
221
|
+
documentScrollHeight: documentElement.scrollHeight || 0,
|
|
222
|
+
contentRangeHeight: Math.ceil(rangeRect.height || 0),
|
|
223
|
+
maxElementBottom: Math.ceil(maxElementBottom),
|
|
224
|
+
paddingTop: parseFloat(computedStyle?.paddingTop || '0') || 0,
|
|
225
|
+
paddingBottom: parseFloat(computedStyle?.paddingBottom || '0') || 0,
|
|
226
|
+
marginTop: parseFloat(computedStyle?.marginTop || '0') || 0,
|
|
227
|
+
marginBottom: parseFloat(computedStyle?.marginBottom || '0') || 0,
|
|
228
|
+
marginLeft: parseFloat(computedStyle?.marginLeft || '0') || 0,
|
|
229
|
+
bodyWidth: Math.ceil(body.offsetWidth || bodyRect.width || 0),
|
|
230
|
+
viewportHeight: window.innerHeight || 0,
|
|
231
|
+
};
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
catch (e) {
|
|
235
|
+
(0, logger_1.debug)(config, `获取内容尺寸失败: ${e}`, 'height error', 'error');
|
|
236
|
+
}
|
|
237
|
+
const actualHeight = calculateContentHeight(contentMetrics);
|
|
156
238
|
const viewportHeight = Math.max(actualHeight, 100);
|
|
239
|
+
const clipWidth = Math.max(contentMetrics.bodyWidth || bodyWidth, 1);
|
|
240
|
+
const clipX = Math.max(Math.floor(contentMetrics.marginLeft), 0);
|
|
241
|
+
const clipY = Math.max(Math.floor(contentMetrics.marginTop), 0);
|
|
157
242
|
await page.setViewport({
|
|
158
243
|
width: viewportWidth,
|
|
159
244
|
height: viewportHeight,
|
|
@@ -179,12 +264,15 @@ async function renderHtml2Image(ctx, config, $http, htmlContent, arg) {
|
|
|
179
264
|
}
|
|
180
265
|
return koishi_1.h.image(image, 'image/png');
|
|
181
266
|
}
|
|
182
|
-
let
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
267
|
+
let height = actualHeight;
|
|
268
|
+
let width = clipWidth;
|
|
269
|
+
let x = clipX;
|
|
270
|
+
let y = clipY;
|
|
271
|
+
// 保守处理:如果高度异常,使用更小的值
|
|
272
|
+
if (height > 5000) {
|
|
273
|
+
(0, logger_1.debug)(config, `检测到异常高度 ${height},进行裁剪`, 'height warn', 'info');
|
|
274
|
+
height = Math.min(height, 2000);
|
|
275
|
+
}
|
|
188
276
|
let size = 10000;
|
|
189
277
|
(0, logger_1.debug)(config, [height, width, x, y], 'pptr img size', 'details');
|
|
190
278
|
const split = Math.ceil(height / size);
|
|
@@ -208,7 +296,8 @@ async function renderHtml2Image(ctx, config, $http, htmlContent, arg) {
|
|
|
208
296
|
}
|
|
209
297
|
(0, logger_1.debug)(config, { height, width, split }, 'split img', 'details');
|
|
210
298
|
const reduceY = (index) => Math.floor(height / split * index);
|
|
211
|
-
|
|
299
|
+
// 最后一份使用完整高度减去前面所有份的高度,确保覆盖全部内容
|
|
300
|
+
const reduceHeight = (index) => index === split - 1 ? height - reduceY(index) : Math.floor(height / split);
|
|
212
301
|
let imgData = await Promise.all(Array.from({ length: split }, async (v, i) => await page.screenshot({
|
|
213
302
|
type: "png",
|
|
214
303
|
clip: {
|