@zhin.js/plugin-rss 1.0.1 → 1.1.1
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/commands/rss-add/[url]/index.js +61 -0
- package/commands/rss-add/[url]/index.ts +63 -0
- package/commands/rss-check/[[url]]/index.js +43 -0
- package/commands/rss-check/[[url]]/index.ts +42 -0
- package/commands/rss-list/index.js +29 -0
- package/commands/rss-list/index.ts +32 -0
- package/commands/rss-preview/[url]/index.js +22 -0
- package/commands/rss-preview/[url]/index.ts +19 -0
- package/commands/rss-remove/[url]/index.js +35 -0
- package/commands/rss-remove/[url]/index.ts +37 -0
- package/lib/channel.d.ts +16 -0
- package/lib/channel.d.ts.map +1 -0
- package/lib/channel.js +42 -0
- package/lib/channel.js.map +1 -0
- package/lib/db-store.d.ts +5 -0
- package/lib/db-store.d.ts.map +1 -0
- package/lib/db-store.js +9 -0
- package/lib/db-store.js.map +1 -0
- package/lib/feed.d.ts +29 -0
- package/lib/feed.d.ts.map +1 -0
- package/lib/feed.js +89 -0
- package/lib/feed.js.map +1 -0
- package/lib/index.d.ts +7 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +5 -492
- package/lib/index.js.map +1 -1
- package/lib/memory-store.d.ts +30 -0
- package/lib/memory-store.d.ts.map +1 -0
- package/lib/memory-store.js +61 -0
- package/lib/memory-store.js.map +1 -0
- package/lib/poll.d.ts +28 -0
- package/lib/poll.d.ts.map +1 -0
- package/lib/poll.js +147 -0
- package/lib/poll.js.map +1 -0
- package/lib/runtime.d.ts +16 -0
- package/lib/runtime.d.ts.map +1 -0
- package/lib/runtime.js +3 -0
- package/lib/runtime.js.map +1 -0
- package/package.json +52 -13
- package/plugin.js +88 -0
- package/schema.json +30 -0
- package/skills/rss/tools/rss_list_subscriptions/index.js +32 -0
- package/skills/rss/tools/rss_list_subscriptions/index.ts +38 -0
- package/skills/rss/tools/rss_preview_feed/index.js +26 -0
- package/skills/rss/tools/rss_preview_feed/index.ts +23 -0
- package/src/channel.ts +69 -0
- package/src/db-store.ts +22 -0
- package/src/feed.ts +106 -0
- package/src/index.ts +13 -556
- package/src/memory-store.ts +88 -0
- package/src/poll.ts +153 -0
- package/src/runtime.ts +22 -0
|
@@ -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,MAAM,WAAW,CAAC;AAErD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAM/C,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAY7F;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAc1G;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,UAAU,EAAE,OAAO,EAAE;IACrE,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CACzB,GAAG,OAAO,CAAC,WAAW,CAAC,CAuCvB;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBrE;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAO5E"}
|
package/lib/poll.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feed poll helpers: detect new items; optional OutboundHost push to subscribers.
|
|
3
|
+
*/
|
|
4
|
+
import { fetchFeed } from './feed.js';
|
|
5
|
+
import { getRssSeen, getRssSubs } from './db-store.js';
|
|
6
|
+
function ts() {
|
|
7
|
+
return new Date().toISOString();
|
|
8
|
+
}
|
|
9
|
+
export function formatNewItems(feedTitle, items, maxItems) {
|
|
10
|
+
const lines = [`${feedTitle} 有 ${items.length} 条新内容:`];
|
|
11
|
+
for (const item of items.slice(0, maxItems)) {
|
|
12
|
+
lines.push('');
|
|
13
|
+
lines.push(`${item.title}`);
|
|
14
|
+
if (item.summary)
|
|
15
|
+
lines.push(item.summary);
|
|
16
|
+
if (item.link)
|
|
17
|
+
lines.push(item.link);
|
|
18
|
+
}
|
|
19
|
+
if (items.length > maxItems) {
|
|
20
|
+
lines.push(`\n...还有 ${items.length - maxItems} 条,请查看源站`);
|
|
21
|
+
}
|
|
22
|
+
return lines.join('\n');
|
|
23
|
+
}
|
|
24
|
+
export async function markItemsSeen(runtime, feedUrl, items) {
|
|
25
|
+
const Seen = getRssSeen(runtime.db);
|
|
26
|
+
if (!Seen)
|
|
27
|
+
return;
|
|
28
|
+
for (const item of items) {
|
|
29
|
+
const dup = await Seen.select().where({ feed_url: feedUrl, item_guid: item.guid });
|
|
30
|
+
if (dup.length === 0) {
|
|
31
|
+
await Seen.insert({
|
|
32
|
+
feed_url: feedUrl,
|
|
33
|
+
item_guid: item.guid,
|
|
34
|
+
item_title: item.title,
|
|
35
|
+
seen_at: ts(),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function pushToSubscribers(runtime, feedUrl, content) {
|
|
41
|
+
const push = runtime.outbound;
|
|
42
|
+
const Subs = getRssSubs(runtime.db);
|
|
43
|
+
if (!push || !Subs)
|
|
44
|
+
return 0;
|
|
45
|
+
const subscribers = await Subs.select().where({ url: feedUrl });
|
|
46
|
+
let pushed = 0;
|
|
47
|
+
for (const sub of subscribers) {
|
|
48
|
+
const adapterName = String(sub.adapter_name ?? '');
|
|
49
|
+
const channelId = String(sub.channel_id ?? '');
|
|
50
|
+
if (!adapterName || !channelId)
|
|
51
|
+
continue;
|
|
52
|
+
try {
|
|
53
|
+
await push({
|
|
54
|
+
adapterName,
|
|
55
|
+
endpointKey: String(sub.endpoint_id ?? adapterName),
|
|
56
|
+
channelType: String(sub.channel_type ?? 'private'),
|
|
57
|
+
channelId,
|
|
58
|
+
content,
|
|
59
|
+
});
|
|
60
|
+
pushed += 1;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// OutboundHost already logs; continue other subscribers.
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return pushed;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Check subscribed feeds for new items and mark them seen.
|
|
70
|
+
* When OutboundHost is wired, pushes formatted text to each subscriber channel.
|
|
71
|
+
*/
|
|
72
|
+
export async function checkSubscriptions(runtime, options) {
|
|
73
|
+
const Seen = getRssSeen(runtime.db);
|
|
74
|
+
const Subs = getRssSubs(runtime.db);
|
|
75
|
+
if (!Seen || !Subs) {
|
|
76
|
+
return { text: 'RSS 数据库尚未就绪', totalNew: 0, pushed: 0 };
|
|
77
|
+
}
|
|
78
|
+
const parts = [];
|
|
79
|
+
let totalNew = 0;
|
|
80
|
+
let pushed = 0;
|
|
81
|
+
for (const url of options.urls) {
|
|
82
|
+
try {
|
|
83
|
+
const { title, items } = await fetchFeed(url, runtime.config.timeout);
|
|
84
|
+
const seenRows = await Seen.select().where({ feed_url: url });
|
|
85
|
+
const seenGuids = new Set(seenRows.map((r) => String(r.item_guid ?? '')));
|
|
86
|
+
const newItems = items.filter((item) => item.guid && !seenGuids.has(item.guid));
|
|
87
|
+
if (newItems.length === 0) {
|
|
88
|
+
parts.push(`${title || url}: 无新内容`);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
await markItemsSeen(runtime, url, newItems);
|
|
92
|
+
totalNew += newItems.length;
|
|
93
|
+
const body = formatNewItems(title || url, newItems, runtime.config.maxItems);
|
|
94
|
+
parts.push(body);
|
|
95
|
+
pushed += await pushToSubscribers(runtime, url, body);
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
parts.push(`${url}: 检查失败 — ${e.message}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (parts.length === 0)
|
|
102
|
+
return { text: '没有可检查的订阅', totalNew: 0, pushed: 0 };
|
|
103
|
+
const pushNote = runtime.outbound
|
|
104
|
+
? (pushed > 0 ? `,已推送 ${pushed} 个会话` : ',无出站推送')
|
|
105
|
+
: '(未装配 OutboundHost,仅本地标记)';
|
|
106
|
+
const header = totalNew > 0
|
|
107
|
+
? `检查完成,共 ${totalNew} 条新内容${pushNote}`
|
|
108
|
+
: '检查完成,暂无新内容';
|
|
109
|
+
return { text: `${header}\n\n${parts.join('\n\n')}`, totalNew, pushed };
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Periodically clean seen records older than 7 days.
|
|
113
|
+
*/
|
|
114
|
+
export async function cleanOldSeen(runtime) {
|
|
115
|
+
const Seen = getRssSeen(runtime.db);
|
|
116
|
+
if (!Seen)
|
|
117
|
+
return;
|
|
118
|
+
try {
|
|
119
|
+
const cutoff = new Date();
|
|
120
|
+
cutoff.setDate(cutoff.getDate() - 7);
|
|
121
|
+
const cutoffStr = cutoff.toISOString();
|
|
122
|
+
const all = await Seen.select();
|
|
123
|
+
const expired = all.filter((r) => typeof r.seen_at === 'string' && r.seen_at < cutoffStr);
|
|
124
|
+
for (const row of expired) {
|
|
125
|
+
// 表结构没有 id 列,按业务键 (feed_url, item_guid) 删除。
|
|
126
|
+
await Seen.delete().where({ feed_url: row.feed_url, item_guid: row.item_guid });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
// Cleanup failure must not break the main flow, but it must be visible.
|
|
131
|
+
console.warn(`[rss] cleanOldSeen failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Poll all unique feed URLs across subscriptions.
|
|
136
|
+
*/
|
|
137
|
+
export async function pollAllFeeds(runtime) {
|
|
138
|
+
const Subs = getRssSubs(runtime.db);
|
|
139
|
+
if (!Subs)
|
|
140
|
+
return { text: 'RSS 数据库尚未就绪', totalNew: 0, pushed: 0 };
|
|
141
|
+
const all = await Subs.select();
|
|
142
|
+
const urls = [...new Set(all.map((s) => String(s.url ?? '')).filter(Boolean))];
|
|
143
|
+
if (urls.length === 0)
|
|
144
|
+
return { text: '暂无任何 RSS 订阅', totalNew: 0, pushed: 0 };
|
|
145
|
+
return checkSubscriptions(runtime, { urls });
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=poll.js.map
|
package/lib/poll.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"poll.js","sourceRoot":"","sources":["../src/poll.ts"],"names":[],"mappings":"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"}
|
package/lib/runtime.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RssConfig } from './feed.js';
|
|
2
|
+
import type { RssMemoryDb } from './memory-store.js';
|
|
3
|
+
export type RssOutboundPush = (input: {
|
|
4
|
+
readonly adapterName: string;
|
|
5
|
+
readonly endpointKey: string;
|
|
6
|
+
readonly channelType: string;
|
|
7
|
+
readonly channelId: string;
|
|
8
|
+
readonly content: string;
|
|
9
|
+
}) => Promise<void>;
|
|
10
|
+
export interface RssRuntime {
|
|
11
|
+
readonly db: RssMemoryDb;
|
|
12
|
+
readonly config: Readonly<RssConfig>;
|
|
13
|
+
readonly outbound: RssOutboundPush | null;
|
|
14
|
+
}
|
|
15
|
+
export declare const rssRuntimeToken: import("zhin.js").Token<RssRuntime>;
|
|
16
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE;IACpC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,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;AAEpB,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;CAC3C;AAED,eAAO,MAAM,eAAe,qCAG3B,CAAC"}
|
package/lib/runtime.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAkBtC,MAAM,CAAC,MAAM,eAAe,GAAG,WAAW,CACxC,kBAAkB,EAClB,0BAA0B,CAC3B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/plugin-rss",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "RSS/Atom feed subscription
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "RSS/Atom feed subscription plugin for Zhin.js (Plugin Runtime)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
7
7
|
"types": "./lib/index.d.ts",
|
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
"./package.json": "./package.json"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
|
+
"plugin.js",
|
|
18
|
+
"schema.json",
|
|
19
|
+
"commands",
|
|
17
20
|
"src",
|
|
18
21
|
"lib",
|
|
19
|
-
"
|
|
20
|
-
"dist",
|
|
22
|
+
"tools",
|
|
21
23
|
"skills",
|
|
22
24
|
"README.md"
|
|
23
25
|
],
|
|
@@ -30,10 +32,7 @@
|
|
|
30
32
|
"atom",
|
|
31
33
|
"feed",
|
|
32
34
|
"subscription",
|
|
33
|
-
"push"
|
|
34
|
-
"notify",
|
|
35
|
-
"chatbot",
|
|
36
|
-
"im"
|
|
35
|
+
"push"
|
|
37
36
|
],
|
|
38
37
|
"author": {
|
|
39
38
|
"name": "lc-cn",
|
|
@@ -42,14 +41,28 @@
|
|
|
42
41
|
},
|
|
43
42
|
"license": "MIT",
|
|
44
43
|
"dependencies": {
|
|
45
|
-
"rss-parser": "^3.13.0"
|
|
44
|
+
"rss-parser": "^3.13.0",
|
|
45
|
+
"@zhin.js/tool": "1.1.1",
|
|
46
|
+
"@zhin.js/skill": "1.1.1"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
49
|
"typescript": "^6.0.3",
|
|
49
|
-
"
|
|
50
|
+
"vitest": "^4.1.10",
|
|
51
|
+
"zod": "^4.4.3",
|
|
52
|
+
"zhin.js": "1.1.2"
|
|
50
53
|
},
|
|
51
54
|
"peerDependencies": {
|
|
52
|
-
"
|
|
55
|
+
"zod": "^4.0.0",
|
|
56
|
+
"zhin.js": "^1.1.2",
|
|
57
|
+
"@zhin.js/command": "^1.1.1"
|
|
58
|
+
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"@zhin.js/command": {
|
|
61
|
+
"optional": true
|
|
62
|
+
},
|
|
63
|
+
"zod": {
|
|
64
|
+
"optional": true
|
|
65
|
+
}
|
|
53
66
|
},
|
|
54
67
|
"repository": {
|
|
55
68
|
"type": "git",
|
|
@@ -60,8 +73,34 @@
|
|
|
60
73
|
"access": "public",
|
|
61
74
|
"registry": "https://registry.npmjs.org"
|
|
62
75
|
},
|
|
76
|
+
"engines": {
|
|
77
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
78
|
+
},
|
|
79
|
+
"zhin": {
|
|
80
|
+
"protocol": 1,
|
|
81
|
+
"type": "plugin",
|
|
82
|
+
"entry": "./plugin.js",
|
|
83
|
+
"engine": "^1.0.0",
|
|
84
|
+
"runtime": "trusted",
|
|
85
|
+
"features": [
|
|
86
|
+
{
|
|
87
|
+
"package": "@zhin.js/command",
|
|
88
|
+
"api": "^1.0.0"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"package": "@zhin.js/tool",
|
|
92
|
+
"api": "^1.0.0"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"package": "@zhin.js/skill",
|
|
96
|
+
"api": "^1.0.0"
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
"plugins": []
|
|
100
|
+
},
|
|
63
101
|
"scripts": {
|
|
64
|
-
"build": "tsc
|
|
65
|
-
"clean": "rimraf lib"
|
|
102
|
+
"build": "tsc",
|
|
103
|
+
"clean": "rimraf lib",
|
|
104
|
+
"test": "NODE_OPTIONS=--experimental-strip-types vitest run --root ../../.. plugins/utils/rss/tests"
|
|
66
105
|
}
|
|
67
106
|
}
|
package/plugin.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { definePlugin, databaseHostToken, outboundHostToken, scheduleHostToken, } from 'zhin.js';
|
|
3
|
+
import { resolveRssConfig } from "./lib/feed.js";
|
|
4
|
+
import { createInMemoryRssDb, RSS_SEEN_TABLE, RSS_SUBS_TABLE, } from "./lib/db-store.js";
|
|
5
|
+
import { cleanOldSeen, pollAllFeeds } from "./lib/poll.js";
|
|
6
|
+
import { rssRuntimeToken } from "./lib/runtime.js";
|
|
7
|
+
function defineRssTables(db) {
|
|
8
|
+
db.define(RSS_SUBS_TABLE, {
|
|
9
|
+
url: { type: 'text', nullable: false },
|
|
10
|
+
feed_title: { type: 'text', default: '' },
|
|
11
|
+
adapter_name: { type: 'text', nullable: false },
|
|
12
|
+
endpoint_id: { type: 'text', default: '' },
|
|
13
|
+
channel_type: { type: 'text', default: 'private' },
|
|
14
|
+
channel_id: { type: 'text', nullable: false },
|
|
15
|
+
creator_id: { type: 'text', default: '' },
|
|
16
|
+
creator_name: { type: 'text', default: '' },
|
|
17
|
+
created_at: { type: 'text', default: '' },
|
|
18
|
+
});
|
|
19
|
+
db.define(RSS_SEEN_TABLE, {
|
|
20
|
+
feed_url: { type: 'text', nullable: false },
|
|
21
|
+
item_guid: { type: 'text', nullable: false },
|
|
22
|
+
item_title: { type: 'text', default: '' },
|
|
23
|
+
seen_at: { type: 'text', default: '' },
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Plugin Runtime:
|
|
28
|
+
* - Commands: add/list/remove/check.
|
|
29
|
+
* - DB: prefer `databaseHostToken`; else in-memory.
|
|
30
|
+
* - Outbound: `outboundHostToken` pushes new items to subscriber channels.
|
|
31
|
+
* - Cron: `scheduleHostToken` poll job when available.
|
|
32
|
+
*/
|
|
33
|
+
export default definePlugin({
|
|
34
|
+
name: 'rss',
|
|
35
|
+
metadata: {
|
|
36
|
+
displayName: 'RSS',
|
|
37
|
+
},
|
|
38
|
+
setup(context) {
|
|
39
|
+
const config = resolveRssConfig(context.config.get());
|
|
40
|
+
const db = context.resources.has(databaseHostToken) ? (() => {
|
|
41
|
+
const host = context.resources.use(databaseHostToken);
|
|
42
|
+
defineRssTables(host);
|
|
43
|
+
return host;
|
|
44
|
+
})() : createInMemoryRssDb();
|
|
45
|
+
let outboundPush = null;
|
|
46
|
+
if (context.resources.has(outboundHostToken)) {
|
|
47
|
+
const outbound = context.resources.use(outboundHostToken);
|
|
48
|
+
outboundPush = async (input) => {
|
|
49
|
+
await outbound.send({
|
|
50
|
+
adapter: input.adapterName,
|
|
51
|
+
endpointKey: input.endpointKey,
|
|
52
|
+
conversation: {
|
|
53
|
+
kind: input.channelType,
|
|
54
|
+
id: input.channelId,
|
|
55
|
+
},
|
|
56
|
+
content: input.content,
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const runtime = Object.freeze({
|
|
61
|
+
db,
|
|
62
|
+
config: Object.freeze({ ...config }),
|
|
63
|
+
outbound: outboundPush,
|
|
64
|
+
});
|
|
65
|
+
context.resources.provide(rssRuntimeToken, runtime);
|
|
66
|
+
if (!context.resources.has(scheduleHostToken))
|
|
67
|
+
return;
|
|
68
|
+
const schedule = context.resources.use(scheduleHostToken);
|
|
69
|
+
const dispose = schedule.register({
|
|
70
|
+
id: 'rss/poll_feeds',
|
|
71
|
+
cron: config.pollCron,
|
|
72
|
+
description: 'Poll RSS subscriptions',
|
|
73
|
+
async execute() {
|
|
74
|
+
await pollAllFeeds(runtime);
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
const disposeClean = schedule.register({
|
|
78
|
+
id: 'rss/clean_old_seen',
|
|
79
|
+
cron: '0 0 4 * * *',
|
|
80
|
+
description: 'Clean seen records older than 7 days',
|
|
81
|
+
async execute() {
|
|
82
|
+
await cleanOldSeen(runtime);
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
context.lifecycle.add(dispose);
|
|
86
|
+
context.lifecycle.add(disposeClean);
|
|
87
|
+
},
|
|
88
|
+
});
|
package/schema.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"additionalProperties": false,
|
|
5
|
+
"properties": {
|
|
6
|
+
"pollCron": {
|
|
7
|
+
"type": "string",
|
|
8
|
+
"default": "0 */5 * * * *",
|
|
9
|
+
"description": "轮询频率 (6 段 Cron 表达式)"
|
|
10
|
+
},
|
|
11
|
+
"maxPerGroup": {
|
|
12
|
+
"type": "number",
|
|
13
|
+
"default": 30,
|
|
14
|
+
"minimum": 1,
|
|
15
|
+
"maximum": 200
|
|
16
|
+
},
|
|
17
|
+
"maxItems": {
|
|
18
|
+
"type": "number",
|
|
19
|
+
"default": 5,
|
|
20
|
+
"minimum": 1,
|
|
21
|
+
"maximum": 20
|
|
22
|
+
},
|
|
23
|
+
"timeout": {
|
|
24
|
+
"type": "number",
|
|
25
|
+
"default": 15000,
|
|
26
|
+
"minimum": 1000,
|
|
27
|
+
"maximum": 60000
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineAgentTool } from '@zhin.js/tool';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { getRssSubs } from "../../../../lib/db-store.js";
|
|
5
|
+
import { rssRuntimeToken } from "../../../../lib/runtime.js";
|
|
6
|
+
export default defineAgentTool({
|
|
7
|
+
description: '查询当前所有 RSS 订阅',
|
|
8
|
+
inputSchema: z.object({}),
|
|
9
|
+
async execute(_input, context) {
|
|
10
|
+
const Subs = getRssSubs(context.use(rssRuntimeToken).db);
|
|
11
|
+
if (!Subs)
|
|
12
|
+
return 'RSS 数据库尚未就绪';
|
|
13
|
+
const all = (await Subs.select());
|
|
14
|
+
if (all.length === 0)
|
|
15
|
+
return '暂无任何 RSS 订阅';
|
|
16
|
+
const grouped = new Map();
|
|
17
|
+
for (const sub of all) {
|
|
18
|
+
const key = `${sub.channel_type}:${sub.channel_id}`;
|
|
19
|
+
const list = grouped.get(key) || [];
|
|
20
|
+
list.push(sub);
|
|
21
|
+
grouped.set(key, list);
|
|
22
|
+
}
|
|
23
|
+
const lines = [];
|
|
24
|
+
for (const [channel, subs] of grouped) {
|
|
25
|
+
lines.push(`[${channel}]`);
|
|
26
|
+
for (const s of subs) {
|
|
27
|
+
lines.push(` - ${s.feed_title || s.url}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return `RSS 订阅总览 (${all.length} 条):\n${lines.join('\n')}`;
|
|
31
|
+
},
|
|
32
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/tool';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getRssSubs } from '../../../../src/db-store.js';
|
|
4
|
+
import { rssRuntimeToken } from '../../../../src/runtime.js';
|
|
5
|
+
|
|
6
|
+
export default defineAgentTool({
|
|
7
|
+
description: '查询当前所有 RSS 订阅',
|
|
8
|
+
inputSchema: z.object({}),
|
|
9
|
+
async execute(_input, context) {
|
|
10
|
+
const Subs = getRssSubs(context.use(rssRuntimeToken).db);
|
|
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,26 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineAgentTool } from '@zhin.js/tool';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { fetchFeed } from "../../../../lib/feed.js";
|
|
5
|
+
import { rssRuntimeToken } from "../../../../lib/runtime.js";
|
|
6
|
+
export default defineAgentTool({
|
|
7
|
+
description: '预览一个 RSS 源的最新内容',
|
|
8
|
+
inputSchema: z.object({ url: z.string().min(1) }),
|
|
9
|
+
async execute({ url }, context) {
|
|
10
|
+
if (!url)
|
|
11
|
+
return '请提供 RSS 源 URL';
|
|
12
|
+
try {
|
|
13
|
+
const runtime = context.use(rssRuntimeToken);
|
|
14
|
+
const { title, items } = await fetchFeed(url, runtime.config.timeout);
|
|
15
|
+
if (items.length === 0)
|
|
16
|
+
return `${title}: 无内容`;
|
|
17
|
+
return items
|
|
18
|
+
.slice(0, 5)
|
|
19
|
+
.map((item, i) => `${i + 1}. ${item.title}\n ${item.link}`)
|
|
20
|
+
.join('\n');
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
return `解析失败: ${e.message}`;
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/tool';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { fetchFeed } from '../../../../src/feed.js';
|
|
4
|
+
import { rssRuntimeToken } from '../../../../src/runtime.js';
|
|
5
|
+
|
|
6
|
+
export default defineAgentTool<{ url: string }>({
|
|
7
|
+
description: '预览一个 RSS 源的最新内容',
|
|
8
|
+
inputSchema: z.object({ url: z.string().min(1) }),
|
|
9
|
+
async execute({ url }, context) {
|
|
10
|
+
if (!url) return '请提供 RSS 源 URL';
|
|
11
|
+
try {
|
|
12
|
+
const runtime = context.use(rssRuntimeToken);
|
|
13
|
+
const { title, items } = await fetchFeed(url, runtime.config.timeout);
|
|
14
|
+
if (items.length === 0) return `${title}: 无内容`;
|
|
15
|
+
return items
|
|
16
|
+
.slice(0, 5)
|
|
17
|
+
.map((item, i) => `${i + 1}. ${item.title}\n ${item.link}`)
|
|
18
|
+
.join('\n');
|
|
19
|
+
} catch (e) {
|
|
20
|
+
return `解析失败: ${(e as Error).message}`;
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
});
|
package/src/channel.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract subscription channel keys from Runtime Message (`input`).
|
|
3
|
+
* Gaps vs legacy: no `$channel` / `$group` — use `conversation` / `metadata`.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface ChannelInfo {
|
|
7
|
+
adapterName: string;
|
|
8
|
+
endpointKey: string;
|
|
9
|
+
channelType: string;
|
|
10
|
+
channelId: string;
|
|
11
|
+
senderId: string;
|
|
12
|
+
senderName: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Smoke / agent / execute()-without-dispatch fallback. */
|
|
16
|
+
export const SMOKE_CHANNEL: ChannelInfo = {
|
|
17
|
+
adapterName: 'smoke',
|
|
18
|
+
endpointKey: 'local',
|
|
19
|
+
channelType: 'private',
|
|
20
|
+
channelId: 'global',
|
|
21
|
+
senderId: '',
|
|
22
|
+
senderName: '',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function extractChannelInfo(input: unknown): ChannelInfo {
|
|
26
|
+
if (!input || typeof input !== 'object') return { ...SMOKE_CHANNEL };
|
|
27
|
+
|
|
28
|
+
const msg = input as {
|
|
29
|
+
conversation?: {
|
|
30
|
+
endpoint?: { id?: string; adapter?: string };
|
|
31
|
+
kind?: string;
|
|
32
|
+
id?: string;
|
|
33
|
+
};
|
|
34
|
+
sender?: string;
|
|
35
|
+
metadata?: Record<string, unknown>;
|
|
36
|
+
};
|
|
37
|
+
const meta = msg.metadata ?? {};
|
|
38
|
+
const conversation = msg.conversation;
|
|
39
|
+
const channelType = String(
|
|
40
|
+
meta.channelType ?? meta.type ?? meta.channel_type ?? conversation?.kind ?? 'private',
|
|
41
|
+
);
|
|
42
|
+
const channelId = String(
|
|
43
|
+
meta.channelId ?? meta.channel_id ?? conversation?.id ?? SMOKE_CHANNEL.channelId,
|
|
44
|
+
);
|
|
45
|
+
const endpointCapabilityId = typeof conversation?.endpoint?.id === 'string'
|
|
46
|
+
? conversation.endpoint.id
|
|
47
|
+
: undefined;
|
|
48
|
+
const adapterName = String(
|
|
49
|
+
(endpointCapabilityId?.split('\0').pop() ?? endpointCapabilityId)?.split('~')[0]
|
|
50
|
+
?? meta.adapter
|
|
51
|
+
?? SMOKE_CHANNEL.adapterName,
|
|
52
|
+
);
|
|
53
|
+
const endpointKey = String(
|
|
54
|
+
meta.endpoint ?? meta.endpointKey ?? meta.endpoint_id ?? adapterName,
|
|
55
|
+
);
|
|
56
|
+
const senderId = String(msg.sender ?? meta.senderId ?? '');
|
|
57
|
+
const senderName = String(meta.senderName ?? meta.name ?? '');
|
|
58
|
+
|
|
59
|
+
if (!channelId) return { ...SMOKE_CHANNEL, adapterName, endpointKey, senderId, senderName };
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
adapterName,
|
|
63
|
+
endpointKey,
|
|
64
|
+
channelType,
|
|
65
|
+
channelId,
|
|
66
|
+
senderId,
|
|
67
|
+
senderName,
|
|
68
|
+
};
|
|
69
|
+
}
|
package/src/db-store.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createInMemoryRssDb,
|
|
3
|
+
RSS_SEEN_TABLE,
|
|
4
|
+
RSS_SUBS_TABLE,
|
|
5
|
+
type RssMemoryDb,
|
|
6
|
+
type RssModel,
|
|
7
|
+
} from './memory-store.js';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
RSS_SEEN_TABLE,
|
|
11
|
+
RSS_SUBS_TABLE,
|
|
12
|
+
type RssMemoryDb,
|
|
13
|
+
type RssModel,
|
|
14
|
+
} from './memory-store.js';
|
|
15
|
+
|
|
16
|
+
export function getRssSubs(db: RssMemoryDb): RssModel | null {
|
|
17
|
+
return db.models.get(RSS_SUBS_TABLE) ?? null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getRssSeen(db: RssMemoryDb): RssModel | null {
|
|
21
|
+
return db.models.get(RSS_SEEN_TABLE) ?? null;
|
|
22
|
+
}
|