dsh-rss 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -4,6 +4,10 @@
4
4
 
5
5
  DSH (DeepSeek Harness) plugin for RSS/Atom subscriptions: manage feeds, fetch and parse RSS 0.9x / 1.0 / 2.0 and Atom, with OPML bulk import/export, exposing seven model-facing tools.
6
6
 
7
+ ## Compatibility
8
+
9
+ Verified against `@deepseek-ai/dsh@0.1.1-rc.2` on 2026-08-26. Built for the cordis patch-bundle plugin model (`cordis.patch.yml` + `dsh.bundle.patch`). No runtime imports of `@deepseek-ai/*` internals.
10
+
7
11
  ## Installation
8
12
 
9
13
  ```bash
@@ -12,6 +16,15 @@ dsh plugin --profile web add dsh-rss
12
16
 
13
17
  Restart the web service after installing.
14
18
 
19
+ ## Uninstall
20
+
21
+ ```bash
22
+ dsh plugin --profile web remove dsh-rss
23
+ ```
24
+
25
+ Then restart the web service. To clean up fully, also remove the plugin entry from your profile `cordis.patch.yml` if you overrode it.
26
+
27
+
15
28
  ## Configuration
16
29
 
17
30
  Override the plugin row in your profile's `cordis.patch.yml` (the plugin also loads with all defaults when absent):
@@ -78,4 +91,4 @@ pnpm test # builds + 55 tests
78
91
 
79
92
  ## License
80
93
 
81
- MIT
94
+ MIT
package/README.md CHANGED
@@ -9,7 +9,11 @@
9
9
  [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
10
10
 
11
11
 
12
- DSH(DeepSeek Harness)的 RSS/Atom 订阅工具插件:管理订阅源,抓取并解析 RSS 0.9x / 1.0 / 2.0 与 Atom,支持 OPML 批量导入导出,给模型提供七个可直接调用的工具。
12
+ DSH(DeepSeek Harness)的 RSS/Atom 订阅工具插件:管理订阅源,抓取并解析 RSS 0.9x / 1.0 / 2.0 与 Atom,支持 OPML 批量导入导出,给模型提供九个可直接调用的工具。
13
+
14
+ ## 兼容性
15
+
16
+ 在 `@deepseek-ai/dsh@0.1.1-rc.2` 上验证(2026-08-26)。遵循 cordis 组合包补丁模型(`cordis.patch.yml` + `dsh.bundle.patch`),运行时不 import 任何 `@deepseek-ai/*` 内部模块。
13
17
 
14
18
  ## 安装
15
19
 
@@ -19,6 +23,15 @@ dsh plugin --profile web add dsh-rss
19
23
 
20
24
  安装后重启 Web 服务即可。
21
25
 
26
+ ## 卸载
27
+
28
+ ```bash
29
+ dsh plugin --profile web remove dsh-rss
30
+ ```
31
+
32
+ 卸载后重启 Web 服务。如需彻底清理,可再手动删除自己 profile `cordis.patch.yml` 中覆盖的插件行。
33
+
34
+
22
35
  ## 配置
23
36
 
24
37
  在你自己的 profile 的 `cordis.patch.yml` 里覆盖本插件行(缺省时插件也能加载,只是全部用默认值):
@@ -48,6 +61,8 @@ dsh plugin --profile web add dsh-rss
48
61
  | `rss_check` | 校验地址是否为可解析的订阅源 | `url` 必填 |
49
62
  | `rss_opml_export` | 导出订阅列表为 OPML 2.0 文本(可写入文件) | `path` 可选 |
50
63
  | `rss_opml_import` | 从 OPML 2.0 文本批量导入订阅源 | `opml` 必填 |
64
+ | `rss_search` | 跨订阅源关键词搜索条目(标题/摘要/正文/作者) | `query` 必填;`name`/`url`/`since`/`limit` 可选 |
65
+ | `rss_health` | 插件自检(配置/订阅/游标/解析器,离线) | 无 |
51
66
 
52
67
  ### 示例
53
68
 
@@ -80,12 +95,11 @@ rss_opml_import { opml: "<?xml version=\"1.0\"?>..." }
80
95
 
81
96
  ```bash
82
97
  pnpm install
83
- pnpm test # 构建 + 55 个测试
98
+ pnpm test # 构建 + 63 个测试
84
99
  ```
85
100
 
86
101
  发布前门禁:危险模式扫描、manifest 自检、`pnpm audit --prod`、全量测试,以及全新 profile 的真实启动冒烟测试。
87
102
 
88
103
  ## License
89
104
 
90
- MIT
91
-
105
+ MIT
package/lib/index.d.ts CHANGED
@@ -11,6 +11,7 @@
11
11
  import { type RssConfig } from './config.js';
12
12
  import { type RssSettingsScope, type RssToolDefinition } from './tools.js';
13
13
  /** cordis 服务注入:apply 里要用 ctx.settings 与 ctx.tools,必须显式声明,否则宿主会抛 cannot get property without inject。 */
14
+ export declare const name = "rss";
14
15
  export declare const inject: string[];
15
16
  /** 插件所需的最小 ctx 面(社区插件不依赖宿主内部类型)。 */
16
17
  export interface RssPluginContext {
package/lib/index.js CHANGED
@@ -12,6 +12,7 @@ import { resolveConfig } from './config.js';
12
12
  import { RSS_SETTINGS_NAMESPACE, RssSettingsSchema } from './settings.js';
13
13
  import { buildRssTools } from './tools.js';
14
14
  /** cordis 服务注入:apply 里要用 ctx.settings 与 ctx.tools,必须显式声明,否则宿主会抛 cannot get property without inject。 */
15
+ export const name = 'rss';
15
16
  export const inject = ['settings', 'tools'];
16
17
  /**
17
18
  * 插件入口:解析配置、注册 settings 命名空间与五个 RSS 工具。
package/lib/settings.d.ts CHANGED
@@ -9,10 +9,13 @@ export declare const RSS_SETTINGS_NAMESPACE = "dsh-rss";
9
9
  /** settings 页形状:目前只有订阅列表(YAML 文本)。 */
10
10
  export declare const RssSettingsSchema: z<Schemastery.ObjectS<{
11
11
  feedsYaml: z<string, string>;
12
+ cursorsJson: z<string, string>;
12
13
  }>, Schemastery.ObjectT<{
13
14
  feedsYaml: z<string, string>;
15
+ cursorsJson: z<string, string>;
14
16
  }>>;
15
17
  /** settings 解析后的值。 */
16
18
  export interface RssSettingsValue {
17
19
  feedsYaml: string;
20
+ cursorsJson: string;
18
21
  }
package/lib/settings.js CHANGED
@@ -9,4 +9,5 @@ export const RSS_SETTINGS_NAMESPACE = 'dsh-rss';
9
9
  /** settings 页形状:目前只有订阅列表(YAML 文本)。 */
10
10
  export const RssSettingsSchema = z.object({
11
11
  feedsYaml: z.string().default(''),
12
+ cursorsJson: z.string().default(''),
12
13
  });
package/lib/tools.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 七个面向模型的 RSS 工具:list / add / remove / fetch / check / opml_export / opml_import。
2
+ * 九个面向模型的 RSS 工具:list / add / remove / fetch / check / opml_export / opml_import / search / health(fetch 支持增量)。
3
3
  * 直接调用 ctx.tools.register 注册【编译好的 JSON Schema】参数与 canonical 输出。
4
4
  *
5
5
  * @module dsh-rss/tools
@@ -37,6 +37,17 @@ export type FetchLike = (url: string, init?: {
37
37
  redirect?: string;
38
38
  signal?: AbortSignal;
39
39
  }) => Promise<Response>;
40
+ /** 单个订阅源的增量游标:已知条目 id 列表与更新时间。 */
41
+ export interface FeedCursor {
42
+ guids: string[];
43
+ updatedAt: string;
44
+ }
45
+ /** 解析 settings 里的增量游标 JSON,损坏时返回空对象(不阻断功能)。 */
46
+ export declare function parseCursors(json: string): Record<string, FeedCursor>;
47
+ /** 合并本次抓到的条目 id,截断到上限,返回新游标。 */
48
+ export declare function advanceCursor(prev: FeedCursor | undefined, entries: unknown[], now: string): FeedCursor;
49
+ /** 用游标过滤出未见过的条目(无游标时全部视为新)。 */
50
+ export declare function filterNewEntries(cursor: FeedCursor | undefined, entries: unknown[]): unknown[];
40
51
  /**
41
52
  * 构建七个工具定义。每个 execute 惰性读取配置与订阅,错误时抛出中文指引。
42
53
  * @param config - 已解析配置。
package/lib/tools.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 七个面向模型的 RSS 工具:list / add / remove / fetch / check / opml_export / opml_import。
2
+ * 九个面向模型的 RSS 工具:list / add / remove / fetch / check / opml_export / opml_import / search / health(fetch 支持增量)。
3
3
  * 直接调用 ctx.tools.register 注册【编译好的 JSON Schema】参数与 canonical 输出。
4
4
  *
5
5
  * @module dsh-rss/tools
@@ -295,6 +295,153 @@ function renderOpmlImport(_args, value) {
295
295
  }
296
296
  return [{ type: 'text', text: lines.join('\n') }];
297
297
  }
298
+ const CURSOR_GUID_LIMIT = 200;
299
+ /** 条目唯一 id:优先 guid,退回 link,再退回 标题|发布时间。 */
300
+ function entryId(entry) {
301
+ const rec = asRecord(entry);
302
+ const guid = typeof rec.guid === 'string' && rec.guid !== '' ? rec.guid : '';
303
+ if (guid !== '')
304
+ return guid;
305
+ const link = typeof rec.link === 'string' && rec.link !== '' ? rec.link : '';
306
+ if (link !== '')
307
+ return link;
308
+ const title = typeof rec.title === 'string' ? rec.title : '';
309
+ const pubDate = typeof rec.pubDate === 'string' ? rec.pubDate : '';
310
+ return title + '|' + pubDate;
311
+ }
312
+ /** 解析 settings 里的增量游标 JSON,损坏时返回空对象(不阻断功能)。 */
313
+ export function parseCursors(json) {
314
+ if (json.trim() === '')
315
+ return {};
316
+ try {
317
+ const value = JSON.parse(json);
318
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
319
+ return {};
320
+ const out = {};
321
+ for (const [url, cur] of Object.entries(value)) {
322
+ const rec = typeof cur === 'object' && cur !== null ? cur : {};
323
+ const guids = Array.isArray(rec.guids) ? rec.guids.filter((g) => typeof g === 'string') : [];
324
+ out[url] = { guids, updatedAt: typeof rec.updatedAt === 'string' ? rec.updatedAt : '' };
325
+ }
326
+ return out;
327
+ }
328
+ catch {
329
+ return {};
330
+ }
331
+ }
332
+ /** 合并本次抓到的条目 id,截断到上限,返回新游标。 */
333
+ export function advanceCursor(prev, entries, now) {
334
+ const known = new Set(prev?.guids ?? []);
335
+ for (const entry of entries)
336
+ known.add(entryId(entry));
337
+ const guids = [...known];
338
+ return { guids: guids.slice(-CURSOR_GUID_LIMIT), updatedAt: now };
339
+ }
340
+ /** 用游标过滤出未见过的条目(无游标时全部视为新)。 */
341
+ export function filterNewEntries(cursor, entries) {
342
+ if (cursor === undefined || cursor.guids.length === 0)
343
+ return entries;
344
+ const known = new Set(cursor.guids);
345
+ return entries.filter((entry) => !known.has(entryId(entry)));
346
+ }
347
+ // ---------- 搜索辅助 ----------
348
+ /** 解析 since 参数为毫秒时间戳,非法时报错。 */
349
+ function parseSinceMs(input) {
350
+ if (input === undefined)
351
+ return null;
352
+ const text = input.trim();
353
+ const ms = /^\d{4}-\d{2}-\d{2}$/.test(text) ? Date.parse(text + 'T00:00:00Z') : Date.parse(text);
354
+ if (Number.isNaN(ms)) {
355
+ throw new Error('since 参数不是有效日期,请给形如 2026-08-01 或 2026-08-01T00:00:00Z 的值。');
356
+ }
357
+ return ms;
358
+ }
359
+ /** 关键词是否命中条目的标题/摘要/正文/作者/分类(不区分大小写)。 */
360
+ function entryMatches(entry, queryLower) {
361
+ const rec = asRecord(entry);
362
+ const fields = [];
363
+ for (const key of ['title', 'summary', 'content', 'author']) {
364
+ const value = rec[key];
365
+ if (typeof value === 'string')
366
+ fields.push(value);
367
+ }
368
+ const categories = rec.categories;
369
+ if (Array.isArray(categories)) {
370
+ for (const category of categories) {
371
+ if (typeof category === 'string')
372
+ fields.push(category);
373
+ }
374
+ }
375
+ return fields.some((value) => value.toLowerCase().includes(queryLower));
376
+ }
377
+ // ---------- 搜索与健康自检输出 Schema ----------
378
+ const searchHitSchema = {
379
+ type: 'object',
380
+ properties: {
381
+ feedName: { type: 'string' },
382
+ feedUrl: { type: 'string' },
383
+ title: { type: 'string' },
384
+ link: { type: 'string' },
385
+ pubDate: { type: 'string' },
386
+ summary: { type: 'string' },
387
+ },
388
+ additionalProperties: true,
389
+ };
390
+ const searchSchema = {
391
+ type: 'object',
392
+ properties: {
393
+ count: { type: 'integer' },
394
+ hits: { type: 'array', items: searchHitSchema },
395
+ failedFeeds: { type: 'array', items: { type: 'object', additionalProperties: true } },
396
+ },
397
+ additionalProperties: true,
398
+ };
399
+ const healthSchema = {
400
+ type: 'object',
401
+ properties: {
402
+ ok: { type: 'boolean' },
403
+ plugin: { type: 'string' },
404
+ feedCount: { type: 'integer' },
405
+ proxyConfigured: { type: 'boolean' },
406
+ cursorCount: { type: 'integer' },
407
+ parserSelfTest: { type: 'boolean' },
408
+ config: { type: 'object', additionalProperties: true },
409
+ },
410
+ additionalProperties: true,
411
+ };
412
+ /** rss_search 的渲染。 */
413
+ function renderSearch(_args, value) {
414
+ const rec = asRecord(value);
415
+ const hits = Array.isArray(rec.hits) ? rec.hits : [];
416
+ const failed = Array.isArray(rec.failedFeeds) ? rec.failedFeeds : [];
417
+ const lines = ['搜索命中 ' + hits.length + ' 条:'];
418
+ for (const hit of hits) {
419
+ const item = asRecord(hit);
420
+ const title = typeof item.title === 'string' && item.title !== '' ? item.title : '(无标题)';
421
+ const from = typeof item.feedName === 'string' && item.feedName !== '' ? '【' + item.feedName + '】' : '';
422
+ const pubDate = typeof item.pubDate === 'string' && item.pubDate !== '' ? '(' + item.pubDate + ')' : '';
423
+ const link = typeof item.link === 'string' && item.link !== '' ? ' ' + item.link : '';
424
+ lines.push('- ' + from + title + pubDate + link);
425
+ }
426
+ for (const item of failed) {
427
+ const row = asRecord(item);
428
+ lines.push('- 抓取失败 ' + String(row.name ?? row.url ?? '') + ':' + String(row.error ?? ''));
429
+ }
430
+ return [{ type: 'text', text: lines.join('\n') }];
431
+ }
432
+ /** rss_health 的渲染。 */
433
+ function renderHealth(_args, value) {
434
+ const rec = asRecord(value);
435
+ const ok = rec.ok === true;
436
+ const lines = [
437
+ 'dsh-rss 自检' + (ok ? ':正常。' : ':发现异常,请查看下方详情。'),
438
+ '- 订阅源数量:' + String(rec.feedCount ?? 0),
439
+ '- 特殊代理(梯子):' + (rec.proxyConfigured === true ? '已配置' : '未配置'),
440
+ '- 增量游标数量:' + String(rec.cursorCount ?? 0),
441
+ '- 解析器自检:' + (rec.parserSelfTest === true ? '通过' : '失败'),
442
+ ];
443
+ return [{ type: 'text', text: lines.join('\n') }];
444
+ }
298
445
  // ---------- 工具构建 ----------
299
446
  /**
300
447
  * 构建七个工具定义。每个 execute 惰性读取配置与订阅,错误时抛出中文指引。
@@ -377,13 +524,26 @@ export function buildRssTools(config, settingsScope, fetchImpl) {
377
524
  },
378
525
  timeoutMs: TIMEOUT_MS,
379
526
  };
527
+ const readCursorsJson = () => {
528
+ const value = settingsScope.get();
529
+ return value !== null && typeof value === 'object' && typeof value.cursorsJson === 'string' ? value.cursorsJson : '';
530
+ };
531
+ const persistCursors = async (cursors) => {
532
+ try {
533
+ await settingsScope.update({ cursorsJson: JSON.stringify(cursors) });
534
+ }
535
+ catch (error) {
536
+ throw new Error('写入增量游标失败(可能已被其他会话修改,请重试):' + (error instanceof Error ? error.message : String(error)));
537
+ }
538
+ };
380
539
  const rssFetch = {
381
540
  name: 'rss_fetch',
382
- description: '抓取并解析一个 RSS/Atom 订阅源,返回源信息与最新条目(标题、链接、guid、发布时间、作者、摘要、分类、附件)。url 与 name 至少给一个:name 查已订阅源(同名多个时需改用 url),url 直接抓取任意地址。limit 控制返回条数(1-100,默认 20),超过部分以 truncated=true 提示。',
541
+ description: '抓取并解析一个 RSS/Atom 订阅源,返回源信息与最新条目(标题、链接、guid、发布时间、作者、摘要、分类、附件)。url 与 name 至少给一个:name 查已订阅源(同名多个时需改用 url),url 直接抓取任意地址。limit 控制返回条数(1-100,默认 20),超过部分以 truncated=true 提示。incremental=true 时启用增量模式:只返回上次抓取以来的新条目(按条目 id 去重,游标持久化在 settings),适合定时巡检省 token。',
383
542
  parameters: compileParameters({
384
543
  url: { type: 'string', description: '订阅源地址(可选,与 name 至少给一个)。' },
385
544
  name: { type: 'string', description: '已订阅源的名字(可选,可用 rss_list 查看)。' },
386
545
  limit: { type: 'integer', description: '返回条数,1-100,默认 20。' },
546
+ incremental: { type: 'boolean', description: '增量模式:只返回上次抓取后的新条目(默认 false)。' },
387
547
  }),
388
548
  output: {
389
549
  schema: fetchSchema,
@@ -408,10 +568,19 @@ export function buildRssTools(config, settingsScope, fetchImpl) {
408
568
  }
409
569
  assertHttpUrl(url);
410
570
  const limit = clampedInteger(args, 'limit', 20, 1, 100);
571
+ const incremental = args.incremental === true;
411
572
  const { xml, finalUrl } = await fetchFeedXml(url, cfg, fetchImpl);
412
573
  const parsed = parseFeed(xml, finalUrl);
413
- const entries = parsed.entries.slice(0, limit);
414
- return { url: finalUrl, feed: parsed.feed, entries, truncated: parsed.entries.length > limit };
574
+ if (!incremental) {
575
+ const entries = parsed.entries.slice(0, limit);
576
+ return { url: finalUrl, feed: parsed.feed, entries, truncated: parsed.entries.length > limit };
577
+ }
578
+ const cursors = parseCursors(readCursorsJson());
579
+ const fresh = filterNewEntries(cursors[url], parsed.entries);
580
+ cursors[url] = advanceCursor(cursors[url], parsed.entries, new Date().toISOString());
581
+ await persistCursors(cursors);
582
+ const entries = fresh.slice(0, limit);
583
+ return { url: finalUrl, feed: parsed.feed, entries, truncated: fresh.length > limit, incremental: true, newCount: fresh.length };
415
584
  },
416
585
  timeoutMs: TIMEOUT_MS,
417
586
  };
@@ -491,5 +660,97 @@ export function buildRssTools(config, settingsScope, fetchImpl) {
491
660
  },
492
661
  timeoutMs: TIMEOUT_MS,
493
662
  };
494
- return [rssList, rssAdd, rssRemove, rssFetch, rssCheck, rssOpmlExport, rssOpmlImport];
663
+ const rssSearch = {
664
+ name: 'rss_search',
665
+ description: '跨订阅源搜索条目:实时抓取已订阅源,对标题/摘要/正文/作者/分类做不区分大小写的关键词匹配。query 必填;name 或 url 可限定单个订阅;since 过滤发布日期(如 2026-08-01);limit 控制返回条数(1-50,默认 20)。个别源抓取失败会单独报告,不影响其他源的搜索。',
666
+ parameters: compileParameters({
667
+ query: { type: 'string', required: true, description: '搜索关键词(必填,不区分大小写)。' },
668
+ name: { type: 'string', description: '只搜索这个名字的订阅(可选)。' },
669
+ url: { type: 'string', description: '只搜索这个地址的订阅(可选)。' },
670
+ since: { type: 'string', description: '只要该日期之后发布的条目(可选,如 2026-08-01)。' },
671
+ limit: { type: 'integer', description: '返回条数,1-50,默认 20。' },
672
+ }),
673
+ output: {
674
+ schema: searchSchema,
675
+ render: renderSearch,
676
+ },
677
+ async execute(rawArgs) {
678
+ const args = asRecord(rawArgs);
679
+ const query = requiredString(args, 'query', '搜索关键词').toLowerCase();
680
+ const limit = clampedInteger(args, 'limit', 20, 1, 50);
681
+ const sinceMs = parseSinceMs(optionalString(args, 'since'));
682
+ const urlFilter = optionalString(args, 'url');
683
+ const nameFilter = optionalString(args, 'name');
684
+ let targets = getFeeds();
685
+ if (urlFilter !== undefined)
686
+ targets = targets.filter((feed) => feed.url.toLowerCase() === urlFilter.toLowerCase());
687
+ if (nameFilter !== undefined)
688
+ targets = findFeedsByName(targets, nameFilter);
689
+ if (targets.length === 0) {
690
+ if (urlFilter !== undefined || nameFilter !== undefined) {
691
+ throw new Error('没有找到匹配的订阅。可用 rss_list 查看订阅列表。');
692
+ }
693
+ throw new Error('当前还没有订阅。请先用 rss_add 添加订阅。');
694
+ }
695
+ const hits = [];
696
+ const failedFeeds = [];
697
+ for (const feed of targets) {
698
+ if (hits.length >= limit)
699
+ break;
700
+ try {
701
+ const { xml, finalUrl } = await fetchFeedXml(feed.url, cfg, fetchImpl);
702
+ const parsed = parseFeed(xml, finalUrl);
703
+ for (const entry of parsed.entries) {
704
+ const rec = asRecord(entry);
705
+ const at = Date.parse(typeof rec.pubDate === 'string' ? rec.pubDate : '');
706
+ if (sinceMs !== null && !Number.isNaN(at) && at < sinceMs)
707
+ continue;
708
+ if (entryMatches(entry, query)) {
709
+ hits.push({ feedName: feed.name, feedUrl: feed.url, ...rec });
710
+ if (hits.length >= limit)
711
+ break;
712
+ }
713
+ }
714
+ }
715
+ catch (error) {
716
+ failedFeeds.push({ url: feed.url, name: feed.name, error: error instanceof Error ? error.message : String(error) });
717
+ }
718
+ }
719
+ return { count: hits.length, hits, failedFeeds };
720
+ },
721
+ timeoutMs: Math.min(300000, Math.max(TIMEOUT_MS, cfg.timeoutMs * 10)),
722
+ };
723
+ const rssHealth = {
724
+ name: 'rss_health',
725
+ description: 'dsh-rss 自检:检查配置有效性、订阅数量、增量游标与解析器自检(不发任何网络请求)。遇到问题时先运行本工具定位。',
726
+ parameters: compileParameters({}),
727
+ output: {
728
+ schema: healthSchema,
729
+ render: renderHealth,
730
+ },
731
+ async execute() {
732
+ const feeds = getFeeds();
733
+ const settingsValue = settingsScope.get();
734
+ const cursorsJson = settingsValue !== null && typeof settingsValue === 'object' && typeof settingsValue.cursorsJson === 'string' ? settingsValue.cursorsJson : '';
735
+ let parserSelfTest = false;
736
+ try {
737
+ const sample = parseFeed('<?xml version="1.0"?><rss version="2.0"><channel><title>selftest</title><item><title>item</title></item></channel></rss>', 'https://selftest.local/');
738
+ parserSelfTest = sample.entries.length === 1 && sample.feed.feedType === 'rss';
739
+ }
740
+ catch {
741
+ parserSelfTest = false;
742
+ }
743
+ return {
744
+ ok: parserSelfTest,
745
+ plugin: 'dsh-rss',
746
+ feedCount: feeds.length,
747
+ proxyConfigured: cfg.proxyUrl !== '',
748
+ cursorCount: Object.keys(parseCursors(cursorsJson)).length,
749
+ parserSelfTest,
750
+ config: { timeoutMs: cfg.timeoutMs, maxBodyBytes: cfg.maxBodyBytes, userAgent: cfg.userAgent },
751
+ };
752
+ },
753
+ timeoutMs: TIMEOUT_MS,
754
+ };
755
+ return [rssList, rssAdd, rssRemove, rssFetch, rssCheck, rssOpmlExport, rssOpmlImport, rssSearch, rssHealth];
495
756
  }
package/package.json CHANGED
@@ -1,66 +1,65 @@
1
- {
2
- "name": "dsh-rss",
3
- "version": "0.2.0",
4
- "description": "DSH RSS 订阅工具插件:管理订阅源、OPML 导入导出、抓取解析 RSS/Atom 并保留正文(rss_list / rss_add / rss_remove / rss_fetch / rss_check / rss_opml_export / rss_opml_import)",
5
- "type": "module",
6
- "main": "lib/index.js",
7
- "types": "lib/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./lib/index.d.ts",
11
- "default": "./lib/index.js"
12
- },
13
- "./cordis.patch.yml": "./cordis.patch.yml",
14
- "./package.json": "./package.json"
15
- },
16
- "files": [
17
- "lib",
18
- "cordis.patch.yml",
19
- "README.md",
20
- "README.en.md"
21
- ],
22
- "scripts": {
23
- "build": "tsc -p tsconfig.json",
24
- "prepare": "tsc -p tsconfig.json",
25
- "typecheck": "tsc -p tsconfig.json --noEmit",
26
- "test": "pnpm run build && node --test \"test/*.test.mjs\"",
27
- "prepublishOnly": "pnpm run build"
28
- },
29
- "dsh": {
30
- "bundle": {
31
- "patch": "./cordis.patch.yml"
32
- }
33
- },
34
- "keywords": [
35
- "dsh",
36
- "deepseek-harness",
37
- "plugin",
38
- "rss",
39
- "atom",
40
- "feed"
41
- ],
42
- "license": "MIT",
43
- "engines": {
44
- "node": ">=20"
45
- },
46
- "dependencies": {
47
- "fast-xml-parser": "^5.0.0",
48
- "schemastery": "^3.18.0",
49
- "undici": "^8.10.0",
50
- "yaml": "^2.9.0"
51
- },
52
- "devDependencies": {
53
- "@types/node": "^24.0.0",
54
- "typescript": "^5.6.0"
55
- },
56
- "repository": {
57
- "type": "git",
58
- "url": "git+https://github.com/STARDUSTLC666/dsh-rss.git"
59
- },
60
- "bugs": {
61
- "url": "https://github.com/STARDUSTLC666/dsh-rss/issues"
62
- },
63
- "homepage": "https://github.com/STARDUSTLC666/dsh-rss#readme",
64
- "author": "stardustlc",
65
- "packageManager": "pnpm@11.7.0"
66
- }
1
+ {
2
+ "name": "dsh-rss",
3
+ "version": "0.3.0",
4
+ "description": "DSH RSS 订阅工具插件:管理订阅源、OPML 导入导出、抓取解析 RSS/Atom 并保留正文(rss_list / rss_add / rss_remove / rss_fetch / rss_check / rss_opml_export / rss_opml_import)",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "types": "lib/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/index.d.ts",
11
+ "default": "./lib/index.js"
12
+ },
13
+ "./cordis.patch.yml": "./cordis.patch.yml",
14
+ "./package.json": "./package.json"
15
+ },
16
+ "files": [
17
+ "lib",
18
+ "cordis.patch.yml",
19
+ "README.md",
20
+ "README.en.md"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsc -p tsconfig.json",
24
+ "typecheck": "tsc -p tsconfig.json --noEmit",
25
+ "test": "pnpm run build && node --test \"test/*.test.mjs\"",
26
+ "prepublishOnly": "pnpm run build"
27
+ },
28
+ "dsh": {
29
+ "bundle": {
30
+ "patch": "./cordis.patch.yml"
31
+ }
32
+ },
33
+ "keywords": [
34
+ "dsh",
35
+ "deepseek-harness",
36
+ "plugin",
37
+ "rss",
38
+ "atom",
39
+ "feed"
40
+ ],
41
+ "license": "MIT",
42
+ "engines": {
43
+ "node": ">=22"
44
+ },
45
+ "dependencies": {
46
+ "fast-xml-parser": "^5.0.0",
47
+ "schemastery": "^3.18.0",
48
+ "undici": "^8.10.0",
49
+ "yaml": "^2.9.0"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^24.0.0",
53
+ "typescript": "^5.6.0"
54
+ },
55
+ "repository": {
56
+ "type": "git",
57
+ "url": "git+https://github.com/STARDUSTLC666/dsh-rss.git"
58
+ },
59
+ "bugs": {
60
+ "url": "https://github.com/STARDUSTLC666/dsh-rss/issues"
61
+ },
62
+ "homepage": "https://github.com/STARDUSTLC666/dsh-rss#readme",
63
+ "author": "stardustlc",
64
+ "packageManager": "pnpm@11.7.0"
65
+ }