@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.
Files changed (38) hide show
  1. package/lib/commands/error-handler.js +2 -5
  2. package/lib/commands/index.d.ts +17 -1
  3. package/lib/commands/index.js +388 -2
  4. package/lib/commands/subscription-edit.d.ts +7 -0
  5. package/lib/commands/subscription-edit.js +177 -0
  6. package/lib/commands/subscription-management.d.ts +12 -0
  7. package/lib/commands/subscription-management.js +176 -0
  8. package/lib/commands/utils.d.ts +13 -1
  9. package/lib/commands/utils.js +43 -2
  10. package/lib/config.js +19 -0
  11. package/lib/core/ai.d.ts +16 -2
  12. package/lib/core/ai.js +73 -6
  13. package/lib/core/feeder.d.ts +1 -1
  14. package/lib/core/feeder.js +238 -125
  15. package/lib/core/item-processor.d.ts +5 -0
  16. package/lib/core/item-processor.js +66 -136
  17. package/lib/core/notification-queue.d.ts +2 -0
  18. package/lib/core/notification-queue.js +80 -33
  19. package/lib/core/parser.js +12 -0
  20. package/lib/core/renderer.d.ts +15 -0
  21. package/lib/core/renderer.js +105 -16
  22. package/lib/index.js +28 -784
  23. package/lib/tsconfig.tsbuildinfo +1 -1
  24. package/lib/types.d.ts +24 -0
  25. package/lib/utils/common.js +52 -3
  26. package/lib/utils/error-handler.d.ts +8 -0
  27. package/lib/utils/error-handler.js +27 -0
  28. package/lib/utils/error-tracker.js +24 -8
  29. package/lib/utils/fetcher.js +68 -9
  30. package/lib/utils/logger.d.ts +4 -2
  31. package/lib/utils/logger.js +144 -6
  32. package/lib/utils/media.js +3 -6
  33. package/lib/utils/sanitizer.d.ts +58 -0
  34. package/lib/utils/sanitizer.js +227 -0
  35. package/lib/utils/security.d.ts +75 -0
  36. package/lib/utils/security.js +312 -0
  37. package/lib/utils/structured-logger.js +3 -20
  38. package/package.json +2 -1
@@ -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
- const bodyWidth = config.template.bodyWidth || 600;
129
- const bodyPadding = config.template.bodyPadding || 20;
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: 10000,
155
+ height: initialViewportHeight,
135
156
  deviceScaleFactor: config.template.deviceScaleFactor
136
157
  });
137
- (0, logger_1.debug)(config, `设置截图清晰度: ${config.template.deviceScaleFactor}x, 初始 viewport: ${viewportWidth}x10000`, 'deviceScaleFactor', 'info');
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
- // 获取实际内容高度,根据内容高度动态调整 viewport
152
- const actualHeight = await page.evaluate(() => {
153
- return Math.max(document.body.offsetHeight, document.documentElement.scrollHeight, document.body.scrollHeight);
154
- });
155
- // 设置最小高度为 100,避免空内容时截图失败
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 [height, width, x, y] = await page.evaluate(() => [
183
- document.body.offsetHeight,
184
- document.body.offsetWidth,
185
- parseInt(document.defaultView.getComputedStyle(document.body).marginLeft) || 0,
186
- parseInt(document.defaultView.getComputedStyle(document.body).marginTop) || 0
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
- const reduceHeight = (index) => Math.floor(height / split);
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: {