@zhin.js/plugin-rss 1.0.0 → 1.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.
Files changed (52) hide show
  1. package/README.md +1 -1
  2. package/agent/tools/rss_list_subscriptions.ts +38 -0
  3. package/agent/tools/rss_preview_feed.ts +22 -0
  4. package/commands/rss-add/[url].js +59 -0
  5. package/commands/rss-add/[url].ts +61 -0
  6. package/commands/rss-check/[[url]].js +42 -0
  7. package/commands/rss-check/[[url]].ts +41 -0
  8. package/commands/rss-list.js +28 -0
  9. package/commands/rss-list.ts +31 -0
  10. package/commands/rss-preview/[url].js +22 -0
  11. package/commands/rss-preview/[url].ts +19 -0
  12. package/commands/rss-remove/[url].js +34 -0
  13. package/commands/rss-remove/[url].ts +36 -0
  14. package/lib/channel.d.ts +16 -0
  15. package/lib/channel.d.ts.map +1 -0
  16. package/lib/channel.js +42 -0
  17. package/lib/channel.js.map +1 -0
  18. package/lib/db-store.d.ts +16 -0
  19. package/lib/db-store.d.ts.map +1 -0
  20. package/lib/db-store.js +40 -0
  21. package/lib/db-store.js.map +1 -0
  22. package/lib/feed.d.ts +29 -0
  23. package/lib/feed.d.ts.map +1 -0
  24. package/lib/feed.js +89 -0
  25. package/lib/feed.js.map +1 -0
  26. package/lib/index.d.ts +6 -1
  27. package/lib/index.d.ts.map +1 -1
  28. package/lib/index.js +4 -492
  29. package/lib/index.js.map +1 -1
  30. package/lib/memory-store.d.ts +30 -0
  31. package/lib/memory-store.d.ts.map +1 -0
  32. package/lib/memory-store.js +61 -0
  33. package/lib/memory-store.js.map +1 -0
  34. package/lib/poll.d.ts +37 -0
  35. package/lib/poll.d.ts.map +1 -0
  36. package/lib/poll.js +154 -0
  37. package/lib/poll.js.map +1 -0
  38. package/lib/rss-agent-deps.d.ts +15 -0
  39. package/lib/rss-agent-deps.d.ts.map +1 -0
  40. package/lib/rss-agent-deps.js +10 -0
  41. package/lib/rss-agent-deps.js.map +1 -0
  42. package/package.json +46 -13
  43. package/plugin.js +93 -0
  44. package/schema.json +30 -0
  45. package/src/channel.ts +69 -0
  46. package/src/db-store.ts +59 -0
  47. package/src/feed.ts +106 -0
  48. package/src/index.ts +20 -556
  49. package/src/memory-store.ts +88 -0
  50. package/src/poll.ts +171 -0
  51. package/src/rss-agent-deps.ts +15 -0
  52. /package/{skills/rss/SKILL.md → agent/skills/rss.md} +0 -0
package/README.md CHANGED
@@ -71,7 +71,7 @@ rss:
71
71
  | url | text | Feed 地址 |
72
72
  | feed_title | text | Feed 标题(自动获取) |
73
73
  | adapter_name | text | 适配器名称 |
74
- | bot_id | text | Bot ID |
74
+ | endpoint_id | text | Endpoint ID |
75
75
  | channel_type | text | 会话类型 (group/private) |
76
76
  | channel_id | text | 会话 ID |
77
77
 
@@ -0,0 +1,38 @@
1
+ import { defineAgentTool } from '@zhin.js/agent/tools';
2
+ import { z } from 'zod';
3
+ import { getRssAgentDeps } from '../../src/rss-agent-deps.js';
4
+
5
+ export default defineAgentTool({
6
+ description: '查询当前所有 RSS 订阅',
7
+ inputSchema: z.object({}),
8
+ async execute() {
9
+ const { getSubs } = getRssAgentDeps();
10
+ const Subs = getSubs();
11
+ if (!Subs) return 'RSS 数据库尚未就绪';
12
+
13
+ const all = (await Subs.select()) as Array<{
14
+ channel_type: string;
15
+ channel_id: string;
16
+ feed_title?: string;
17
+ url: string;
18
+ }>;
19
+ if (all.length === 0) return '暂无任何 RSS 订阅';
20
+
21
+ const grouped = new Map<string, typeof all>();
22
+ for (const sub of all) {
23
+ const key = `${sub.channel_type}:${sub.channel_id}`;
24
+ const list = grouped.get(key) || [];
25
+ list.push(sub);
26
+ grouped.set(key, list);
27
+ }
28
+
29
+ const lines: string[] = [];
30
+ for (const [channel, subs] of grouped) {
31
+ lines.push(`[${channel}]`);
32
+ for (const s of subs) {
33
+ lines.push(` - ${s.feed_title || s.url}`);
34
+ }
35
+ }
36
+ return `RSS 订阅总览 (${all.length} 条):\n${lines.join('\n')}`;
37
+ },
38
+ });
@@ -0,0 +1,22 @@
1
+ import { defineAgentTool } from '@zhin.js/agent/tools';
2
+ import { z } from 'zod';
3
+ import { getRssAgentDeps } from '../../src/rss-agent-deps.js';
4
+
5
+ export default defineAgentTool<{ url: string }>({
6
+ description: '预览一个 RSS 源的最新内容',
7
+ inputSchema: z.object({ url: z.string().min(1) }),
8
+ async execute({ url }) {
9
+ if (!url) return '请提供 RSS 源 URL';
10
+ try {
11
+ const { fetchFeed } = getRssAgentDeps();
12
+ const { title, items } = await fetchFeed(url);
13
+ if (items.length === 0) return `${title}: 无内容`;
14
+ return items
15
+ .slice(0, 5)
16
+ .map((item, i) => `${i + 1}. ${item.title}\n ${item.link}`)
17
+ .join('\n');
18
+ } catch (e) {
19
+ return `解析失败: ${(e as Error).message}`;
20
+ }
21
+ },
22
+ });
@@ -0,0 +1,59 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineCommand } from 'zhin.js/command';
3
+ import { extractChannelInfo } from "../../lib/channel.js";
4
+ import { getRssSeen, getRssSubs } from "../../lib/db-store.js";
5
+ import { fetchFeed, resolveRssConfig } from "../../lib/feed.js";
6
+ import { markItemsSeen } from "../../lib/poll.js";
7
+ export default defineCommand({
8
+ description: '订阅一个 RSS/Atom 源',
9
+ params: { url: { type: 'string' } },
10
+ async execute({ params, config, input }) {
11
+ const Subs = getRssSubs();
12
+ if (!Subs)
13
+ return 'RSS 数据库尚未就绪,请稍后重试';
14
+ const url = String(params.url ?? '').trim();
15
+ if (!url)
16
+ return '请提供 RSS 源地址,格式:rss-add <URL>';
17
+ if (!/^https?:\/\//i.test(url))
18
+ return '请提供有效的 HTTP/HTTPS 地址';
19
+ const channel = extractChannelInfo(input);
20
+ const cfg = resolveRssConfig(config);
21
+ const existing = await Subs.select().where({
22
+ url,
23
+ adapter_name: channel.adapterName,
24
+ channel_type: channel.channelType,
25
+ channel_id: channel.channelId,
26
+ });
27
+ if (existing.length > 0)
28
+ return '当前会话已订阅该源,无需重复添加';
29
+ const currentSubs = await Subs.select().where({
30
+ adapter_name: channel.adapterName,
31
+ channel_type: channel.channelType,
32
+ channel_id: channel.channelId,
33
+ });
34
+ if (currentSubs.length >= cfg.maxPerGroup) {
35
+ return `当前会话订阅数已达上限 (${cfg.maxPerGroup}),请先取消一些`;
36
+ }
37
+ let feedTitle = '';
38
+ try {
39
+ const { title, items } = await fetchFeed(url, cfg.timeout);
40
+ feedTitle = title;
41
+ await markItemsSeen(url, items);
42
+ }
43
+ catch (e) {
44
+ return `无法解析该地址: ${e.message}\n请确认是有效的 RSS/Atom 源`;
45
+ }
46
+ await Subs.insert({
47
+ url,
48
+ feed_title: feedTitle,
49
+ adapter_name: channel.adapterName,
50
+ endpoint_id: channel.endpointKey,
51
+ channel_type: channel.channelType,
52
+ channel_id: channel.channelId,
53
+ creator_id: channel.senderId,
54
+ creator_name: channel.senderName,
55
+ created_at: new Date().toISOString(),
56
+ });
57
+ return `订阅成功!\n源: ${feedTitle || url}\n新内容将在轮询时推送到本会话(需 OutboundHost)`;
58
+ },
59
+ });
@@ -0,0 +1,61 @@
1
+ import { defineCommand } from 'zhin.js/command';
2
+ import { extractChannelInfo } from '../../src/channel.js';
3
+ import { getRssSeen, getRssSubs } from '../../src/db-store.js';
4
+ import { fetchFeed, resolveRssConfig, type RssConfig } from '../../src/feed.js';
5
+ import { markItemsSeen } from '../../src/poll.js';
6
+
7
+ export default defineCommand<RssConfig>({
8
+ description: '订阅一个 RSS/Atom 源',
9
+ params: { url: { type: 'string' } },
10
+ async execute({ params, config, input }) {
11
+ const Subs = getRssSubs();
12
+ if (!Subs) return 'RSS 数据库尚未就绪,请稍后重试';
13
+
14
+ const url = String(params.url ?? '').trim();
15
+ if (!url) return '请提供 RSS 源地址,格式:rss-add <URL>';
16
+ if (!/^https?:\/\//i.test(url)) return '请提供有效的 HTTP/HTTPS 地址';
17
+
18
+ const channel = extractChannelInfo(input);
19
+ const cfg = resolveRssConfig(config);
20
+
21
+ const existing = await Subs.select().where({
22
+ url,
23
+ adapter_name: channel.adapterName,
24
+ channel_type: channel.channelType,
25
+ channel_id: channel.channelId,
26
+ });
27
+ if (existing.length > 0) return '当前会话已订阅该源,无需重复添加';
28
+
29
+ const currentSubs = await Subs.select().where({
30
+ adapter_name: channel.adapterName,
31
+ channel_type: channel.channelType,
32
+ channel_id: channel.channelId,
33
+ });
34
+ if (currentSubs.length >= cfg.maxPerGroup) {
35
+ return `当前会话订阅数已达上限 (${cfg.maxPerGroup}),请先取消一些`;
36
+ }
37
+
38
+ let feedTitle = '';
39
+ try {
40
+ const { title, items } = await fetchFeed(url, cfg.timeout);
41
+ feedTitle = title;
42
+ await markItemsSeen(url, items);
43
+ } catch (e) {
44
+ return `无法解析该地址: ${(e as Error).message}\n请确认是有效的 RSS/Atom 源`;
45
+ }
46
+
47
+ await Subs.insert({
48
+ url,
49
+ feed_title: feedTitle,
50
+ adapter_name: channel.adapterName,
51
+ endpoint_id: channel.endpointKey,
52
+ channel_type: channel.channelType,
53
+ channel_id: channel.channelId,
54
+ creator_id: channel.senderId,
55
+ creator_name: channel.senderName,
56
+ created_at: new Date().toISOString(),
57
+ });
58
+
59
+ return `订阅成功!\n源: ${feedTitle || url}\n新内容将在轮询时推送到本会话(需 OutboundHost)`;
60
+ },
61
+ });
@@ -0,0 +1,42 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineCommand } from 'zhin.js/command';
3
+ import { extractChannelInfo } from "../../lib/channel.js";
4
+ import { getRssSubs } from "../../lib/db-store.js";
5
+ import { resolveRssConfig } from "../../lib/feed.js";
6
+ import { checkSubscriptions } from "../../lib/poll.js";
7
+ export default defineCommand({
8
+ description: '手动触发检查 RSS 更新',
9
+ params: { url: { type: 'string', default: '' } },
10
+ async execute({ params, config, input }) {
11
+ const Subs = getRssSubs();
12
+ if (!Subs)
13
+ return 'RSS 数据库尚未就绪';
14
+ const channel = extractChannelInfo(input);
15
+ const cfg = resolveRssConfig(config);
16
+ const targetUrl = String(params.url ?? '').trim();
17
+ let urls;
18
+ if (targetUrl) {
19
+ const rows = await Subs.select().where({
20
+ url: targetUrl,
21
+ adapter_name: channel.adapterName,
22
+ channel_type: channel.channelType,
23
+ channel_id: channel.channelId,
24
+ });
25
+ if (rows.length === 0)
26
+ return '当前会话未订阅该源';
27
+ urls = [targetUrl];
28
+ }
29
+ else {
30
+ const rows = await Subs.select().where({
31
+ adapter_name: channel.adapterName,
32
+ channel_type: channel.channelType,
33
+ channel_id: channel.channelId,
34
+ });
35
+ if (rows.length === 0)
36
+ return '当前会话没有任何订阅';
37
+ urls = [...new Set(rows.map((r) => String(r.url ?? '')).filter(Boolean))];
38
+ }
39
+ const result = await checkSubscriptions({ urls, config: cfg });
40
+ return result.text;
41
+ },
42
+ });
@@ -0,0 +1,41 @@
1
+ import { defineCommand } from 'zhin.js/command';
2
+ import { extractChannelInfo } from '../../src/channel.js';
3
+ import { getRssSubs } from '../../src/db-store.js';
4
+ import { resolveRssConfig, type RssConfig } from '../../src/feed.js';
5
+ import { checkSubscriptions } from '../../src/poll.js';
6
+
7
+ export default defineCommand<RssConfig>({
8
+ description: '手动触发检查 RSS 更新',
9
+ params: { url: { type: 'string', default: '' } },
10
+ async execute({ params, config, input }) {
11
+ const Subs = getRssSubs();
12
+ if (!Subs) return 'RSS 数据库尚未就绪';
13
+
14
+ const channel = extractChannelInfo(input);
15
+ const cfg = resolveRssConfig(config);
16
+ const targetUrl = String(params.url ?? '').trim();
17
+
18
+ let urls: string[];
19
+ if (targetUrl) {
20
+ const rows = await Subs.select().where({
21
+ url: targetUrl,
22
+ adapter_name: channel.adapterName,
23
+ channel_type: channel.channelType,
24
+ channel_id: channel.channelId,
25
+ });
26
+ if (rows.length === 0) return '当前会话未订阅该源';
27
+ urls = [targetUrl];
28
+ } else {
29
+ const rows = await Subs.select().where({
30
+ adapter_name: channel.adapterName,
31
+ channel_type: channel.channelType,
32
+ channel_id: channel.channelId,
33
+ });
34
+ if (rows.length === 0) return '当前会话没有任何订阅';
35
+ urls = [...new Set(rows.map((r) => String(r.url ?? '')).filter(Boolean))];
36
+ }
37
+
38
+ const result = await checkSubscriptions({ urls, config: cfg });
39
+ return result.text;
40
+ },
41
+ });
@@ -0,0 +1,28 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineCommand } from 'zhin.js/command';
3
+ import { extractChannelInfo } from "../lib/channel.js";
4
+ import { getRssSubs } from "../lib/db-store.js";
5
+ import { resolveRssConfig } from "../lib/feed.js";
6
+ export default defineCommand({
7
+ description: '查看当前会话的所有 RSS 订阅',
8
+ async execute({ config, input }) {
9
+ const Subs = getRssSubs();
10
+ if (!Subs)
11
+ return 'RSS 数据库尚未就绪';
12
+ const channel = extractChannelInfo(input);
13
+ const cfg = resolveRssConfig(config);
14
+ const rows = await Subs.select().where({
15
+ adapter_name: channel.adapterName,
16
+ channel_type: channel.channelType,
17
+ channel_id: channel.channelId,
18
+ });
19
+ if (rows.length === 0) {
20
+ return '当前会话没有订阅任何 RSS 源\n使用 rss-add <URL> 添加订阅';
21
+ }
22
+ const lines = rows.map((r, i) => {
23
+ const title = String(r.feed_title || '(未知标题)');
24
+ return `${i + 1}. ${title}\n ${String(r.url ?? '')}`;
25
+ });
26
+ return `当前订阅 (${rows.length}/${cfg.maxPerGroup}):\n${lines.join('\n')}`;
27
+ },
28
+ });
@@ -0,0 +1,31 @@
1
+ import { defineCommand } from 'zhin.js/command';
2
+ import { extractChannelInfo } from '../src/channel.js';
3
+ import { getRssSubs } from '../src/db-store.js';
4
+ import { resolveRssConfig, type RssConfig } from '../src/feed.js';
5
+
6
+ export default defineCommand<RssConfig>({
7
+ description: '查看当前会话的所有 RSS 订阅',
8
+ async execute({ config, input }) {
9
+ const Subs = getRssSubs();
10
+ if (!Subs) return 'RSS 数据库尚未就绪';
11
+
12
+ const channel = extractChannelInfo(input);
13
+ const cfg = resolveRssConfig(config);
14
+ const rows = await Subs.select().where({
15
+ adapter_name: channel.adapterName,
16
+ channel_type: channel.channelType,
17
+ channel_id: channel.channelId,
18
+ });
19
+
20
+ if (rows.length === 0) {
21
+ return '当前会话没有订阅任何 RSS 源\n使用 rss-add <URL> 添加订阅';
22
+ }
23
+
24
+ const lines = rows.map((r, i) => {
25
+ const title = String(r.feed_title || '(未知标题)');
26
+ return `${i + 1}. ${title}\n ${String(r.url ?? '')}`;
27
+ });
28
+
29
+ return `当前订阅 (${rows.length}/${cfg.maxPerGroup}):\n${lines.join('\n')}`;
30
+ },
31
+ });
@@ -0,0 +1,22 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineCommand } from 'zhin.js/command';
3
+ import { fetchFeed, formatFeedPreview, resolveRssConfig } from "../../lib/feed.js";
4
+ export default defineCommand({
5
+ description: '预览一个 RSS 源的最新内容(不订阅)',
6
+ params: { url: { type: 'string' } },
7
+ async execute({ params, config }) {
8
+ const url = String(params.url ?? '').trim();
9
+ if (!url)
10
+ return '请提供 RSS 源地址';
11
+ if (!/^https?:\/\//i.test(url))
12
+ return '请提供有效的 HTTP/HTTPS 地址';
13
+ const cfg = resolveRssConfig(config);
14
+ try {
15
+ const { title, items } = await fetchFeed(url, cfg.timeout);
16
+ return formatFeedPreview(title, items);
17
+ }
18
+ catch (e) {
19
+ return `解析失败: ${e.message}\n请确认是有效的 RSS/Atom 地址`;
20
+ }
21
+ },
22
+ });
@@ -0,0 +1,19 @@
1
+ import { defineCommand } from 'zhin.js/command';
2
+ import { fetchFeed, formatFeedPreview, resolveRssConfig, type RssConfig } from '../../src/feed.js';
3
+
4
+ export default defineCommand<RssConfig>({
5
+ description: '预览一个 RSS 源的最新内容(不订阅)',
6
+ params: { url: { type: 'string' } },
7
+ async execute({ params, config }) {
8
+ const url = String(params.url ?? '').trim();
9
+ if (!url) return '请提供 RSS 源地址';
10
+ if (!/^https?:\/\//i.test(url)) return '请提供有效的 HTTP/HTTPS 地址';
11
+ const cfg = resolveRssConfig(config);
12
+ try {
13
+ const { title, items } = await fetchFeed(url, cfg.timeout);
14
+ return formatFeedPreview(title, items);
15
+ } catch (e) {
16
+ return `解析失败: ${(e as Error).message}\n请确认是有效的 RSS/Atom 地址`;
17
+ }
18
+ },
19
+ });
@@ -0,0 +1,34 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineCommand } from 'zhin.js/command';
3
+ import { extractChannelInfo } from "../../lib/channel.js";
4
+ import { getRssSubs } from "../../lib/db-store.js";
5
+ export default defineCommand({
6
+ description: '取消一个 RSS 订阅',
7
+ params: { url: { type: 'string' } },
8
+ async execute({ params, input }) {
9
+ const Subs = getRssSubs();
10
+ if (!Subs)
11
+ return 'RSS 数据库尚未就绪';
12
+ const url = String(params.url ?? '').trim();
13
+ if (!url)
14
+ return '请提供要取消的 RSS 源地址';
15
+ const channel = extractChannelInfo(input);
16
+ const rows = await Subs.select().where({
17
+ url,
18
+ adapter_name: channel.adapterName,
19
+ channel_type: channel.channelType,
20
+ channel_id: channel.channelId,
21
+ });
22
+ if (rows.length === 0)
23
+ return '未找到该订阅';
24
+ const row = rows[0];
25
+ // 表结构没有 id 列,删除必须按业务键匹配(与上面的 select 条件一致)。
26
+ await Subs.delete().where({
27
+ url,
28
+ adapter_name: channel.adapterName,
29
+ channel_type: channel.channelType,
30
+ channel_id: channel.channelId,
31
+ });
32
+ return `已取消订阅: ${String(row.feed_title || url)}`;
33
+ },
34
+ });
@@ -0,0 +1,36 @@
1
+ import { defineCommand } from 'zhin.js/command';
2
+ import { extractChannelInfo } from '../../src/channel.js';
3
+ import { getRssSubs } from '../../src/db-store.js';
4
+ import type { RssConfig } from '../../src/feed.js';
5
+
6
+ export default defineCommand<RssConfig>({
7
+ description: '取消一个 RSS 订阅',
8
+ params: { url: { type: 'string' } },
9
+ async execute({ params, input }) {
10
+ const Subs = getRssSubs();
11
+ if (!Subs) return 'RSS 数据库尚未就绪';
12
+
13
+ const url = String(params.url ?? '').trim();
14
+ if (!url) return '请提供要取消的 RSS 源地址';
15
+
16
+ const channel = extractChannelInfo(input);
17
+ const rows = await Subs.select().where({
18
+ url,
19
+ adapter_name: channel.adapterName,
20
+ channel_type: channel.channelType,
21
+ channel_id: channel.channelId,
22
+ });
23
+
24
+ if (rows.length === 0) return '未找到该订阅';
25
+
26
+ const row = rows[0]!;
27
+ // 表结构没有 id 列,删除必须按业务键匹配(与上面的 select 条件一致)。
28
+ await Subs.delete().where({
29
+ url,
30
+ adapter_name: channel.adapterName,
31
+ channel_type: channel.channelType,
32
+ channel_id: channel.channelId,
33
+ });
34
+ return `已取消订阅: ${String(row.feed_title || url)}`;
35
+ },
36
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Extract subscription channel keys from Runtime Message (`input`).
3
+ * Gaps vs legacy: no `$channel` / `$group` — use `conversation` / `metadata`.
4
+ */
5
+ export interface ChannelInfo {
6
+ adapterName: string;
7
+ endpointKey: string;
8
+ channelType: string;
9
+ channelId: string;
10
+ senderId: string;
11
+ senderName: string;
12
+ }
13
+ /** Smoke / agent / execute()-without-dispatch fallback. */
14
+ export declare const SMOKE_CHANNEL: ChannelInfo;
15
+ export declare function extractChannelInfo(input: unknown): ChannelInfo;
16
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,2DAA2D;AAC3D,eAAO,MAAM,aAAa,EAAE,WAO3B,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CA4C9D"}
package/lib/channel.js ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Extract subscription channel keys from Runtime Message (`input`).
3
+ * Gaps vs legacy: no `$channel` / `$group` — use `conversation` / `metadata`.
4
+ */
5
+ /** Smoke / agent / execute()-without-dispatch fallback. */
6
+ export const SMOKE_CHANNEL = {
7
+ adapterName: 'smoke',
8
+ endpointKey: 'local',
9
+ channelType: 'private',
10
+ channelId: 'global',
11
+ senderId: '',
12
+ senderName: '',
13
+ };
14
+ export function extractChannelInfo(input) {
15
+ if (!input || typeof input !== 'object')
16
+ return { ...SMOKE_CHANNEL };
17
+ const msg = input;
18
+ const meta = msg.metadata ?? {};
19
+ const conversation = msg.conversation;
20
+ const channelType = String(meta.channelType ?? meta.type ?? meta.channel_type ?? conversation?.kind ?? 'private');
21
+ const channelId = String(meta.channelId ?? meta.channel_id ?? conversation?.id ?? SMOKE_CHANNEL.channelId);
22
+ const endpointCapabilityId = typeof conversation?.endpoint?.id === 'string'
23
+ ? conversation.endpoint.id
24
+ : undefined;
25
+ const adapterName = String((endpointCapabilityId?.split('\0').pop() ?? endpointCapabilityId)?.split('~')[0]
26
+ ?? meta.adapter
27
+ ?? SMOKE_CHANNEL.adapterName);
28
+ const endpointKey = String(meta.endpoint ?? meta.endpointKey ?? meta.endpoint_id ?? adapterName);
29
+ const senderId = String(msg.sender ?? meta.senderId ?? '');
30
+ const senderName = String(meta.senderName ?? meta.name ?? '');
31
+ if (!channelId)
32
+ return { ...SMOKE_CHANNEL, adapterName, endpointKey, senderId, senderName };
33
+ return {
34
+ adapterName,
35
+ endpointKey,
36
+ channelType,
37
+ channelId,
38
+ senderId,
39
+ senderName,
40
+ };
41
+ }
42
+ //# sourceMappingURL=channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.js","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,2DAA2D;AAC3D,MAAM,CAAC,MAAM,aAAa,GAAgB;IACxC,WAAW,EAAE,OAAO;IACpB,WAAW,EAAE,OAAO;IACpB,WAAW,EAAE,SAAS;IACtB,SAAS,EAAE,QAAQ;IACnB,QAAQ,EAAE,EAAE;IACZ,UAAU,EAAE,EAAE;CACf,CAAC;AAEF,MAAM,UAAU,kBAAkB,CAAC,KAAc;IAC/C,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,EAAE,GAAG,aAAa,EAAE,CAAC;IAErE,MAAM,GAAG,GAAG,KAQX,CAAC;IACF,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC;IAChC,MAAM,YAAY,GAAG,GAAG,CAAC,YAAY,CAAC;IACtC,MAAM,WAAW,GAAG,MAAM,CACxB,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,YAAY,IAAI,YAAY,EAAE,IAAI,IAAI,SAAS,CACtF,CAAC;IACF,MAAM,SAAS,GAAG,MAAM,CACtB,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,UAAU,IAAI,YAAY,EAAE,EAAE,IAAI,aAAa,CAAC,SAAS,CACjF,CAAC;IACF,MAAM,oBAAoB,GAAG,OAAO,YAAY,EAAE,QAAQ,EAAE,EAAE,KAAK,QAAQ;QACzE,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE;QAC1B,CAAC,CAAC,SAAS,CAAC;IACd,MAAM,WAAW,GAAG,MAAM,CACxB,CAAC,oBAAoB,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,IAAI,oBAAoB,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;WAC7E,IAAI,CAAC,OAAO;WACZ,aAAa,CAAC,WAAW,CAC7B,CAAC;IACF,MAAM,WAAW,GAAG,MAAM,CACxB,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,IAAI,WAAW,CACrE,CAAC;IACF,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,IAAI,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;IAC3D,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAE9D,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,GAAG,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;IAE5F,OAAO;QACL,WAAW;QACX,WAAW;QACX,WAAW;QACX,SAAS;QACT,QAAQ;QACR,UAAU;KACX,CAAC;AACJ,CAAC"}
@@ -0,0 +1,16 @@
1
+ export { createInMemoryRssDb, RSS_SEEN_TABLE, RSS_SUBS_TABLE, type RssMemoryDb, type RssModel, } from './memory-store.js';
2
+ import { type Dispose, type GenerationStoreContext } from 'zhin.js';
3
+ import { type RssMemoryDb, type RssModel } from './memory-store.js';
4
+ /**
5
+ * Generation-owned database binding used by Plugin Runtime setup().
6
+ * Auto-unregisters when the generation lifecycle disposes, so no stale
7
+ * reference to the previous generation's host survives a reload.
8
+ */
9
+ export declare function provideRssDb(context: GenerationStoreContext, db: RssMemoryDb): Dispose;
10
+ export declare function ensureRssMemoryDb(): RssMemoryDb;
11
+ export declare function getRssDb(): RssMemoryDb | null;
12
+ export declare function setRssDb(db: RssMemoryDb | null): void;
13
+ export declare function resetRssDb(): void;
14
+ export declare function getRssSubs(): RssModel | null;
15
+ export declare function getRssSeen(): RssModel | null;
16
+ //# sourceMappingURL=db-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-store.d.ts","sourceRoot":"","sources":["../src/db-store.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,QAAQ,GACd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAyB,KAAK,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAC3F,OAAO,EAIL,KAAK,WAAW,EAChB,KAAK,QAAQ,EACd,MAAM,mBAAmB,CAAC;AAO3B;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,sBAAsB,EAAE,EAAE,EAAE,WAAW,GAAG,OAAO,CAEtF;AAED,wBAAgB,iBAAiB,IAAI,WAAW,CAK/C;AAED,wBAAgB,QAAQ,IAAI,WAAW,GAAG,IAAI,CAE7C;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAErD;AAED,wBAAgB,UAAU,IAAI,IAAI,CAIjC;AAED,wBAAgB,UAAU,IAAI,QAAQ,GAAG,IAAI,CAE5C;AAED,wBAAgB,UAAU,IAAI,QAAQ,GAAG,IAAI,CAE5C"}
@@ -0,0 +1,40 @@
1
+ export { createInMemoryRssDb, RSS_SEEN_TABLE, RSS_SUBS_TABLE, } from './memory-store.js';
2
+ import { createGenerationStore } from 'zhin.js';
3
+ import { createInMemoryRssDb, RSS_SEEN_TABLE, RSS_SUBS_TABLE, } from './memory-store.js';
4
+ const rssDbStore = createGenerationStore('rss/db');
5
+ let _db = null;
6
+ let _memoryDb = null;
7
+ /**
8
+ * Generation-owned database binding used by Plugin Runtime setup().
9
+ * Auto-unregisters when the generation lifecycle disposes, so no stale
10
+ * reference to the previous generation's host survives a reload.
11
+ */
12
+ export function provideRssDb(context, db) {
13
+ return rssDbStore.provide(context, db);
14
+ }
15
+ export function ensureRssMemoryDb() {
16
+ const provided = rssDbStore.tryUse() ?? _db;
17
+ if (provided)
18
+ return provided;
19
+ if (!_memoryDb)
20
+ _memoryDb = createInMemoryRssDb();
21
+ return _memoryDb;
22
+ }
23
+ export function getRssDb() {
24
+ return ensureRssMemoryDb();
25
+ }
26
+ export function setRssDb(db) {
27
+ _db = db;
28
+ }
29
+ export function resetRssDb() {
30
+ rssDbStore.clear();
31
+ _db = null;
32
+ _memoryDb = null;
33
+ }
34
+ export function getRssSubs() {
35
+ return ensureRssMemoryDb().models.get(RSS_SUBS_TABLE) ?? null;
36
+ }
37
+ export function getRssSeen() {
38
+ return ensureRssMemoryDb().models.get(RSS_SEEN_TABLE) ?? null;
39
+ }
40
+ //# sourceMappingURL=db-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-store.js","sourceRoot":"","sources":["../src/db-store.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,cAAc,GAGf,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,qBAAqB,EAA6C,MAAM,SAAS,CAAC;AAC3F,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,cAAc,GAGf,MAAM,mBAAmB,CAAC;AAE3B,MAAM,UAAU,GAAG,qBAAqB,CAAc,QAAQ,CAAC,CAAC;AAEhE,IAAI,GAAG,GAAuB,IAAI,CAAC;AACnC,IAAI,SAAS,GAAuB,IAAI,CAAC;AAEzC;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,OAA+B,EAAE,EAAe;IAC3E,OAAO,UAAU,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,EAAE,IAAI,GAAG,CAAC;IAC5C,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,IAAI,CAAC,SAAS;QAAE,SAAS,GAAG,mBAAmB,EAAE,CAAC;IAClD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,QAAQ;IACtB,OAAO,iBAAiB,EAAE,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,EAAsB;IAC7C,GAAG,GAAG,EAAE,CAAC;AACX,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,UAAU,CAAC,KAAK,EAAE,CAAC;IACnB,GAAG,GAAG,IAAI,CAAC;IACX,SAAS,GAAG,IAAI,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,iBAAiB,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,IAAI,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,iBAAiB,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,IAAI,CAAC;AAChE,CAAC"}
package/lib/feed.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ export interface FeedItem {
2
+ title: string;
3
+ link: string;
4
+ date: string;
5
+ summary: string;
6
+ guid: string;
7
+ }
8
+ export interface RssConfig {
9
+ readonly pollCron: string;
10
+ readonly maxPerGroup: number;
11
+ readonly maxItems: number;
12
+ readonly timeout: number;
13
+ }
14
+ export declare const DEFAULT_RSS_CONFIG: RssConfig;
15
+ export declare function resolveRssConfig(raw: Partial<RssConfig> | undefined): RssConfig;
16
+ /**
17
+ * 剔除 HTML 标签与 `&nbsp;` 实体并折叠空白。
18
+ * 线性单遍扫描实现(手写状态机),语义等价于原先的
19
+ * `/<[^>]*>/g` → `/&nbsp;/g` → `/\s+/g` 三步 replace,但避免:
20
+ * - js/polynomial-redos:大量 `<` 无闭合时正则逐位重扫的二次方回溯;
21
+ * - js/incomplete-multi-character-sanitization:多趟 replace 的残留问题。
22
+ */
23
+ export declare function stripHtml(html: string): string;
24
+ export declare function fetchFeed(url: string, timeout?: number): Promise<{
25
+ title: string;
26
+ items: FeedItem[];
27
+ }>;
28
+ export declare function formatFeedPreview(title: string, items: FeedItem[]): string;
29
+ //# sourceMappingURL=feed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feed.d.ts","sourceRoot":"","sources":["../src/feed.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,kBAAkB,EAAE,SAKhC,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG,SAAS,GAAG,SAAS,CAE/E;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgC9C;AAED,wBAAsB,SAAS,CAC7B,GAAG,EAAE,MAAM,EACX,OAAO,SAA6B,GACnC,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,QAAQ,EAAE,CAAA;CAAE,CAAC,CAiB/C;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,CAW1E"}