@zhin.js/plugin-rss 3.0.2 → 3.0.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 (51) hide show
  1. package/agent/tools/rss_list_subscriptions.ts +2 -2
  2. package/agent/tools/rss_preview_feed.ts +2 -2
  3. package/commands/rss-add/[url:string].ts +60 -0
  4. package/commands/rss-check/[url:string=].ts +40 -0
  5. package/commands/rss-list.ts +31 -0
  6. package/commands/rss-preview/[url:string].ts +18 -0
  7. package/commands/rss-remove/[url:string].ts +29 -0
  8. package/lib/channel.d.ts +16 -0
  9. package/lib/channel.d.ts.map +1 -0
  10. package/lib/channel.js +36 -0
  11. package/lib/channel.js.map +1 -0
  12. package/lib/db-store.d.ts +9 -0
  13. package/lib/db-store.d.ts.map +1 -0
  14. package/lib/db-store.js +24 -0
  15. package/lib/db-store.js.map +1 -0
  16. package/lib/feed.d.ts +29 -0
  17. package/lib/feed.d.ts.map +1 -0
  18. package/lib/feed.js +89 -0
  19. package/lib/feed.js.map +1 -0
  20. package/lib/index.d.ts +7 -0
  21. package/lib/index.d.ts.map +1 -0
  22. package/lib/index.js +5 -0
  23. package/lib/index.js.map +1 -0
  24. package/lib/memory-store.d.ts +28 -0
  25. package/lib/memory-store.d.ts.map +1 -0
  26. package/lib/memory-store.js +57 -0
  27. package/lib/memory-store.js.map +1 -0
  28. package/lib/poll.d.ts +37 -0
  29. package/lib/poll.d.ts.map +1 -0
  30. package/lib/poll.js +152 -0
  31. package/lib/poll.js.map +1 -0
  32. package/lib/rss-agent-deps.d.ts +15 -0
  33. package/lib/rss-agent-deps.d.ts.map +1 -0
  34. package/lib/rss-agent-deps.js.map +1 -0
  35. package/package.json +37 -21
  36. package/plugin.ts +103 -0
  37. package/schema.json +30 -0
  38. package/src/channel.ts +58 -0
  39. package/src/db-store.ts +42 -0
  40. package/src/feed.ts +106 -0
  41. package/src/index.ts +19 -506
  42. package/src/memory-store.ts +86 -0
  43. package/src/poll.ts +169 -0
  44. package/lib/agent/tools/rss_list_subscriptions.js +0 -32
  45. package/lib/agent/tools/rss_list_subscriptions.js.map +0 -1
  46. package/lib/agent/tools/rss_preview_feed.js +0 -25
  47. package/lib/agent/tools/rss_preview_feed.js.map +0 -1
  48. package/lib/src/index.js +0 -447
  49. package/lib/src/index.js.map +0 -1
  50. package/lib/src/rss-agent-deps.js.map +0 -1
  51. /package/lib/{src/rss-agent-deps.js → rss-agent-deps.js} +0 -0
@@ -1,8 +1,8 @@
1
- import { defineTool } from '@zhin.js/agent/tools';
1
+ import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
3
  import { getRssAgentDeps } from '../../src/rss-agent-deps.js';
4
4
 
5
- export default defineTool({
5
+ export default defineAgentTool({
6
6
  description: '查询当前所有 RSS 订阅',
7
7
  inputSchema: z.object({}),
8
8
  async execute() {
@@ -1,8 +1,8 @@
1
- import { defineTool } from '@zhin.js/agent/tools';
1
+ import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
3
  import { getRssAgentDeps } from '../../src/rss-agent-deps.js';
4
4
 
5
- export default defineTool<{ url: string }>({
5
+ export default defineAgentTool<{ url: string }>({
6
6
  description: '预览一个 RSS 源的最新内容',
7
7
  inputSchema: z.object({ url: z.string().min(1) }),
8
8
  async execute({ url }) {
@@ -0,0 +1,60 @@
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
+ async execute({ params, config, input }) {
10
+ const Subs = getRssSubs();
11
+ if (!Subs) return 'RSS 数据库尚未就绪,请稍后重试';
12
+
13
+ const url = String(params.url ?? '').trim();
14
+ if (!url) return '请提供 RSS 源地址,格式:rss-add <URL>';
15
+ if (!/^https?:\/\//i.test(url)) return '请提供有效的 HTTP/HTTPS 地址';
16
+
17
+ const channel = extractChannelInfo(input);
18
+ const cfg = resolveRssConfig(config);
19
+
20
+ const existing = await Subs.select().where({
21
+ url,
22
+ adapter_name: channel.adapterName,
23
+ channel_type: channel.channelType,
24
+ channel_id: channel.channelId,
25
+ });
26
+ if (existing.length > 0) return '当前会话已订阅该源,无需重复添加';
27
+
28
+ const currentSubs = await Subs.select().where({
29
+ adapter_name: channel.adapterName,
30
+ channel_type: channel.channelType,
31
+ channel_id: channel.channelId,
32
+ });
33
+ if (currentSubs.length >= cfg.maxPerGroup) {
34
+ return `当前会话订阅数已达上限 (${cfg.maxPerGroup}),请先取消一些`;
35
+ }
36
+
37
+ let feedTitle = '';
38
+ try {
39
+ const { title, items } = await fetchFeed(url, cfg.timeout);
40
+ feedTitle = title;
41
+ await markItemsSeen(url, items);
42
+ } catch (e) {
43
+ return `无法解析该地址: ${(e as Error).message}\n请确认是有效的 RSS/Atom 源`;
44
+ }
45
+
46
+ await Subs.insert({
47
+ url,
48
+ feed_title: feedTitle,
49
+ adapter_name: channel.adapterName,
50
+ endpoint_id: channel.endpointId,
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
+
58
+ return `订阅成功!\n源: ${feedTitle || url}\n新内容将在轮询时推送到本会话(需 OutboundHost)`;
59
+ },
60
+ });
@@ -0,0 +1,40 @@
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
+ async execute({ params, config, input }) {
10
+ const Subs = getRssSubs();
11
+ if (!Subs) return 'RSS 数据库尚未就绪';
12
+
13
+ const channel = extractChannelInfo(input);
14
+ const cfg = resolveRssConfig(config);
15
+ const targetUrl = String(params.url ?? '').trim();
16
+
17
+ let urls: string[];
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) return '当前会话未订阅该源';
26
+ urls = [targetUrl];
27
+ } else {
28
+ const rows = await Subs.select().where({
29
+ adapter_name: channel.adapterName,
30
+ channel_type: channel.channelType,
31
+ channel_id: channel.channelId,
32
+ });
33
+ if (rows.length === 0) return '当前会话没有任何订阅';
34
+ urls = [...new Set(rows.map((r) => String(r.url ?? '')).filter(Boolean))];
35
+ }
36
+
37
+ const result = await checkSubscriptions({ urls, config: cfg });
38
+ return result.text;
39
+ },
40
+ });
@@ -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,18 @@
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
+ async execute({ params, config }) {
7
+ const url = String(params.url ?? '').trim();
8
+ if (!url) return '请提供 RSS 源地址';
9
+ if (!/^https?:\/\//i.test(url)) return '请提供有效的 HTTP/HTTPS 地址';
10
+ const cfg = resolveRssConfig(config);
11
+ try {
12
+ const { title, items } = await fetchFeed(url, cfg.timeout);
13
+ return formatFeedPreview(title, items);
14
+ } catch (e) {
15
+ return `解析失败: ${(e as Error).message}\n请确认是有效的 RSS/Atom 地址`;
16
+ }
17
+ },
18
+ });
@@ -0,0 +1,29 @@
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
+ async execute({ params, input }) {
9
+ const Subs = getRssSubs();
10
+ if (!Subs) return 'RSS 数据库尚未就绪';
11
+
12
+ const url = String(params.url ?? '').trim();
13
+ if (!url) return '请提供要取消的 RSS 源地址';
14
+
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
+
23
+ if (rows.length === 0) return '未找到该订阅';
24
+
25
+ const row = rows[0]!;
26
+ await Subs.delete().where({ id: row.id });
27
+ return `已取消订阅: ${String(row.feed_title || url)}`;
28
+ },
29
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Extract subscription channel keys from Runtime Message (`input`).
3
+ * Gaps vs legacy: no `$channel` / `$group` — use `adapter` / `target` / `metadata`.
4
+ */
5
+ export interface ChannelInfo {
6
+ adapterName: string;
7
+ endpointId: 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,UAAU,EAAE,MAAM,CAAC;IACnB,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,CAiC9D"}
package/lib/channel.js ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Extract subscription channel keys from Runtime Message (`input`).
3
+ * Gaps vs legacy: no `$channel` / `$group` — use `adapter` / `target` / `metadata`.
4
+ */
5
+ /** Smoke / agent / execute()-without-dispatch fallback. */
6
+ export const SMOKE_CHANNEL = {
7
+ adapterName: 'smoke',
8
+ endpointId: '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 channelType = String(meta.channelType ?? meta.type ?? meta.channel_type ?? 'private');
20
+ const channelId = String(meta.channelId ?? meta.channel_id ?? msg.target ?? SMOKE_CHANNEL.channelId);
21
+ const adapterName = String(msg.adapter ?? meta.adapter ?? SMOKE_CHANNEL.adapterName);
22
+ const endpointId = String(meta.endpoint ?? meta.endpointId ?? meta.endpoint_id ?? adapterName);
23
+ const senderId = String(msg.sender ?? meta.senderId ?? '');
24
+ const senderName = String(meta.senderName ?? meta.name ?? '');
25
+ if (!channelId)
26
+ return { ...SMOKE_CHANNEL, adapterName, endpointId, senderId, senderName };
27
+ return {
28
+ adapterName,
29
+ endpointId,
30
+ channelType,
31
+ channelId,
32
+ senderId,
33
+ senderName,
34
+ };
35
+ }
36
+ //# 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,UAAU,EAAE,OAAO;IACnB,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,KAKX,CAAC;IACF,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC;IAChC,MAAM,WAAW,GAAG,MAAM,CACxB,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,YAAY,IAAI,SAAS,CAChE,CAAC;IACF,MAAM,SAAS,GAAG,MAAM,CACtB,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,UAAU,IAAI,GAAG,CAAC,MAAM,IAAI,aAAa,CAAC,SAAS,CAC3E,CAAC;IACF,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,IAAI,aAAa,CAAC,WAAW,CAAC,CAAC;IACrF,MAAM,UAAU,GAAG,MAAM,CACvB,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,WAAW,IAAI,WAAW,CACpE,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,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;IAE3F,OAAO;QACL,WAAW;QACX,UAAU;QACV,WAAW;QACX,SAAS;QACT,QAAQ;QACR,UAAU;KACX,CAAC;AACJ,CAAC"}
@@ -0,0 +1,9 @@
1
+ export { createInMemoryRssDb, RSS_SEEN_TABLE, RSS_SUBS_TABLE, type RssMemoryDb, type RssModel, } from './memory-store.js';
2
+ import { type RssMemoryDb, type RssModel } from './memory-store.js';
3
+ export declare function ensureRssMemoryDb(): RssMemoryDb;
4
+ export declare function getRssDb(): RssMemoryDb | null;
5
+ export declare function setRssDb(db: RssMemoryDb | null): void;
6
+ export declare function resetRssDb(): void;
7
+ export declare function getRssSubs(): RssModel | null;
8
+ export declare function getRssSeen(): RssModel | null;
9
+ //# 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,EAIL,KAAK,WAAW,EAChB,KAAK,QAAQ,EACd,MAAM,mBAAmB,CAAC;AAI3B,wBAAgB,iBAAiB,IAAI,WAAW,CAG/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,CAEjC;AAED,wBAAgB,UAAU,IAAI,QAAQ,GAAG,IAAI,CAE5C;AAED,wBAAgB,UAAU,IAAI,QAAQ,GAAG,IAAI,CAE5C"}
@@ -0,0 +1,24 @@
1
+ export { createInMemoryRssDb, RSS_SEEN_TABLE, RSS_SUBS_TABLE, } from './memory-store.js';
2
+ import { createInMemoryRssDb, RSS_SEEN_TABLE, RSS_SUBS_TABLE, } from './memory-store.js';
3
+ let _db = null;
4
+ export function ensureRssMemoryDb() {
5
+ if (!_db)
6
+ _db = createInMemoryRssDb();
7
+ return _db;
8
+ }
9
+ export function getRssDb() {
10
+ return ensureRssMemoryDb();
11
+ }
12
+ export function setRssDb(db) {
13
+ _db = db;
14
+ }
15
+ export function resetRssDb() {
16
+ _db = null;
17
+ }
18
+ export function getRssSubs() {
19
+ return ensureRssMemoryDb().models.get(RSS_SUBS_TABLE) ?? null;
20
+ }
21
+ export function getRssSeen() {
22
+ return ensureRssMemoryDb().models.get(RSS_SEEN_TABLE) ?? null;
23
+ }
24
+ //# 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,EACL,mBAAmB,EACnB,cAAc,EACd,cAAc,GAGf,MAAM,mBAAmB,CAAC;AAE3B,IAAI,GAAG,GAAuB,IAAI,CAAC;AAEnC,MAAM,UAAU,iBAAiB;IAC/B,IAAI,CAAC,GAAG;QAAE,GAAG,GAAG,mBAAmB,EAAE,CAAC;IACtC,OAAO,GAAG,CAAC;AACb,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,GAAG,GAAG,IAAI,CAAC;AACb,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"}
package/lib/feed.js ADDED
@@ -0,0 +1,89 @@
1
+ /**
2
+ * RSS feed fetch/preview helpers (no Plugin host).
3
+ */
4
+ import Parser from 'rss-parser';
5
+ export const DEFAULT_RSS_CONFIG = {
6
+ pollCron: '0 */5 * * * *',
7
+ maxPerGroup: 30,
8
+ maxItems: 5,
9
+ timeout: 15_000,
10
+ };
11
+ export function resolveRssConfig(raw) {
12
+ return { ...DEFAULT_RSS_CONFIG, ...raw };
13
+ }
14
+ /**
15
+ * 剔除 HTML 标签与 `&nbsp;` 实体并折叠空白。
16
+ * 线性单遍扫描实现(手写状态机),语义等价于原先的
17
+ * `/<[^>]*>/g` → `/&nbsp;/g` → `/\s+/g` 三步 replace,但避免:
18
+ * - js/polynomial-redos:大量 `<` 无闭合时正则逐位重扫的二次方回溯;
19
+ * - js/incomplete-multi-character-sanitization:多趟 replace 的残留问题。
20
+ */
21
+ export function stripHtml(html) {
22
+ let out = '';
23
+ let pendingSpace = false;
24
+ let mayHaveTagEnd = html.includes('>');
25
+ let i = 0;
26
+ while (i < html.length) {
27
+ const ch = html[i];
28
+ if (ch === '<' && mayHaveTagEnd) {
29
+ const end = html.indexOf('>', i + 1);
30
+ if (end >= 0) {
31
+ i = end + 1;
32
+ continue;
33
+ }
34
+ // 后续不再有 `>`:剩余的 `<` 都是字面字符,不再重复扫描。
35
+ mayHaveTagEnd = false;
36
+ }
37
+ if (ch === '&' && html.startsWith('&nbsp;', i)) {
38
+ pendingSpace = true;
39
+ i += '&nbsp;'.length;
40
+ continue;
41
+ }
42
+ if (/\s/u.test(ch)) {
43
+ pendingSpace = true;
44
+ i += 1;
45
+ continue;
46
+ }
47
+ if (pendingSpace && out.length > 0)
48
+ out += ' ';
49
+ pendingSpace = false;
50
+ out += ch;
51
+ i += 1;
52
+ }
53
+ return out;
54
+ }
55
+ export async function fetchFeed(url, timeout = DEFAULT_RSS_CONFIG.timeout) {
56
+ const parser = new Parser({
57
+ timeout,
58
+ headers: {
59
+ 'User-Agent': 'ZhinBot-RSS/1.0',
60
+ Accept: 'application/rss+xml, application/atom+xml, application/xml, text/xml',
61
+ },
62
+ });
63
+ const feed = await parser.parseURL(url);
64
+ const items = (feed.items || []).slice(0, 20).map((item) => ({
65
+ title: (item.title || '').trim(),
66
+ link: (item.link || '').trim(),
67
+ date: item.isoDate || item.pubDate || '',
68
+ summary: stripHtml(item.contentSnippet || item.content || '').slice(0, 200),
69
+ guid: item.guid || item.link || item.title || '',
70
+ }));
71
+ return { title: (feed.title || url).trim(), items };
72
+ }
73
+ export function formatFeedPreview(title, items) {
74
+ if (items.length === 0)
75
+ return `${title}: 暂无内容`;
76
+ const lines = [`${title} (共 ${items.length} 条)`];
77
+ for (const item of items.slice(0, 5)) {
78
+ lines.push('');
79
+ lines.push(`${item.title}`);
80
+ if (item.date)
81
+ lines.push(` ${new Date(item.date).toLocaleString('zh-CN')}`);
82
+ if (item.link)
83
+ lines.push(` ${item.link}`);
84
+ }
85
+ if (items.length > 5)
86
+ lines.push(`\n...还有 ${items.length - 5} 条`);
87
+ return lines.join('\n');
88
+ }
89
+ //# sourceMappingURL=feed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feed.js","sourceRoot":"","sources":["../src/feed.ts"],"names":[],"mappings":"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"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { DEFAULT_RSS_CONFIG, fetchFeed, formatFeedPreview, resolveRssConfig, stripHtml, } from './feed.js';
2
+ export type { FeedItem, RssConfig } from './feed.js';
3
+ export { getRssDb, getRssSeen, getRssSubs, setRssDb, ensureRssMemoryDb, resetRssDb, } from './db-store.js';
4
+ export { extractChannelInfo, SMOKE_CHANNEL } from './channel.js';
5
+ export { checkSubscriptions, pollAllFeeds, formatNewItems, setRssOutboundPush } from './poll.js';
6
+ export type { RssOutboundPush, CheckResult } from './poll.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,SAAS,EACT,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,GACV,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EACL,QAAQ,EACR,UAAU,EACV,UAAU,EACV,QAAQ,EACR,iBAAiB,EACjB,UAAU,GACX,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AACjG,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC"}
package/lib/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { DEFAULT_RSS_CONFIG, fetchFeed, formatFeedPreview, resolveRssConfig, stripHtml, } from './feed.js';
2
+ export { getRssDb, getRssSeen, getRssSubs, setRssDb, ensureRssMemoryDb, resetRssDb, } from './db-store.js';
3
+ export { extractChannelInfo, SMOKE_CHANNEL } from './channel.js';
4
+ export { checkSubscriptions, pollAllFeeds, formatNewItems, setRssOutboundPush } from './poll.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,SAAS,EACT,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,GACV,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,QAAQ,EACR,UAAU,EACV,UAAU,EACV,QAAQ,EACR,iBAAiB,EACjB,UAAU,GACX,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * In-memory RSS models (slice-2) until Plugin Runtime DatabaseFeature Resource lands.
3
+ */
4
+ export interface RssRow extends Record<string, unknown> {
5
+ id: string;
6
+ }
7
+ export interface RssModel {
8
+ select: () => {
9
+ where: (query: Record<string, unknown>) => Promise<RssRow[]>;
10
+ then: <TResult1 = RssRow[], TResult2 = never>(onfulfilled?: ((value: RssRow[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null) => Promise<TResult1 | TResult2>;
11
+ };
12
+ insert: (row: Record<string, unknown>) => Promise<RssRow>;
13
+ delete: () => {
14
+ where: (query: Record<string, unknown>) => Promise<void>;
15
+ };
16
+ update: (patch: Record<string, unknown>) => {
17
+ where: (query: Record<string, unknown>) => Promise<void>;
18
+ };
19
+ }
20
+ export interface RssMemoryDb {
21
+ models: {
22
+ get: (name: string) => RssModel | undefined;
23
+ };
24
+ }
25
+ export declare const RSS_SUBS_TABLE = "rss_subscriptions";
26
+ export declare const RSS_SEEN_TABLE = "rss_seen_items";
27
+ export declare function createInMemoryRssDb(): RssMemoryDb;
28
+ //# sourceMappingURL=memory-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-store.d.ts","sourceRoot":"","sources":["../src/memory-store.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,MAAO,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACrD,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM;QACZ,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAC7D,IAAI,EAAE,CAAC,QAAQ,GAAG,MAAM,EAAE,EAAE,QAAQ,GAAG,KAAK,EAC1C,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,EAC5E,UAAU,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,KAAK,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,KACxE,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC,CAAC;KACnC,CAAC;IACF,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1D,MAAM,EAAE,MAAM;QAAE,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC;IAC3E,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK;QAC1C,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KAC1D,CAAC;CACH;AA6CD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE;QACN,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,GAAG,SAAS,CAAC;KAC7C,CAAC;CACH;AAED,eAAO,MAAM,cAAc,sBAAsB,CAAC;AAClD,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAE/C,wBAAgB,mBAAmB,IAAI,WAAW,CAUjD"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * In-memory RSS models (slice-2) until Plugin Runtime DatabaseFeature Resource lands.
3
+ */
4
+ function createMemoryModel() {
5
+ const rows = [];
6
+ let nextId = 1;
7
+ function matches(row, query) {
8
+ return Object.entries(query).every(([key, value]) => row[key] === value);
9
+ }
10
+ function all() {
11
+ return rows.map((row) => ({ ...row }));
12
+ }
13
+ return {
14
+ select: () => {
15
+ const promise = Promise.resolve(all());
16
+ return {
17
+ where: async (query) => rows.filter((row) => matches(row, query)).map((row) => ({ ...row })),
18
+ then: (onfulfilled, onrejected) => promise.then(onfulfilled, onrejected),
19
+ };
20
+ },
21
+ insert: async (row) => {
22
+ const withId = { id: String(nextId++), ...row };
23
+ rows.push(withId);
24
+ return { ...withId };
25
+ },
26
+ delete: () => ({
27
+ where: async (query) => {
28
+ for (let i = rows.length - 1; i >= 0; i -= 1) {
29
+ if (matches(rows[i], query))
30
+ rows.splice(i, 1);
31
+ }
32
+ },
33
+ }),
34
+ update: (patch) => ({
35
+ where: async (query) => {
36
+ for (const row of rows) {
37
+ if (matches(row, query))
38
+ Object.assign(row, patch);
39
+ }
40
+ },
41
+ }),
42
+ };
43
+ }
44
+ export const RSS_SUBS_TABLE = 'rss_subscriptions';
45
+ export const RSS_SEEN_TABLE = 'rss_seen_items';
46
+ export function createInMemoryRssDb() {
47
+ const models = new Map([
48
+ [RSS_SUBS_TABLE, createMemoryModel()],
49
+ [RSS_SEEN_TABLE, createMemoryModel()],
50
+ ]);
51
+ return {
52
+ models: {
53
+ get: (name) => models.get(name),
54
+ },
55
+ };
56
+ }
57
+ //# sourceMappingURL=memory-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-store.js","sourceRoot":"","sources":["../src/memory-store.ts"],"names":[],"mappings":"AAAA;;GAEG;AAqBH,SAAS,iBAAiB;IACxB,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,SAAS,OAAO,CAAC,GAA4B,EAAE,KAA8B;QAC3E,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,CAAC;IAC3E,CAAC;IAED,SAAS,GAAG;QACV,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC;IACzC,CAAC;IAED,OAAO;QACL,MAAM,EAAE,GAAG,EAAE;YACX,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;YACvC,OAAO;gBACL,KAAK,EAAE,KAAK,EAAE,KAA8B,EAAE,EAAE,CAC9C,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC;gBACtE,IAAI,EAAE,CAAC,WAAW,EAAE,UAAU,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC;aACzE,CAAC;QACJ,CAAC;QACD,MAAM,EAAE,KAAK,EAAE,GAA4B,EAAE,EAAE;YAC7C,MAAM,MAAM,GAAW,EAAE,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC;YACxD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAClB,OAAO,EAAE,GAAG,MAAM,EAAE,CAAC;QACvB,CAAC;QACD,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;YACb,KAAK,EAAE,KAAK,EAAE,KAA8B,EAAE,EAAE;gBAC9C,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC7C,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAE,EAAE,KAAK,CAAC;wBAAE,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBAClD,CAAC;YACH,CAAC;SACF,CAAC;QACF,MAAM,EAAE,CAAC,KAA8B,EAAE,EAAE,CAAC,CAAC;YAC3C,KAAK,EAAE,KAAK,EAAE,KAA8B,EAAE,EAAE;gBAC9C,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;oBACvB,IAAI,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC;wBAAE,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;gBACrD,CAAC;YACH,CAAC;SACF,CAAC;KACH,CAAC;AACJ,CAAC;AAQD,MAAM,CAAC,MAAM,cAAc,GAAG,mBAAmB,CAAC;AAClD,MAAM,CAAC,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAE/C,MAAM,UAAU,mBAAmB;IACjC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAmB;QACvC,CAAC,cAAc,EAAE,iBAAiB,EAAE,CAAC;QACrC,CAAC,cAAc,EAAE,iBAAiB,EAAE,CAAC;KACtC,CAAC,CAAC;IACH,OAAO;QACL,MAAM,EAAE;YACN,GAAG,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;SACxC;KACF,CAAC;AACJ,CAAC"}
package/lib/poll.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Feed poll helpers: detect new items; optional OutboundHost push to subscribers.
3
+ */
4
+ import { type FeedItem, type RssConfig } from './feed.js';
5
+ export type RssOutboundPush = (input: {
6
+ readonly adapterName: string;
7
+ readonly endpointId: string;
8
+ readonly channelType: string;
9
+ readonly channelId: string;
10
+ readonly content: string;
11
+ }) => Promise<void>;
12
+ export declare function setRssOutboundPush(push: RssOutboundPush | null): void;
13
+ export declare function getRssOutboundPush(): RssOutboundPush | null;
14
+ export declare function formatNewItems(feedTitle: string, items: FeedItem[], maxItems: number): string;
15
+ export declare function markItemsSeen(feedUrl: string, items: FeedItem[]): Promise<void>;
16
+ export interface CheckResult {
17
+ readonly text: string;
18
+ readonly totalNew: number;
19
+ readonly pushed: number;
20
+ }
21
+ /**
22
+ * Check subscribed feeds for new items and mark them seen.
23
+ * When OutboundHost is wired, pushes formatted text to each subscriber channel.
24
+ */
25
+ export declare function checkSubscriptions(options: {
26
+ urls: readonly string[];
27
+ config: RssConfig;
28
+ }): Promise<CheckResult>;
29
+ /**
30
+ * Periodically clean seen records older than 7 days.
31
+ */
32
+ export declare function cleanOldSeen(): Promise<void>;
33
+ /**
34
+ * Poll all unique feed URLs across subscriptions.
35
+ */
36
+ export declare function pollAllFeeds(config: RssConfig): Promise<CheckResult>;
37
+ //# sourceMappingURL=poll.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"poll.d.ts","sourceRoot":"","sources":["../src/poll.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAa,KAAK,QAAQ,EAAE,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAGrE,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE;IACpC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAIpB,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,GAAG,IAAI,CAErE;AAED,wBAAgB,kBAAkB,IAAI,eAAe,GAAG,IAAI,CAE3D;AAMD,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAY7F;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAcrF;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AA4BD;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IAChD,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,MAAM,EAAE,SAAS,CAAC;CACnB,GAAG,OAAO,CAAC,WAAW,CAAC,CAuCvB;AAED;;GAEG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAelD;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CAO1E"}