@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
package/lib/index.js
CHANGED
|
@@ -16,7 +16,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.inject = exports.name = exports.templateList = exports.Config = void 0;
|
|
18
18
|
exports.apply = apply;
|
|
19
|
-
const koishi_1 = require("koishi");
|
|
20
19
|
// Export types and config
|
|
21
20
|
var config_1 = require("./config");
|
|
22
21
|
Object.defineProperty(exports, "Config", { enumerable: true, get: function () { return config_1.Config; } });
|
|
@@ -24,17 +23,11 @@ __exportStar(require("./types"), exports);
|
|
|
24
23
|
var config_2 = require("./config");
|
|
25
24
|
Object.defineProperty(exports, "templateList", { enumerable: true, get: function () { return config_2.templateList; } });
|
|
26
25
|
exports.name = '@anyul/koishi-plugin-rss';
|
|
27
|
-
// Import utilities
|
|
28
|
-
const logger_1 = require("./utils/logger");
|
|
29
26
|
const fetcher_1 = require("./utils/fetcher");
|
|
30
|
-
const common_1 = require("./utils/common");
|
|
31
27
|
const media_1 = require("./utils/media");
|
|
32
|
-
const error_handler_1 = require("./utils/error-handler");
|
|
33
28
|
const error_tracker_1 = require("./utils/error-tracker");
|
|
34
29
|
const commands_1 = require("./commands");
|
|
35
30
|
// Import core modules
|
|
36
|
-
const ai_1 = require("./core/ai");
|
|
37
|
-
const parser_1 = require("./core/parser");
|
|
38
31
|
const item_processor_1 = require("./core/item-processor");
|
|
39
32
|
const feeder_1 = require("./core/feeder");
|
|
40
33
|
const message_cache_1 = require("./utils/message-cache");
|
|
@@ -43,9 +36,6 @@ const notification_queue_1 = require("./core/notification-queue");
|
|
|
43
36
|
// Import database and constants
|
|
44
37
|
const database_1 = require("./database");
|
|
45
38
|
const constants_1 = require("./constants");
|
|
46
|
-
const logger = new koishi_1.Logger('rss-owl');
|
|
47
|
-
const X2JS = require("x2js");
|
|
48
|
-
const x2js = new X2JS();
|
|
49
39
|
exports.inject = { required: ["database"], optional: ["puppeteer", "censor", "assets", "server"] };
|
|
50
40
|
function apply(ctx, config) {
|
|
51
41
|
// Setup database
|
|
@@ -65,6 +55,7 @@ function apply(ctx, config) {
|
|
|
65
55
|
const $http = (0, fetcher_1.createHttpFunction)(ctx, config, requestManager);
|
|
66
56
|
// Initialize RSS item processor
|
|
67
57
|
const processor = new item_processor_1.RssItemProcessor(ctx, config, $http);
|
|
58
|
+
const commandRuntime = (0, commands_1.createCommandRuntimeDeps)(ctx, config, $http, processor);
|
|
68
59
|
// Initialize notification queue manager
|
|
69
60
|
const queueManager = new notification_queue_1.NotificationQueueManager(ctx, config);
|
|
70
61
|
// Initialize message cache
|
|
@@ -83,385 +74,40 @@ function apply(ctx, config) {
|
|
|
83
74
|
(0, media_1.delCache)(config);
|
|
84
75
|
}
|
|
85
76
|
});
|
|
86
|
-
// Helper functions for commands
|
|
87
|
-
const debugLocal = (message, name = '', type = 'details') => {
|
|
88
|
-
(0, logger_1.debug)(config, message, name, type);
|
|
89
|
-
};
|
|
90
|
-
// Frequently used helper functions
|
|
91
|
-
const parseQuickUrlLocal = (url) => (0, common_1.parseQuickUrl)(url, config.msg.rssHubUrl, constants_1.quickList);
|
|
92
|
-
const parsePubDateLocal = (pubDate) => (0, common_1.parsePubDate)(config, pubDate);
|
|
93
|
-
const getRssDataLocal = async (url, arg) => (0, parser_1.getRssData)(ctx, config, $http, url, arg);
|
|
94
|
-
const parseRssItem = async (item, arg, authorId) => processor.parseRssItem(item, arg, authorId);
|
|
95
|
-
const formatArgLocal = (options) => (0, feeder_1.formatArg)(options, config);
|
|
96
|
-
const mixinArgLocal = (arg) => (0, feeder_1.mixinArg)(arg, config);
|
|
97
|
-
const findRssItemLocal = (rssList, keyword) => (0, feeder_1.findRssItem)(rssList, keyword);
|
|
98
|
-
const generateSelectorByAILocal = async (url, instruction, html) => (0, ai_1.generateSelectorByAI)(config, url, instruction, html);
|
|
99
77
|
// ============================================
|
|
100
78
|
// 子命令:订阅管理
|
|
101
79
|
// ============================================
|
|
102
80
|
(0, commands_1.registerSubscriptionManagementCommands)({
|
|
103
81
|
ctx,
|
|
104
82
|
config,
|
|
105
|
-
parsePubDate:
|
|
106
|
-
parseQuickUrl:
|
|
107
|
-
getRssData:
|
|
108
|
-
parseRssItem,
|
|
109
|
-
mixinArg:
|
|
83
|
+
parsePubDate: commandRuntime.parsePubDate,
|
|
84
|
+
parseQuickUrl: commandRuntime.parseQuickUrl,
|
|
85
|
+
getRssData: commandRuntime.getRssData,
|
|
86
|
+
parseRssItem: commandRuntime.parseRssItem,
|
|
87
|
+
mixinArg: commandRuntime.mixinArg,
|
|
110
88
|
});
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
.
|
|
117
|
-
.
|
|
118
|
-
.
|
|
119
|
-
.
|
|
120
|
-
.
|
|
121
|
-
.
|
|
122
|
-
.
|
|
123
|
-
.option('followAll', '<序号> 在该订阅更新时提醒所有人 [已移至 rsso.follow --all 子命令,使用列表序号]')
|
|
124
|
-
.option('target', '--target <platform:guildId> 跨群订阅(高级权限)')
|
|
125
|
-
.option('arg', '-a <content> 自定义配置')
|
|
126
|
-
.option('template', '-i <content> 消息模板')
|
|
127
|
-
.option('title', '-t <content> 自定义命名')
|
|
128
|
-
.option('pull', '-p <序号> 拉取订阅最新更新 [已移至 rsso.pull 子命令,使用列表序号]')
|
|
129
|
-
.option('force', '强行写入')
|
|
130
|
-
.option('daily', '-d <content>')
|
|
131
|
-
.option('test', '-T 测试')
|
|
132
|
-
.option('quick', '-q [content] 查询快速订阅列表')
|
|
133
|
-
.example('rsso https://hub.slarker.me/qqorw')
|
|
134
|
-
.action(async ({ session, options }, url) => {
|
|
135
|
-
debugLocal(options, 'options', 'info');
|
|
136
|
-
const { id: guildId } = session.event.guild;
|
|
137
|
-
const { platform } = session.event;
|
|
138
|
-
const { id: userId } = session.event.user;
|
|
139
|
-
const { authority } = session.user;
|
|
140
|
-
// 获取 bot selfId 用于后续推送
|
|
141
|
-
const botSelfId = session.bot?.selfId;
|
|
142
|
-
debugLocal(`${platform}:${userId}:${guildId}, bot:${botSelfId}`, '', 'info');
|
|
143
|
-
if (options?.quick === '') {
|
|
144
|
-
return '输入 rsso -q [id] 查询详情\n' + constants_1.quickList.map((v, i) => `${i + 1}.${v.name}`).join('\n');
|
|
145
|
-
}
|
|
146
|
-
if (options?.quick) {
|
|
147
|
-
let correntQuickObj = constants_1.quickList[parseInt(options?.quick) - 1];
|
|
148
|
-
return `${correntQuickObj.name}\n${correntQuickObj.detail}\n例:rsso -T ${correntQuickObj.example}\n(${parseQuickUrlLocal(correntQuickObj.example)})`;
|
|
149
|
-
}
|
|
150
|
-
if ((platform.indexOf("sandbox") + 1) && !options.test && url) {
|
|
151
|
-
session.send('沙盒中无法推送更新,但RSS依然会被订阅,建议使用 -T 选项进行测试');
|
|
152
|
-
}
|
|
153
|
-
const rssList = await ctx.database.get('rssOwl', { platform, guildId });
|
|
154
|
-
if (options?.list === '' || options?.list) {
|
|
155
|
-
return `💡 提示:请使用子命令查看订阅\n\nrsso.list - 查看所有订阅(显示序号)\nrsso.list 1 - 查看订阅详情\n\n(旧选项 -l 仍可使用,但建议迁移到新命令)`;
|
|
156
|
-
}
|
|
157
|
-
if (options?.remove) {
|
|
158
|
-
return `💡 提示:请使用子命令删除订阅\n\nrsso.remove 1 - 删除订阅 #1(使用列表序号)\nrsso.remove --all - 删除全部订阅\n\n(旧选项 -r 仍可使用,但建议迁移到新命令)`;
|
|
159
|
-
}
|
|
160
|
-
if (options?.removeAll) {
|
|
161
|
-
return `💡 提示:请使用子命令删除订阅\n\nrsso.remove --all - 删除全部订阅\n\n(旧选项仍可使用,但建议迁移到新命令)`;
|
|
162
|
-
}
|
|
163
|
-
if (options?.follow) {
|
|
164
|
-
return `💡 提示:请使用子命令关注订阅\n\nrsso.follow 1 - 关注订阅 #1(使用列表序号)\n\n(旧选项 -f 仍可使用,但建议迁移到新命令)`;
|
|
165
|
-
}
|
|
166
|
-
if (options?.followAll) {
|
|
167
|
-
return `💡 提示:请使用子命令设置全员提醒\n\nrsso.follow 1 --all - 设置全员提醒(使用列表序号)\n\n(旧选项仍可使用,但建议迁移到新命令)`;
|
|
168
|
-
}
|
|
169
|
-
if (options?.pull) {
|
|
170
|
-
return `💡 提示:请使用子命令拉取订阅\n\nrsso.pull 1 - 拉取订阅 #1 的最新更新(使用列表序号)\n\n(旧选项 -p 仍可使用,但建议迁移到新命令)`;
|
|
171
|
-
}
|
|
172
|
-
if (url) {
|
|
173
|
-
if (rssList.find(i => i.url == url))
|
|
174
|
-
return '该订阅已存在';
|
|
175
|
-
let rawArg = formatArgLocal(options);
|
|
176
|
-
let arg = mixinArgLocal(rawArg);
|
|
177
|
-
let targetPlatform = platform;
|
|
178
|
-
let targetGuildId = guildId;
|
|
179
|
-
if (options?.target) {
|
|
180
|
-
if (authority >= config.basic.advancedAuthority) {
|
|
181
|
-
let target = options.target.split(/[::]/);
|
|
182
|
-
if (target.length == 1) {
|
|
183
|
-
return '请输入正确的群号,格式为 platform:guildId 或 platform:guildId\n示例: onebot:123456';
|
|
184
|
-
}
|
|
185
|
-
targetPlatform = target[0];
|
|
186
|
-
targetGuildId = target[1];
|
|
187
|
-
// 测试模式:发送验证消息到目标群组
|
|
188
|
-
if (options.test) {
|
|
189
|
-
try {
|
|
190
|
-
await ctx.broadcast([`${targetPlatform}:${targetGuildId}`], '📤 跨群订阅测试消息');
|
|
191
|
-
return `✅ 测试消息已发送到目标群组\n目标: ${targetPlatform}:${targetGuildId}\n\n说明:Bot 可以访问该群组,跨群订阅可以正常工作。\n去掉 --test 选项完成订阅。`;
|
|
192
|
-
}
|
|
193
|
-
catch (error) {
|
|
194
|
-
return `❌ 无法发送到目标群组\n目标: ${targetPlatform}:${targetGuildId}\n错误: ${error.message}\n\n请确认:\n1. Bot 是否在该群组中\n2. 群组ID 是否正确\n3. 平台名称是否正确(如 onebot, telegram 等)`;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
return `权限不足!当前权限: ${authority},需要权限: ${config.basic.advancedAuthority} 或以上`;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
let title = options?.title || "";
|
|
203
|
-
let rssItemList = [];
|
|
204
|
-
try {
|
|
205
|
-
url = parseQuickUrlLocal(url);
|
|
206
|
-
rssItemList = await getRssDataLocal((0, common_1.ensureUrlProtocol)(url), arg);
|
|
207
|
-
if (options.test) {
|
|
208
|
-
let testItem = rssItemList[0];
|
|
209
|
-
if (!testItem)
|
|
210
|
-
return '未获取到数据';
|
|
211
|
-
// 应用默认模板配置(如果没有指定模板)
|
|
212
|
-
let testArg = { ...arg, url: title || testItem.rss.channel.title, title: title || testItem.rss.channel.title };
|
|
213
|
-
if (!testArg.template) {
|
|
214
|
-
testArg.template = config.basic.defaultTemplate;
|
|
215
|
-
}
|
|
216
|
-
let msg = await parseRssItem(testItem, testArg, userId);
|
|
217
|
-
return msg;
|
|
218
|
-
}
|
|
219
|
-
if (!title) {
|
|
220
|
-
title = rssItemList[0]?.rss.channel.title;
|
|
221
|
-
if (!title)
|
|
222
|
-
return '无法获取标题,请使用 -t 指定标题';
|
|
223
|
-
}
|
|
224
|
-
let lastPubDate = parsePubDateLocal(rssItemList[0]?.pubDate);
|
|
225
|
-
let rssItem = {
|
|
226
|
-
url,
|
|
227
|
-
platform: targetPlatform,
|
|
228
|
-
guildId: targetGuildId,
|
|
229
|
-
author: botSelfId,
|
|
230
|
-
rssId: rssItemList[0]?.rss?.channel?.title ? rssItemList[0].rss.channel.title : title,
|
|
231
|
-
arg: rawArg,
|
|
232
|
-
title,
|
|
233
|
-
lastPubDate,
|
|
234
|
-
lastContent: [],
|
|
235
|
-
followers: []
|
|
236
|
-
};
|
|
237
|
-
if (options.force) {
|
|
238
|
-
if (authority < config.basic.authority)
|
|
239
|
-
return `权限不足!当前权限: ${authority},需要权限: ${config.basic.authority} 或以上`;
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
if (config.basic.urlDeduplication && rssList.find(i => i.rssId == rssItem.rssId))
|
|
243
|
-
return `订阅已存在: ${rssItem.rssId}`;
|
|
244
|
-
}
|
|
245
|
-
await ctx.database.create('rssOwl', rssItem);
|
|
246
|
-
if (config.basic.firstLoad && arg.firstLoad !== false && rssItemList.length > 0) {
|
|
247
|
-
let itemArray = rssItemList.sort((a, b) => parsePubDateLocal(b.pubDate).getTime() - parsePubDateLocal(a.pubDate).getTime());
|
|
248
|
-
if (arg.reverse)
|
|
249
|
-
itemArray = itemArray.reverse();
|
|
250
|
-
const maxItem = arg.forceLength || 1;
|
|
251
|
-
// 使用合并后的配置来确保图片/视频模式生效
|
|
252
|
-
const mergedArg = mixinArgLocal(rssItem.arg);
|
|
253
|
-
let messageList = await Promise.all(itemArray.filter((v, i) => i < maxItem).map(async (i) => await parseRssItem(i, { ...rssItem, ...mergedArg }, rssItem.author)));
|
|
254
|
-
let message = messageList.join("");
|
|
255
|
-
await ctx.broadcast([`${targetPlatform}:${targetGuildId}`], message);
|
|
256
|
-
}
|
|
257
|
-
return `订阅成功: ${title}`;
|
|
258
|
-
}
|
|
259
|
-
catch (error) {
|
|
260
|
-
debugLocal(error, 'add error', 'error');
|
|
261
|
-
return `订阅失败: ${(0, error_handler_1.getFriendlyErrorMessage)(error, '添加订阅')}`;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
return constants_1.usage;
|
|
265
|
-
});
|
|
266
|
-
// HTML monitoring command
|
|
267
|
-
ctx.guild()
|
|
268
|
-
.command('rssowl.html <url:string>', '监控网页变化 (CSS Selector)')
|
|
269
|
-
.alias('rsso.html')
|
|
270
|
-
.usage(`
|
|
271
|
-
HTML 网页监控功能,使用 CSS 选择器提取内容
|
|
272
|
-
用法:
|
|
273
|
-
rsso.html https://example.com -s ".item" - 监控网页变化
|
|
274
|
-
rsso.html https://example.com -s ".item" -T - 测试选择器
|
|
275
|
-
rsso.html https://example.com -s ".item" -t "我的订阅" - 自定义标题
|
|
276
|
-
rsso.html https://example.com -s ".item" -P - SPA 动态页面
|
|
277
|
-
rsso.html https://example.com -s ".item" -w 5000 - 渲染后等待5秒
|
|
278
|
-
|
|
279
|
-
示例:
|
|
280
|
-
rsso.html https://www.zhihu.com/billboard -s ".BillBoard-item:first-child"
|
|
281
|
-
rsso.html https://news.ycombinator.com -s ".titleline > a"
|
|
282
|
-
`)
|
|
283
|
-
.option('selector', '-s <选择器> CSS 选择器 (必填)')
|
|
284
|
-
.option('title', '-t <标题> 自定义订阅标题')
|
|
285
|
-
.option('template', '-i <模板> 消息模板 (推荐 content)')
|
|
286
|
-
.option('text', '--text 只提取纯文本')
|
|
287
|
-
.option('puppeteer', '-P 使用 Puppeteer 渲染 (适用于SPA)')
|
|
288
|
-
.option('wait', '-w <毫秒> 渲染后等待时间')
|
|
289
|
-
.option('waitSelector', '-W <选择器> 等待特定元素出现')
|
|
290
|
-
.option('test', '-T 测试抓取结果 (不创建订阅)')
|
|
291
|
-
.example('rsso.html https://news.ycombinator.com -s ".titleline > a"')
|
|
292
|
-
.action(async ({ session, options }, url) => {
|
|
293
|
-
if (!url)
|
|
294
|
-
return '请输入 URL';
|
|
295
|
-
if (!options.selector)
|
|
296
|
-
return '请指定 CSS 选择器 (-s)';
|
|
297
|
-
const { id: guildId } = session.event.guild;
|
|
298
|
-
const { platform } = session.event;
|
|
299
|
-
const { id: userId } = session.event.user;
|
|
300
|
-
// 获取 bot selfId 用于后续推送
|
|
301
|
-
const botSelfId = session.bot?.selfId;
|
|
302
|
-
url = (0, common_1.ensureUrlProtocol)(url);
|
|
303
|
-
let rawArg = {
|
|
304
|
-
type: 'html',
|
|
305
|
-
selector: options.selector,
|
|
306
|
-
template: options.template || 'content',
|
|
307
|
-
textOnly: !!options.text,
|
|
308
|
-
mode: options.puppeteer ? 'puppeteer' : 'static',
|
|
309
|
-
waitFor: options.wait ? parseInt(options.wait) : undefined,
|
|
310
|
-
waitSelector: options.waitSelector,
|
|
311
|
-
title: options.title
|
|
312
|
-
};
|
|
313
|
-
let arg = mixinArgLocal(rawArg);
|
|
314
|
-
try {
|
|
315
|
-
// Test mode: just preview the data
|
|
316
|
-
if (options.test) {
|
|
317
|
-
let items = await getRssDataLocal(url, arg);
|
|
318
|
-
if (!items || items.length === 0)
|
|
319
|
-
return '未找到符合选择器的元素';
|
|
320
|
-
let preview = items.slice(0, 3).map((item) => `标题: ${item.title}\n内容: ${item.description?.substring(0, 100)}...`).join('\n\n');
|
|
321
|
-
return `找到 ${items.length} 个元素:\n\n${preview}`;
|
|
322
|
-
}
|
|
323
|
-
// Full subscription flow (similar to RSS subscription)
|
|
324
|
-
const rssList = await ctx.database.get('rssOwl', { platform, guildId });
|
|
325
|
-
// Check if subscription already exists
|
|
326
|
-
if (rssList.find(i => i.url == url)) {
|
|
327
|
-
return '该订阅已存在';
|
|
328
|
-
}
|
|
329
|
-
// Get HTML monitoring data
|
|
330
|
-
let htmlItems = await getRssDataLocal(url, arg);
|
|
331
|
-
if (!htmlItems || htmlItems.length === 0) {
|
|
332
|
-
return '未找到符合选择器的元素,无法创建订阅';
|
|
333
|
-
}
|
|
334
|
-
// Determine title
|
|
335
|
-
let title = options?.title || htmlItems[0]?.rss?.channel?.title || `HTML监控: ${url}`;
|
|
336
|
-
// Create subscription record
|
|
337
|
-
let rssItem = {
|
|
338
|
-
url,
|
|
339
|
-
platform,
|
|
340
|
-
guildId,
|
|
341
|
-
author: botSelfId,
|
|
342
|
-
rssId: title, // Use title as rssId for HTML monitoring
|
|
343
|
-
arg: rawArg,
|
|
344
|
-
title,
|
|
345
|
-
lastPubDate: new Date(), // HTML monitoring doesn't have real timestamps
|
|
346
|
-
lastContent: [],
|
|
347
|
-
followers: []
|
|
348
|
-
};
|
|
349
|
-
// Check for duplicate (if enabled)
|
|
350
|
-
if (config.basic.urlDeduplication && rssList.find(i => i.rssId == rssItem.rssId)) {
|
|
351
|
-
return `订阅已存在: ${rssItem.rssId}`;
|
|
352
|
-
}
|
|
353
|
-
// Save to database
|
|
354
|
-
await ctx.database.create('rssOwl', rssItem);
|
|
355
|
-
// First load preview (if enabled)
|
|
356
|
-
if (config.basic.firstLoad && arg.firstLoad !== false && htmlItems.length > 0) {
|
|
357
|
-
const maxItem = arg.forceLength || 1;
|
|
358
|
-
// 使用合并后的配置来确保图片/视频模式生效
|
|
359
|
-
const mergedArg = mixinArgLocal(rssItem.arg);
|
|
360
|
-
let messageList = await Promise.all(htmlItems
|
|
361
|
-
.filter((v, i) => i < maxItem)
|
|
362
|
-
.map(async (i) => await parseRssItem(i, { ...rssItem, ...mergedArg }, rssItem.author)));
|
|
363
|
-
let message = messageList.join("");
|
|
364
|
-
await ctx.broadcast([`${platform}:${guildId}`], message);
|
|
365
|
-
}
|
|
366
|
-
return `订阅成功: ${title}\n提示: HTML监控基于内容变化检测,请确保选择器稳定`;
|
|
367
|
-
}
|
|
368
|
-
catch (error) {
|
|
369
|
-
debugLocal(error, 'html error', 'error');
|
|
370
|
-
return `抓取失败: ${(0, error_handler_1.getFriendlyErrorMessage)(error, 'HTML监控')}`;
|
|
371
|
-
}
|
|
372
|
-
});
|
|
373
|
-
// AI subscription command
|
|
374
|
-
ctx.guild()
|
|
375
|
-
.command('rssowl.ask <url:string> <instruction:text>', 'AI 智能订阅网页')
|
|
376
|
-
.alias('rsso.ask')
|
|
377
|
-
.usage(`AI 智能订阅功能,自动生成 CSS 选择器
|
|
378
|
-
|
|
379
|
-
前置要求:
|
|
380
|
-
- 需要配置 AI 功能 (config.ai.enabled = true)
|
|
381
|
-
- 需要配置 API Key (config.ai.apiKey)
|
|
382
|
-
|
|
383
|
-
用法:
|
|
384
|
-
rsso.ask https://news.ycombinator.com "监控首页的前5条新闻标题"
|
|
385
|
-
|
|
386
|
-
示例:
|
|
387
|
-
rsso.ask https://www.zhihu.com/billboard "获取热榜第一条"
|
|
388
|
-
rsso.ask https://example.com "提取所有文章标题" -T
|
|
389
|
-
`)
|
|
390
|
-
.option('test', '-T 测试模式 (只分析不订阅)')
|
|
391
|
-
.example('rsso.ask https://news.ycombinator.com "监控首页的前5条新闻标题"')
|
|
392
|
-
.action(async ({ session, options }, url, instruction) => {
|
|
393
|
-
if (!url)
|
|
394
|
-
return '请输入网址';
|
|
395
|
-
if (!instruction)
|
|
396
|
-
return '请描述你的需求';
|
|
397
|
-
url = (0, common_1.ensureUrlProtocol)(url);
|
|
398
|
-
try {
|
|
399
|
-
let html = (await $http(url, {})).data;
|
|
400
|
-
let selector = await generateSelectorByAILocal(url, instruction, html);
|
|
401
|
-
if (options.test) {
|
|
402
|
-
let testArg = {
|
|
403
|
-
type: 'html',
|
|
404
|
-
selector,
|
|
405
|
-
template: 'content'
|
|
406
|
-
};
|
|
407
|
-
let items = await getRssDataLocal(url, testArg);
|
|
408
|
-
if (!items || items.length === 0)
|
|
409
|
-
return `选择器未匹配到任何元素: ${selector}`;
|
|
410
|
-
return `AI 生成的选择器: ${selector}\n\n匹配到 ${items.length} 个元素:\n${items.slice(0, 2).map((i) => i.title).join('\n')}`;
|
|
411
|
-
}
|
|
412
|
-
return `AI 生成的选择器: ${selector}\n请使用 rsso.html ${url} -s "${selector}" 完成订阅`;
|
|
413
|
-
}
|
|
414
|
-
catch (error) {
|
|
415
|
-
debugLocal(error, 'ask error', 'error');
|
|
416
|
-
return `AI 分析失败: ${(0, error_handler_1.getFriendlyErrorMessage)(error, 'AI生成选择器')}`;
|
|
417
|
-
}
|
|
89
|
+
(0, commands_1.registerSubscriptionCreateCommand)({
|
|
90
|
+
ctx,
|
|
91
|
+
config,
|
|
92
|
+
usage: constants_1.usage,
|
|
93
|
+
quickList: constants_1.quickList,
|
|
94
|
+
parseQuickUrl: commandRuntime.parseQuickUrl,
|
|
95
|
+
parsePubDate: commandRuntime.parsePubDate,
|
|
96
|
+
getRssData: commandRuntime.getRssData,
|
|
97
|
+
parseRssItem: commandRuntime.parseRssItem,
|
|
98
|
+
formatArg: commandRuntime.formatArg,
|
|
99
|
+
mixinArg: commandRuntime.mixinArg,
|
|
100
|
+
debug: commandRuntime.debug,
|
|
418
101
|
});
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
.
|
|
423
|
-
.
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
rsso.watch https://example.com "缺货" -P - SPA 动态页面
|
|
429
|
-
rsso.watch https://example.com "缺货" -T - 测试模式 (只预览不订阅)
|
|
430
|
-
`)
|
|
431
|
-
.option('puppeteer', '-P 使用 Puppeteer 渲染')
|
|
432
|
-
.option('test', '-T 测试模式 (只预览不订阅)')
|
|
433
|
-
.example('rsso.watch https://example.com "缺货"')
|
|
434
|
-
.action(async ({ session, options }, url, keyword) => {
|
|
435
|
-
if (!url)
|
|
436
|
-
return '请输入 URL';
|
|
437
|
-
const { id: guildId } = session.event.guild;
|
|
438
|
-
const { platform } = session.event;
|
|
439
|
-
const { id: userId } = session.event.user;
|
|
440
|
-
// 获取 bot selfId 用于后续推送
|
|
441
|
-
const botSelfId = session.bot?.selfId;
|
|
442
|
-
url = (0, common_1.ensureUrlProtocol)(url);
|
|
443
|
-
let rawArg = {
|
|
444
|
-
type: 'html',
|
|
445
|
-
selector: keyword ? `*:contains("${keyword}")` : 'body',
|
|
446
|
-
textOnly: !!keyword,
|
|
447
|
-
mode: options.puppeteer ? 'puppeteer' : 'static',
|
|
448
|
-
template: 'content'
|
|
449
|
-
};
|
|
450
|
-
let arg = mixinArgLocal(rawArg);
|
|
451
|
-
try {
|
|
452
|
-
if (options.test) {
|
|
453
|
-
let items = await getRssDataLocal(url, arg);
|
|
454
|
-
if (!items || items.length === 0)
|
|
455
|
-
return '未找到内容';
|
|
456
|
-
let preview = items.slice(0, 3).map((item) => `标题: ${item.title}\n${item.description?.substring(0, 100)}...`).join('\n\n');
|
|
457
|
-
return `找到 ${items.length} 条内容:\n\n${preview}`;
|
|
458
|
-
}
|
|
459
|
-
return '请使用 rsso 命令完成订阅,或使用 -T 测试';
|
|
460
|
-
}
|
|
461
|
-
catch (error) {
|
|
462
|
-
debugLocal(error, 'watch error', 'error');
|
|
463
|
-
return `监控失败: ${(0, error_handler_1.getFriendlyErrorMessage)(error, '网页监控')}`;
|
|
464
|
-
}
|
|
102
|
+
(0, commands_1.registerWebMonitorCommands)({
|
|
103
|
+
ctx,
|
|
104
|
+
config,
|
|
105
|
+
debug: commandRuntime.debug,
|
|
106
|
+
mixinArg: commandRuntime.mixinArg,
|
|
107
|
+
getRssData: commandRuntime.getRssData,
|
|
108
|
+
parseRssItem: commandRuntime.parseRssItem,
|
|
109
|
+
generateSelectorByAI: commandRuntime.generateSelectorByAI,
|
|
110
|
+
fetchUrl: commandRuntime.fetchUrl,
|
|
465
111
|
});
|
|
466
112
|
(0, commands_1.registerSubscriptionEditCommand)({
|
|
467
113
|
ctx,
|