@anyul/koishi-plugin-rss 5.0.5 → 5.1.0
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/config.js +53 -0
- package/lib/core/ai.js +47 -2
- package/lib/core/search.d.ts +101 -0
- package/lib/core/search.js +508 -0
- package/lib/index.js +113 -33
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/types.d.ts +37 -0
- package/package.json +1 -1
package/lib/config.js
CHANGED
|
@@ -94,6 +94,59 @@ exports.Config = koishi_1.Schema.object({
|
|
|
94
94
|
maxInputLength: koishi_1.Schema.number().description('发送给 AI 的最大字数限制').default(2000),
|
|
95
95
|
timeout: koishi_1.Schema.number().description('AI 请求超时时间(毫秒)').default(30000),
|
|
96
96
|
}).description('AI 摘要设置'),
|
|
97
|
+
search: koishi_1.Schema.intersect([
|
|
98
|
+
koishi_1.Schema.object({
|
|
99
|
+
enabled: koishi_1.Schema.boolean().description('启用联网搜索增强 AI 摘要').default(false),
|
|
100
|
+
engine: koishi_1.Schema.union(['tavily', 'searxng', 'volcengine', 'auto']).description('搜索引擎选择(auto=自动按优先级选择)').default('tavily'),
|
|
101
|
+
maxResults: koishi_1.Schema.number().description('最大搜索结果数').default(5).min(1).max(10),
|
|
102
|
+
enginePriority: koishi_1.Schema.array(koishi_1.Schema.union(['tavily', 'searxng', 'volcengine'])).description('引擎优先级(当 engine 为 auto 时使用)').default(['tavily', 'volcengine', 'searxng']),
|
|
103
|
+
}),
|
|
104
|
+
koishi_1.Schema.union([
|
|
105
|
+
koishi_1.Schema.object({
|
|
106
|
+
engine: koishi_1.Schema.const('tavily').required(),
|
|
107
|
+
tavily: koishi_1.Schema.intersect([
|
|
108
|
+
koishi_1.Schema.object({ enabled: koishi_1.Schema.const(true).required() }),
|
|
109
|
+
koishi_1.Schema.union([
|
|
110
|
+
koishi_1.Schema.object({
|
|
111
|
+
apiKey: koishi_1.Schema.string().role('secret').description('Tavily API Key(获取地址: https://tavily.com)').required(),
|
|
112
|
+
searchDepth: koishi_1.Schema.union(['basic', 'advanced']).description('搜索深度').default('basic'),
|
|
113
|
+
includeAnswer: koishi_1.Schema.boolean().description('是否包含 AI 生成的搜索答案').default(true),
|
|
114
|
+
}),
|
|
115
|
+
koishi_1.Schema.object({}),
|
|
116
|
+
]),
|
|
117
|
+
]),
|
|
118
|
+
}),
|
|
119
|
+
koishi_1.Schema.object({
|
|
120
|
+
engine: koishi_1.Schema.const('searxng').required(),
|
|
121
|
+
searxng: koishi_1.Schema.intersect([
|
|
122
|
+
koishi_1.Schema.object({ enabled: koishi_1.Schema.const(true).required() }),
|
|
123
|
+
koishi_1.Schema.union([
|
|
124
|
+
koishi_1.Schema.object({
|
|
125
|
+
instanceUrl: koishi_1.Schema.string().role('link').description('SearXNG 实例 URL(自建或公共实例)').required(),
|
|
126
|
+
language: koishi_1.Schema.string().description('搜索语言(例如: all, zh, en)').default('all'),
|
|
127
|
+
categories: koishi_1.Schema.array(koishi_1.Schema.union(['general', 'news', 'images', 'videos'])).description('搜索类别').default(['general']),
|
|
128
|
+
}),
|
|
129
|
+
koishi_1.Schema.object({}),
|
|
130
|
+
]),
|
|
131
|
+
]),
|
|
132
|
+
}),
|
|
133
|
+
koishi_1.Schema.object({
|
|
134
|
+
engine: koishi_1.Schema.const('volcengine').required(),
|
|
135
|
+
volcengine: koishi_1.Schema.intersect([
|
|
136
|
+
koishi_1.Schema.object({ enabled: koishi_1.Schema.const(true).required() }),
|
|
137
|
+
koishi_1.Schema.union([
|
|
138
|
+
koishi_1.Schema.object({
|
|
139
|
+
apiKey: koishi_1.Schema.string().role('secret').description('火山引擎 API Key(与 AI 配置中的 API Key 相同,或单独配置)').required(),
|
|
140
|
+
models: koishi_1.Schema.array(koishi_1.Schema.string()).description('模型列表(支持轮询,留空则使用默认模型)').default([]),
|
|
141
|
+
useAiModel: koishi_1.Schema.boolean().description('是否使用 AI 配置中的 model(优先级高于 models)').default(true),
|
|
142
|
+
}),
|
|
143
|
+
koishi_1.Schema.object({}),
|
|
144
|
+
]),
|
|
145
|
+
]),
|
|
146
|
+
}),
|
|
147
|
+
koishi_1.Schema.object({}),
|
|
148
|
+
]),
|
|
149
|
+
]).description('联网搜索设置(AI 摘要增强)'),
|
|
97
150
|
cache: koishi_1.Schema.object({
|
|
98
151
|
enabled: koishi_1.Schema.boolean().description('启用消息缓存').default(true),
|
|
99
152
|
maxSize: koishi_1.Schema.number().description('最大缓存消息条数').default(100),
|
package/lib/core/ai.js
CHANGED
|
@@ -50,6 +50,7 @@ const https_proxy_agent_1 = require("https-proxy-agent");
|
|
|
50
50
|
const cheerio = __importStar(require("cheerio"));
|
|
51
51
|
const crypto = __importStar(require("crypto"));
|
|
52
52
|
const logger_1 = require("../utils/logger");
|
|
53
|
+
const search_1 = require("./search");
|
|
53
54
|
/**
|
|
54
55
|
* AI 摘要缓存管理器
|
|
55
56
|
*/
|
|
@@ -247,9 +248,31 @@ async function getAiSummary(config, title, contentHtml) {
|
|
|
247
248
|
return cachedSummary;
|
|
248
249
|
}
|
|
249
250
|
// 构建 Prompt
|
|
250
|
-
|
|
251
|
+
let prompt = config.ai.prompt
|
|
251
252
|
.replace('{{title}}', title || '')
|
|
252
253
|
.replace('{{content}}', plainText);
|
|
254
|
+
// 如果启用了联网搜索,进行搜索并增强 Prompt
|
|
255
|
+
if (config.search?.enabled) {
|
|
256
|
+
try {
|
|
257
|
+
// 生成搜索查询(使用标题作为查询)
|
|
258
|
+
const searchQuery = title || plainText.substring(0, 100);
|
|
259
|
+
(0, logger_1.debug)(config, `正在联网搜索: ${searchQuery}`, 'AI-Search', 'info');
|
|
260
|
+
// 执行搜索
|
|
261
|
+
const searchResults = await (0, search_1.webSearch)(config, searchQuery, config.search);
|
|
262
|
+
// 如果搜索成功,将搜索结果添加到 Prompt 中
|
|
263
|
+
if (searchResults.success && searchResults.results.length > 0) {
|
|
264
|
+
(0, logger_1.debug)(config, `联网搜索成功,找到 ${searchResults.results.length} 条结果`, 'AI-Search', 'details');
|
|
265
|
+
prompt = (0, search_1.buildPromptWithSearchContext)(prompt, searchResults, searchQuery);
|
|
266
|
+
}
|
|
267
|
+
else if (searchResults.error) {
|
|
268
|
+
(0, logger_1.debug)(config, `联网搜索失败: ${searchResults.error}`, 'AI-Search', 'info');
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
(0, logger_1.debug)(config, `联网搜索异常: ${error.message}`, 'AI-Search', 'error');
|
|
273
|
+
// 搜索失败时不影响摘要生成,继续使用原始 Prompt
|
|
274
|
+
}
|
|
275
|
+
}
|
|
253
276
|
// 调用 AI API
|
|
254
277
|
const result = await callAiApi(config, prompt, `单条摘要: ${title}`);
|
|
255
278
|
// 缓存成功的结果
|
|
@@ -283,7 +306,7 @@ async function getBatchAiSummary(config, items) {
|
|
|
283
306
|
return '';
|
|
284
307
|
}
|
|
285
308
|
// 构建批量 Prompt
|
|
286
|
-
|
|
309
|
+
let prompt = `请简要总结以下 ${cleanedItems.length} 条新闻/文章的核心内容,要求:
|
|
287
310
|
1. 语言简洁流畅,每条总结不超过30字
|
|
288
311
|
2. 按顺序总结,使用数字编号
|
|
289
312
|
3. 突出重点信息
|
|
@@ -294,6 +317,28 @@ ${index + 1}. 标题:${item.title}
|
|
|
294
317
|
`).join('\n')}
|
|
295
318
|
|
|
296
319
|
总结:`;
|
|
320
|
+
// 如果启用了联网搜索,对第一个或最重要的标题进行搜索
|
|
321
|
+
if (config.search?.enabled && cleanedItems.length > 0) {
|
|
322
|
+
try {
|
|
323
|
+
// 使用第一条内容的标题作为搜索查询
|
|
324
|
+
const searchQuery = cleanedItems[0].title;
|
|
325
|
+
(0, logger_1.debug)(config, `批量摘要 - 正在联网搜索: ${searchQuery}`, 'AI-Search', 'info');
|
|
326
|
+
// 执行搜索
|
|
327
|
+
const searchResults = await (0, search_1.webSearch)(config, searchQuery, config.search);
|
|
328
|
+
// 如果搜索成功,将搜索结果添加到 Prompt 中
|
|
329
|
+
if (searchResults.success && searchResults.results.length > 0) {
|
|
330
|
+
(0, logger_1.debug)(config, `批量摘要 - 联网搜索成功,找到 ${searchResults.results.length} 条结果`, 'AI-Search', 'details');
|
|
331
|
+
prompt = (0, search_1.buildPromptWithSearchContext)(prompt, searchResults, searchQuery);
|
|
332
|
+
}
|
|
333
|
+
else if (searchResults.error) {
|
|
334
|
+
(0, logger_1.debug)(config, `批量摘要 - 联网搜索失败: ${searchResults.error}`, 'AI-Search', 'info');
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
(0, logger_1.debug)(config, `批量摘要 - 联网搜索异常: ${error.message}`, 'AI-Search', 'error');
|
|
339
|
+
// 搜索失败时不影响摘要生成,继续使用原始 Prompt
|
|
340
|
+
}
|
|
341
|
+
}
|
|
297
342
|
// 调用 AI API
|
|
298
343
|
const result = await callAiApi(config, prompt, `批量摘要: ${cleanedItems.length}条`);
|
|
299
344
|
if (result.success && result.summary) {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 联网搜索模块
|
|
3
|
+
*
|
|
4
|
+
* 支持多种搜索引擎:
|
|
5
|
+
* - Tavily: 专业的 AI 搜索引擎
|
|
6
|
+
* - Searxng: 开源隐私友好的元搜索引擎
|
|
7
|
+
* - Volcengine: 火山引擎联网搜索(支持模型轮询)
|
|
8
|
+
*
|
|
9
|
+
* @module core/search
|
|
10
|
+
*/
|
|
11
|
+
import { Config, SearchConfig } from '../types';
|
|
12
|
+
/**
|
|
13
|
+
* 搜索结果接口
|
|
14
|
+
*/
|
|
15
|
+
export interface SearchResult {
|
|
16
|
+
title: string;
|
|
17
|
+
url: string;
|
|
18
|
+
snippet?: string;
|
|
19
|
+
content?: string;
|
|
20
|
+
score?: number;
|
|
21
|
+
publishedDate?: string;
|
|
22
|
+
source?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 搜索响应接口
|
|
26
|
+
*/
|
|
27
|
+
export interface SearchResponse {
|
|
28
|
+
success: boolean;
|
|
29
|
+
results: SearchResult[];
|
|
30
|
+
query: string;
|
|
31
|
+
engine: string;
|
|
32
|
+
model?: string;
|
|
33
|
+
error?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Tavily 搜索引擎
|
|
37
|
+
*
|
|
38
|
+
* @param config - 插件配置
|
|
39
|
+
* @param query - 搜索查询
|
|
40
|
+
* @param apiKey - Tavily API Key
|
|
41
|
+
* @returns 搜索结果
|
|
42
|
+
*/
|
|
43
|
+
export declare function searchWithTavily(config: Config, query: string, apiKey: string, options?: {
|
|
44
|
+
maxResults?: number;
|
|
45
|
+
searchDepth?: 'basic' | 'advanced';
|
|
46
|
+
includeAnswer?: boolean;
|
|
47
|
+
}): Promise<SearchResponse>;
|
|
48
|
+
/**
|
|
49
|
+
* SearXNG 搜索引擎
|
|
50
|
+
*
|
|
51
|
+
* @param config - 插件配置
|
|
52
|
+
* @param query - 搜索查询
|
|
53
|
+
* @param instanceUrl - SearXNG 实例 URL
|
|
54
|
+
* @returns 搜索结果
|
|
55
|
+
*/
|
|
56
|
+
export declare function searchWithSearxng(config: Config, query: string, instanceUrl: string, options?: {
|
|
57
|
+
maxResults?: number;
|
|
58
|
+
language?: string;
|
|
59
|
+
categories?: Array<'general' | 'news' | 'images' | 'videos'>;
|
|
60
|
+
}): Promise<SearchResponse>;
|
|
61
|
+
/**
|
|
62
|
+
* 火山引擎联网搜索(支持模型轮询)
|
|
63
|
+
*
|
|
64
|
+
* @param config - 插件配置
|
|
65
|
+
* @param query - 搜索查询
|
|
66
|
+
* @param baseUrl - API Base URL
|
|
67
|
+
* @param apiKey - API Key
|
|
68
|
+
* @param model - 模型名称(可选,如果不指定则使用轮询)
|
|
69
|
+
* @param searchConfig - 搜索配置(用于模型轮询)
|
|
70
|
+
* @returns 搜索结果
|
|
71
|
+
*/
|
|
72
|
+
export declare function searchWithVolcengine(config: Config, query: string, baseUrl: string, apiKey: string, model?: string, searchConfig?: SearchConfig): Promise<SearchResponse>;
|
|
73
|
+
/**
|
|
74
|
+
* 统一搜索接口
|
|
75
|
+
*
|
|
76
|
+
* 根据配置自动选择搜索引擎并执行搜索
|
|
77
|
+
* - 如果 engine 为 'auto',则根据配置的 API Keys 按优先级自动选择
|
|
78
|
+
* - 支持多引擎配置时智能选择
|
|
79
|
+
*
|
|
80
|
+
* @param config - 插件配置
|
|
81
|
+
* @param query - 搜索查询
|
|
82
|
+
* @param searchConfig - 搜索配置
|
|
83
|
+
* @returns 搜索结果
|
|
84
|
+
*/
|
|
85
|
+
export declare function webSearch(config: Config, query: string, searchConfig: SearchConfig): Promise<SearchResponse>;
|
|
86
|
+
/**
|
|
87
|
+
* 将搜索结果格式化为 AI 可读的文本
|
|
88
|
+
*
|
|
89
|
+
* @param response - 搜索响应
|
|
90
|
+
* @returns 格式化的文本
|
|
91
|
+
*/
|
|
92
|
+
export declare function formatSearchResults(response: SearchResponse): string;
|
|
93
|
+
/**
|
|
94
|
+
* 构建带搜索上下文的 AI Prompt
|
|
95
|
+
*
|
|
96
|
+
* @param originalPrompt - 原始提示词
|
|
97
|
+
* @param searchResults - 搜索结果
|
|
98
|
+
* @param searchQuery - 搜索查询
|
|
99
|
+
* @returns 增强后的提示词
|
|
100
|
+
*/
|
|
101
|
+
export declare function buildPromptWithSearchContext(originalPrompt: string, searchResults: SearchResponse, searchQuery: string): string;
|