@anyul/koishi-plugin-rss 5.2.3 → 5.2.41
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/README.md +92 -37
- package/lib/commands/error-handler.js +13 -1
- package/lib/commands/index.d.ts +3 -0
- package/lib/commands/index.js +7 -1
- package/lib/commands/runtime.d.ts +17 -0
- package/lib/commands/runtime.js +27 -0
- package/lib/commands/subscription-create.d.ts +23 -0
- package/lib/commands/subscription-create.js +145 -0
- package/lib/commands/web-monitor.d.ts +15 -0
- package/lib/commands/web-monitor.js +222 -0
- package/lib/config.js +7 -1
- package/lib/constants.d.ts +1 -1
- package/lib/constants.js +46 -83
- package/lib/core/ai-cache.d.ts +27 -0
- package/lib/core/ai-cache.js +169 -0
- package/lib/core/ai-client.d.ts +12 -0
- package/lib/core/ai-client.js +65 -0
- package/lib/core/ai-selector.d.ts +2 -0
- package/lib/core/ai-selector.js +80 -0
- package/lib/core/ai-summary.d.ts +10 -0
- package/lib/core/ai-summary.js +73 -0
- package/lib/core/ai-utils.d.ts +10 -0
- package/lib/core/ai-utils.js +104 -0
- package/lib/core/ai.d.ts +3 -91
- package/lib/core/ai.js +13 -522
- package/lib/core/feeder-arg.d.ts +17 -0
- package/lib/core/feeder-arg.js +234 -0
- package/lib/core/feeder-runtime.d.ts +96 -0
- package/lib/core/feeder-runtime.js +233 -0
- package/lib/core/feeder.d.ts +3 -5
- package/lib/core/feeder.js +61 -358
- package/lib/core/item-processor-runtime.d.ts +46 -0
- package/lib/core/item-processor-runtime.js +215 -0
- package/lib/core/item-processor-template.d.ts +16 -0
- package/lib/core/item-processor-template.js +158 -0
- package/lib/core/item-processor.d.ts +1 -15
- package/lib/core/item-processor.js +44 -319
- package/lib/core/notification-queue-retry.d.ts +25 -0
- package/lib/core/notification-queue-retry.js +78 -0
- package/lib/core/notification-queue-sender.d.ts +20 -0
- package/lib/core/notification-queue-sender.js +118 -0
- package/lib/core/notification-queue-store.d.ts +19 -0
- package/lib/core/notification-queue-store.js +137 -0
- package/lib/core/notification-queue-types.d.ts +49 -0
- package/lib/core/notification-queue-types.js +2 -0
- package/lib/core/notification-queue.d.ts +11 -72
- package/lib/core/notification-queue.js +81 -258
- package/lib/core/search-format.d.ts +3 -0
- package/lib/core/search-format.js +36 -0
- package/lib/core/search-providers.d.ts +13 -0
- package/lib/core/search-providers.js +175 -0
- package/lib/core/search-rotation.d.ts +4 -0
- package/lib/core/search-rotation.js +55 -0
- package/lib/core/search-service.d.ts +3 -0
- package/lib/core/search-service.js +100 -0
- package/lib/core/search-types.d.ts +39 -0
- package/lib/core/search-types.js +2 -0
- package/lib/core/search.d.ts +4 -101
- package/lib/core/search.js +10 -508
- package/lib/index.js +27 -381
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/types.d.ts +27 -6
- package/lib/utils/legacy-config.d.ts +12 -0
- package/lib/utils/legacy-config.js +56 -0
- package/lib/utils/logger.js +50 -29
- package/lib/utils/proxy.d.ts +3 -0
- package/lib/utils/proxy.js +14 -0
- package/lib/utils/structured-logger.d.ts +7 -3
- package/lib/utils/structured-logger.js +26 -19
- package/package.json +1 -1
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.normalizeText = normalizeText;
|
|
37
|
+
exports.buildResolvedImageMap = buildResolvedImageMap;
|
|
38
|
+
exports.renderImageListFromHtml = renderImageListFromHtml;
|
|
39
|
+
exports.renderLoadedHtml = renderLoadedHtml;
|
|
40
|
+
exports.renderTemplatedDescription = renderTemplatedDescription;
|
|
41
|
+
exports.processVideos = processVideos;
|
|
42
|
+
exports.formatVideoList = formatVideoList;
|
|
43
|
+
exports.renderImage = renderImage;
|
|
44
|
+
const cheerio = __importStar(require("cheerio"));
|
|
45
|
+
const koishi_1 = require("koishi");
|
|
46
|
+
const marked_1 = require("marked");
|
|
47
|
+
const logger_1 = require("../utils/logger");
|
|
48
|
+
const media_1 = require("../utils/media");
|
|
49
|
+
const renderer_1 = require("./renderer");
|
|
50
|
+
function isImageRenderEnabled(config) {
|
|
51
|
+
return config.basic?.imageMode === 'base64'
|
|
52
|
+
|| config.basic?.imageMode === 'File'
|
|
53
|
+
|| config.basic?.imageMode === 'assets';
|
|
54
|
+
}
|
|
55
|
+
function collectUniqueImageSources(html) {
|
|
56
|
+
const imageSources = [];
|
|
57
|
+
html('img').each((_, element) => {
|
|
58
|
+
const src = element.attribs?.src;
|
|
59
|
+
if (src)
|
|
60
|
+
imageSources.push(src);
|
|
61
|
+
});
|
|
62
|
+
return [...new Set(imageSources)];
|
|
63
|
+
}
|
|
64
|
+
async function prependAiSummarySection(config, htmlContent, item, options) {
|
|
65
|
+
const aiSummary = normalizeText(item?.aiSummary).trim();
|
|
66
|
+
if (!aiSummary || !isImageRenderEnabled(config))
|
|
67
|
+
return htmlContent;
|
|
68
|
+
const aiSummaryHtml = await (0, marked_1.marked)(aiSummary);
|
|
69
|
+
const contentStyleAttr = options?.contentStyle ? ` style="${options.contentStyle}"` : '';
|
|
70
|
+
const dividerAttr = options?.dividerStyle
|
|
71
|
+
? ` style="${options.dividerStyle}"`
|
|
72
|
+
: ' class="border-t border-slate-100 my-6"';
|
|
73
|
+
return `
|
|
74
|
+
<div class="ai-summary-section mb-6">
|
|
75
|
+
<div class="flex items-start gap-3 mb-3">
|
|
76
|
+
<div class="mt-0.5 w-6 h-6 rounded-md bg-primary/10 flex flex-shrink-0 items-center justify-center">
|
|
77
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-4 h-4 text-primary">
|
|
78
|
+
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
|
|
79
|
+
</svg>
|
|
80
|
+
</div>
|
|
81
|
+
<h3 class="text-sm font-bold text-slate-700">AI 摘要</h3>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="pl-9 prose prose-slate prose-sm max-w-none"${contentStyleAttr}>
|
|
84
|
+
${aiSummaryHtml}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
<div${dividerAttr}></div>
|
|
88
|
+
` + htmlContent;
|
|
89
|
+
}
|
|
90
|
+
async function prepareHtmlForRender(deps, html, arg, useXml = false) {
|
|
91
|
+
if (arg?.proxyAgent?.enabled) {
|
|
92
|
+
await Promise.all(html('img').map(async (_, element) => {
|
|
93
|
+
const src = element.attribs?.src;
|
|
94
|
+
if (!src)
|
|
95
|
+
return;
|
|
96
|
+
element.attribs.src = await (0, media_1.getImageUrl)(deps.ctx, deps.config, deps.$http, src, arg, true);
|
|
97
|
+
}).get());
|
|
98
|
+
}
|
|
99
|
+
html('img').attr('style', 'object-fit:scale-down;max-width:100%;height:auto;');
|
|
100
|
+
return useXml ? html.xml() : html.html();
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 标准化 RSS 字段内容,统一数组 / 空值 / 非字符串输入。
|
|
104
|
+
*/
|
|
105
|
+
function normalizeText(value) {
|
|
106
|
+
if (Array.isArray(value))
|
|
107
|
+
return value.join('');
|
|
108
|
+
if (value === undefined || value === null)
|
|
109
|
+
return '';
|
|
110
|
+
return String(value);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 解析并去重 HTML 中的图片资源,返回原图到最终地址的映射表。
|
|
114
|
+
*/
|
|
115
|
+
async function buildResolvedImageMap(deps, html, arg) {
|
|
116
|
+
const imageSources = collectUniqueImageSources(html);
|
|
117
|
+
return Object.assign({}, ...(await Promise.all(imageSources.map(async (src) => ({
|
|
118
|
+
[src]: await (0, media_1.getImageUrl)(deps.ctx, deps.config, deps.$http, src, arg),
|
|
119
|
+
})))));
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* 将 HTML 中的唯一图片列表解析为消息图片串。
|
|
123
|
+
*/
|
|
124
|
+
async function renderImageListFromHtml(deps, html, arg) {
|
|
125
|
+
const imageSources = collectUniqueImageSources(html);
|
|
126
|
+
const resolvedImages = await Promise.all(imageSources.map(async (src) => await (0, media_1.getImageUrl)(deps.ctx, deps.config, deps.$http, src, arg)));
|
|
127
|
+
return resolvedImages
|
|
128
|
+
.filter(Boolean)
|
|
129
|
+
.map(imageUrl => `<img src="${imageUrl}"/>`)
|
|
130
|
+
.join('');
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* 将已加载的 HTML 内容渲染为最终消息。
|
|
134
|
+
*/
|
|
135
|
+
async function renderLoadedHtml(deps, html, arg, useXml = false) {
|
|
136
|
+
const htmlContent = await prepareHtmlForRender(deps, html, arg, useXml);
|
|
137
|
+
return await renderImage(htmlContent, deps, arg);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* 渲染模板描述正文,并在图片模式下按需注入 AI 摘要。
|
|
141
|
+
*/
|
|
142
|
+
async function renderTemplatedDescription(deps, item, arg, description, options) {
|
|
143
|
+
item.description = description;
|
|
144
|
+
(0, logger_1.debug)(deps.config, item.description, 'description');
|
|
145
|
+
item.description = await prependAiSummarySection(deps.config, item.description, item, {
|
|
146
|
+
contentStyle: options?.contentStyle,
|
|
147
|
+
dividerStyle: options?.dividerStyle,
|
|
148
|
+
});
|
|
149
|
+
const html = cheerio.load(item.description);
|
|
150
|
+
if (options?.logImageMode) {
|
|
151
|
+
(0, logger_1.debug)(deps.config, `当前 imageMode: ${deps.config.basic?.imageMode}`, 'imageMode', 'info');
|
|
152
|
+
}
|
|
153
|
+
return await renderLoadedHtml(deps, html, arg);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* 提取 HTML 中的视频资源,并按配置补齐 poster 图。
|
|
157
|
+
*/
|
|
158
|
+
async function processVideos(deps, html, arg, videoList) {
|
|
159
|
+
await Promise.all(html('video').map(async (_, element) => {
|
|
160
|
+
videoList.push([
|
|
161
|
+
await (0, media_1.getVideoUrl)(deps.ctx, deps.config, deps.$http, element.attribs.src, arg, true, element),
|
|
162
|
+
(element.attribs.poster && deps.config.basic?.usePoster)
|
|
163
|
+
? await (0, media_1.getImageUrl)(deps.ctx, deps.config, deps.$http, element.attribs.poster, arg, true)
|
|
164
|
+
: '',
|
|
165
|
+
]);
|
|
166
|
+
}).get());
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* 将视频列表格式化为最终消息片段。
|
|
170
|
+
*/
|
|
171
|
+
function formatVideoList(videoList) {
|
|
172
|
+
return videoList.filter(([src]) => src).map(([src, poster]) => {
|
|
173
|
+
if (src.startsWith('__VIDEO_LINK__:')) {
|
|
174
|
+
const videoUrl = src.replace('__VIDEO_LINK__:', '');
|
|
175
|
+
return `\n🎬 视频: ${videoUrl}\n`;
|
|
176
|
+
}
|
|
177
|
+
return (0, koishi_1.h)('video', { src, poster });
|
|
178
|
+
}).join('');
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* 统一执行图片渲染,兼容 base64 / File / assets / HTML 回退。
|
|
182
|
+
*/
|
|
183
|
+
async function renderImage(htmlContent, deps, arg) {
|
|
184
|
+
const imageMode = deps.config.basic?.imageMode;
|
|
185
|
+
if (imageMode === 'base64') {
|
|
186
|
+
(0, logger_1.debug)(deps.config, '使用 base64 模式渲染', 'render mode', 'info');
|
|
187
|
+
return (await (0, renderer_1.renderHtml2Image)(deps.ctx, deps.config, deps.$http, htmlContent, arg)).toString();
|
|
188
|
+
}
|
|
189
|
+
if (imageMode === 'File' || imageMode === 'assets') {
|
|
190
|
+
if (!deps.ctx.puppeteer) {
|
|
191
|
+
(0, logger_1.debug)(deps.config, '未安装 puppeteer 插件,跳过图片渲染', 'puppeteer error', 'error');
|
|
192
|
+
return htmlContent;
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
(0, logger_1.debug)(deps.config, `使用 ${imageMode} 模式渲染`, 'render mode', 'info');
|
|
196
|
+
const processedHtml = await (0, renderer_1.preprocessHtmlImages)(deps.ctx, deps.config, deps.$http, htmlContent, arg);
|
|
197
|
+
let msg;
|
|
198
|
+
if ((deps.config.template?.deviceScaleFactor ?? 1) !== 1) {
|
|
199
|
+
msg = (await (0, renderer_1.renderHtml2Image)(deps.ctx, deps.config, deps.$http, processedHtml, arg)).toString();
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
msg = await deps.ctx.puppeteer.render(processedHtml);
|
|
203
|
+
}
|
|
204
|
+
msg = await (0, media_1.puppeteerToFile)(deps.ctx, deps.config, msg);
|
|
205
|
+
(0, logger_1.debug)(deps.config, 'puppeteer 渲染完成', 'render success', 'info');
|
|
206
|
+
return msg;
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
(0, logger_1.debug)(deps.config, `puppeteer render 失败: ${error}`, 'puppeteer error', 'error');
|
|
210
|
+
return htmlContent;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
(0, logger_1.debug)(deps.config, `未知的 imageMode: ${imageMode},回退到 HTML`, 'render warning', 'error');
|
|
214
|
+
return htmlContent;
|
|
215
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as cheerio from 'cheerio';
|
|
2
|
+
import { rssArg } from '../types';
|
|
3
|
+
import { ItemProcessorRuntimeDeps } from './item-processor-runtime';
|
|
4
|
+
interface ProcessItemTemplateParams {
|
|
5
|
+
deps: ItemProcessorRuntimeDeps;
|
|
6
|
+
template: string | undefined;
|
|
7
|
+
item: any;
|
|
8
|
+
arg: rssArg;
|
|
9
|
+
html: cheerio.CheerioAPI;
|
|
10
|
+
aiSummary: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 根据模板类型分发并生成 RSS 条目的最终消息内容。
|
|
14
|
+
*/
|
|
15
|
+
export declare function processItemTemplate(params: ProcessItemTemplateParams): Promise<string>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.processItemTemplate = processItemTemplate;
|
|
37
|
+
const cheerio = __importStar(require("cheerio"));
|
|
38
|
+
const koishi_1 = require("koishi");
|
|
39
|
+
const common_1 = require("../utils/common");
|
|
40
|
+
const logger_1 = require("../utils/logger");
|
|
41
|
+
const security_1 = require("../utils/security");
|
|
42
|
+
const template_1 = require("../utils/template");
|
|
43
|
+
const item_processor_runtime_1 = require("./item-processor-runtime");
|
|
44
|
+
async function processCustomTemplate(deps, item, arg, parseContent) {
|
|
45
|
+
const description = parseContent(deps.config.template?.custom || '', { ...item, arg });
|
|
46
|
+
const renderedDescription = await (0, item_processor_runtime_1.renderTemplatedDescription)(deps, item, arg, description);
|
|
47
|
+
return parseContent(deps.config.template?.customRemark || '', {
|
|
48
|
+
...item,
|
|
49
|
+
arg,
|
|
50
|
+
description: renderedDescription,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
async function processContentTemplate(deps, item, arg, html, parseContent) {
|
|
54
|
+
const resolvedImageMap = await (0, item_processor_runtime_1.buildResolvedImageMap)(deps, html, arg);
|
|
55
|
+
html('img').replaceWith((_, element) => {
|
|
56
|
+
const src = element.attribs?.src;
|
|
57
|
+
return src ? `<p>$img{{${src}}}</p>` : '';
|
|
58
|
+
});
|
|
59
|
+
const contentText = html.text();
|
|
60
|
+
item.description = contentText.replace(/\$img\{\{(.*?)\}\}/g, (_match, src) => {
|
|
61
|
+
const finalUrl = resolvedImageMap[src];
|
|
62
|
+
return finalUrl ? `<img src="${finalUrl}"/>` : '';
|
|
63
|
+
});
|
|
64
|
+
return parseContent(deps.config.template?.content || '', { ...item, arg });
|
|
65
|
+
}
|
|
66
|
+
async function processDefaultTemplate(deps, item, arg, parseContent) {
|
|
67
|
+
const description = parseContent((0, template_1.getDefaultTemplate)(deps.config, arg.bodyWidth, arg.bodyPadding, arg.bodyFontSize || deps.config.template?.bodyFontSize), { ...item, arg });
|
|
68
|
+
return await (0, item_processor_runtime_1.renderTemplatedDescription)(deps, item, arg, description, {
|
|
69
|
+
logImageMode: true,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async function processOnlyDescriptionTemplate(deps, item, arg, parseContent) {
|
|
73
|
+
const description = parseContent((0, template_1.getDescriptionTemplate)(deps.config, arg.bodyWidth, arg.bodyPadding, arg.bodyFontSize || deps.config.template?.bodyFontSize), { ...item, arg });
|
|
74
|
+
return await (0, item_processor_runtime_1.renderTemplatedDescription)(deps, item, arg, description, {
|
|
75
|
+
contentStyle: 'color: #475569; line-height: 1.6;',
|
|
76
|
+
dividerStyle: 'border-top: 1px solid #e2e8f0; margin: 24px 0;',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async function processLinkTemplate(deps, item, arg) {
|
|
80
|
+
const html = cheerio.load(item.description);
|
|
81
|
+
const src = html('a').first().attr('href') || (0, item_processor_runtime_1.normalizeText)(item?.link);
|
|
82
|
+
if (!src) {
|
|
83
|
+
(0, logger_1.debug)(deps.config, 'link 模板未找到可用链接,回退原始内容', 'link src', 'info');
|
|
84
|
+
return (0, item_processor_runtime_1.normalizeText)(item?.description);
|
|
85
|
+
}
|
|
86
|
+
(0, logger_1.debug)(deps.config, src, 'link src', 'info');
|
|
87
|
+
try {
|
|
88
|
+
(0, security_1.validateUrlOrThrow)(src, (0, security_1.getSecurityOptions)(deps.config));
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (error instanceof security_1.SecurityError) {
|
|
92
|
+
(0, logger_1.debug)(deps.config, `链接 URL 安全验证失败: ${error.message}`, 'security', 'error');
|
|
93
|
+
return `链接安全验证失败: ${error.message}`;
|
|
94
|
+
}
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
const html2 = cheerio.load((await deps.$http(src, arg)).data);
|
|
98
|
+
const bodyWidth = arg?.bodyWidth ?? deps.config.template?.bodyWidth ?? 600;
|
|
99
|
+
const bodyPadding = arg?.bodyPadding ?? deps.config.template?.bodyPadding ?? 20;
|
|
100
|
+
html2('body').attr('style', `width:${bodyWidth}px;padding:${bodyPadding}px;`);
|
|
101
|
+
return await (0, item_processor_runtime_1.renderLoadedHtml)(deps, html2, arg, true);
|
|
102
|
+
}
|
|
103
|
+
async function appendVideoMessage(deps, html, arg, msg, options) {
|
|
104
|
+
const videoList = [];
|
|
105
|
+
await (0, item_processor_runtime_1.processVideos)(deps, html, arg, videoList);
|
|
106
|
+
let result = msg + (0, item_processor_runtime_1.formatVideoList)(videoList);
|
|
107
|
+
if (options?.appendPosterImages) {
|
|
108
|
+
result += videoList
|
|
109
|
+
.filter(([src, poster]) => poster && !src.startsWith('__VIDEO_LINK__'))
|
|
110
|
+
.map(([_, poster]) => (0, koishi_1.h)('img', { src: poster }))
|
|
111
|
+
.join('');
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 根据模板类型分发并生成 RSS 条目的最终消息内容。
|
|
117
|
+
*/
|
|
118
|
+
async function processItemTemplate(params) {
|
|
119
|
+
const { deps, template, item, arg, html, aiSummary } = params;
|
|
120
|
+
const parseContent = (templateStr, itemObj) => (0, common_1.parseTemplateContent)(templateStr, { ...itemObj, aiSummary });
|
|
121
|
+
switch (template) {
|
|
122
|
+
case 'custom': {
|
|
123
|
+
const msg = await processCustomTemplate(deps, item, arg, parseContent);
|
|
124
|
+
return await appendVideoMessage(deps, html, arg, msg);
|
|
125
|
+
}
|
|
126
|
+
case 'content': {
|
|
127
|
+
const msg = await processContentTemplate(deps, item, arg, html, parseContent);
|
|
128
|
+
return await appendVideoMessage(deps, html, arg, msg, { appendPosterImages: true });
|
|
129
|
+
}
|
|
130
|
+
case 'only text':
|
|
131
|
+
return html.text();
|
|
132
|
+
case 'only media': {
|
|
133
|
+
const msg = await (0, item_processor_runtime_1.renderImageListFromHtml)(deps, html, arg);
|
|
134
|
+
return await appendVideoMessage(deps, html, arg, msg);
|
|
135
|
+
}
|
|
136
|
+
case 'only image':
|
|
137
|
+
return await (0, item_processor_runtime_1.renderImageListFromHtml)(deps, html, arg);
|
|
138
|
+
case 'only video': {
|
|
139
|
+
const videoList = [];
|
|
140
|
+
await (0, item_processor_runtime_1.processVideos)(deps, html, arg, videoList);
|
|
141
|
+
return (0, item_processor_runtime_1.formatVideoList)(videoList);
|
|
142
|
+
}
|
|
143
|
+
case 'proto':
|
|
144
|
+
return item.description;
|
|
145
|
+
case 'default': {
|
|
146
|
+
const msg = await processDefaultTemplate(deps, item, arg, parseContent);
|
|
147
|
+
return await appendVideoMessage(deps, html, arg, msg);
|
|
148
|
+
}
|
|
149
|
+
case 'only description': {
|
|
150
|
+
const msg = await processOnlyDescriptionTemplate(deps, item, arg, parseContent);
|
|
151
|
+
return await appendVideoMessage(deps, html, arg, msg);
|
|
152
|
+
}
|
|
153
|
+
case 'link':
|
|
154
|
+
return await processLinkTemplate(deps, item, arg);
|
|
155
|
+
default:
|
|
156
|
+
return item.description;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -5,20 +5,6 @@ export declare class RssItemProcessor {
|
|
|
5
5
|
private config;
|
|
6
6
|
private $http;
|
|
7
7
|
constructor(ctx: Context, config: Config, $http: any);
|
|
8
|
+
private getRuntimeDeps;
|
|
8
9
|
parseRssItem(item: any, arg: rssArg, authorId: string | number): Promise<string>;
|
|
9
|
-
private processTemplate;
|
|
10
|
-
private processCustomTemplate;
|
|
11
|
-
private processContentTemplate;
|
|
12
|
-
private processOnlyMediaTemplate;
|
|
13
|
-
private processOnlyImageTemplate;
|
|
14
|
-
private processDefaultTemplate;
|
|
15
|
-
private processOnlyDescriptionTemplate;
|
|
16
|
-
private processLinkTemplate;
|
|
17
|
-
private processVideos;
|
|
18
|
-
private formatVideoList;
|
|
19
|
-
/**
|
|
20
|
-
* 统一的图片渲染方法
|
|
21
|
-
* 提取了 custom、default、only description、link 模板中重复的图片渲染逻辑
|
|
22
|
-
*/
|
|
23
|
-
private renderImage;
|
|
24
10
|
}
|