rssany 0.1.2 → 0.1.5

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 (75) hide show
  1. package/README.md +28 -50
  2. package/app/plugins/builtin/agi-eval-evaluation.rssany.js +188 -0
  3. package/app/plugins/builtin/amii-research-talent.rssany.js +73 -0
  4. package/app/plugins/builtin/anthropic-research.rssany.js +155 -0
  5. package/app/plugins/builtin/appen-resources.rssany.js +155 -0
  6. package/app/plugins/builtin/baai-wudao-paper-article.rssany.js +185 -0
  7. package/app/plugins/builtin/baaidata-csdn.rssany.js +242 -0
  8. package/app/plugins/builtin/baidu-research.rssany.js +222 -0
  9. package/app/plugins/builtin/brightdata-blog.rssany.js +301 -0
  10. package/app/plugins/builtin/bytedance-seed-research.rssany.js +231 -0
  11. package/app/plugins/builtin/five-radar.rssany.js +490 -0
  12. package/app/plugins/builtin/flageval-news.rssany.js +118 -0
  13. package/app/plugins/builtin/google-deepmind-research.rssany.js +223 -0
  14. package/app/plugins/builtin/google-research-datasets.rssany.js +171 -0
  15. package/app/plugins/builtin/google-research.rssany.js +220 -0
  16. package/app/plugins/builtin/google.rssany.js +187 -0
  17. package/app/plugins/builtin/hacker-news-newest.rssany.js +130 -0
  18. package/app/plugins/builtin/harvard-dataverse.rssany.js +166 -0
  19. package/app/plugins/builtin/huaweicloud-bbs-blogs.rssany.js +185 -0
  20. package/app/plugins/builtin/lingowhale.rssany.js +119 -0
  21. package/app/plugins/builtin/meituan-tech.rssany.js +130 -0
  22. package/app/plugins/builtin/meta-ai-publications.rssany.js +221 -0
  23. package/app/plugins/builtin/mila-quebec.rssany.js +199 -0
  24. package/app/plugins/builtin/mit-csail-research.rssany.js +208 -0
  25. package/app/plugins/builtin/moonshot.rssany.js +127 -0
  26. package/app/plugins/builtin/opendatalab-news.rssany.js +174 -0
  27. package/app/plugins/builtin/opendatalab.rssany.js +109 -0
  28. package/app/plugins/builtin/opendrivelab-autonomous-driving.rssany.js +114 -0
  29. package/app/plugins/builtin/opendrivelab-embodiedai.rssany.js +114 -0
  30. package/app/plugins/builtin/opendrivelab-publications.rssany.js +130 -0
  31. package/app/plugins/builtin/opendrivelab.rssany.js +333 -0
  32. package/app/plugins/builtin/paperswithcode.rssany.js +227 -0
  33. package/app/plugins/builtin/pjlab-adg-publications.rssany.js +202 -0
  34. package/app/plugins/builtin/rss.rssany.js +11 -1
  35. package/app/plugins/builtin/selectdataset.rssany.js +206 -0
  36. package/app/plugins/builtin/sensetime-tech-achievements.rssany.js +154 -0
  37. package/app/plugins/builtin/supervisely-blog.rssany.js +159 -0
  38. package/app/plugins/builtin/uci-ml-repository.rssany.js +111 -0
  39. package/app/plugins/builtin/venturebeat.rssany.js +97 -0
  40. package/app/plugins/builtin/worldlabs.rssany.js +129 -0
  41. package/app/plugins/builtin/x.rssany.js +159 -0
  42. package/app/plugins/builtin/xiaohongshu.rssany.js +283 -0
  43. package/app/plugins/builtin/zhipu-research.rssany.js +334 -0
  44. package/dist/index.js +79 -9
  45. package/dist/index.js.map +1 -1
  46. package/package.json +1 -1
  47. package/webui/build/200.html +6 -6
  48. package/webui/build/_app/immutable/assets/0.BB88QFoe.css +1 -0
  49. package/webui/build/_app/immutable/assets/{homeFeedPanelStore.BopJZtHu.css → homeFeedPanelStore.iOmfP2qL.css} +1 -1
  50. package/webui/build/_app/immutable/chunks/CZD-YNDw.js +31 -0
  51. package/webui/build/_app/immutable/chunks/{DcAshVxe.js → D6VIKef0.js} +1 -1
  52. package/webui/build/_app/immutable/chunks/{EIZIMsXK.js → Dbqx2mXq.js} +1 -1
  53. package/webui/build/_app/immutable/chunks/DeX-oq5W.js +41 -0
  54. package/webui/build/_app/immutable/chunks/{BXCWEhUd.js → dhB8G5Is.js} +1 -1
  55. package/webui/build/_app/immutable/entry/{app.DdgnooOk.js → app.XPso7q7g.js} +2 -2
  56. package/webui/build/_app/immutable/entry/start.Db4snNCd.js +1 -0
  57. package/webui/build/_app/immutable/nodes/0.BKTQePmA.js +11 -0
  58. package/webui/build/_app/immutable/nodes/{1.5DFDaT4c.js → 1.BS3_Rfxm.js} +1 -1
  59. package/webui/build/_app/immutable/nodes/{10.OVK4i9XE.js → 10.CyyxDCIS.js} +1 -1
  60. package/webui/build/_app/immutable/nodes/{11.Dhn_rO4A.js → 11.CtYgIaGj.js} +1 -1
  61. package/webui/build/_app/immutable/nodes/{14.B_KpJLxn.js → 14.D5OEGPR2.js} +1 -1
  62. package/webui/build/_app/immutable/nodes/{15.RaWaA-0I.js → 15.B4dFN1Gk.js} +1 -1
  63. package/webui/build/_app/immutable/nodes/{16.DSUgqolV.js → 16.M7ZII7tl.js} +1 -1
  64. package/webui/build/_app/immutable/nodes/{3.wQvGs9w-.js → 3.7r8v7qkm.js} +1 -1
  65. package/webui/build/_app/immutable/nodes/{5.CCtn90c0.js → 5.CHIzoGrb.js} +1 -1
  66. package/webui/build/_app/immutable/nodes/{6.C2_mjW1u.js → 6.BDBqx-GY.js} +1 -1
  67. package/webui/build/_app/immutable/nodes/{7.Dwz6W7A1.js → 7.D5czsDmz.js} +1 -1
  68. package/webui/build/_app/immutable/nodes/{8.DzkEw6rx.js → 8.pjVNsCdV.js} +1 -1
  69. package/webui/build/_app/immutable/nodes/{9.DtlXEwe1.js → 9.CsARv1BH.js} +1 -1
  70. package/webui/build/_app/version.json +1 -1
  71. package/webui/build/_app/immutable/assets/0.C6Q_nuW9.css +0 -1
  72. package/webui/build/_app/immutable/chunks/CkUAV0m0.js +0 -41
  73. package/webui/build/_app/immutable/chunks/CtijX1u3.js +0 -31
  74. package/webui/build/_app/immutable/entry/start.DhJaJZhR.js +0 -1
  75. package/webui/build/_app/immutable/nodes/0.BE05Cuc4.js +0 -11
@@ -0,0 +1,109 @@
1
+ let _deps;
2
+
3
+
4
+ const OPENDATALAB_ORIGIN = "https://opendatalab.org.cn";
5
+ const OPENDATALAB_LIST_API = `${OPENDATALAB_ORIGIN}/datasets/api/v3/datasets/list`;
6
+
7
+ function normalizeText(text) {
8
+ return (text ?? "").replace(/\s+/g, " ").trim();
9
+ }
10
+
11
+ function hashGuid(input) {
12
+ return _deps.createHash("sha256").update(input).digest("hex");
13
+ }
14
+
15
+ function toDate(value) {
16
+ const raw = typeof value === "string" ? Number(value) : value;
17
+ if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0) return undefined;
18
+ const ms = raw < 1e12 ? raw * 1000 : raw;
19
+ const date = new Date(ms);
20
+ return Number.isNaN(date.getTime()) ? undefined : date;
21
+ }
22
+
23
+ function pickPubDate(record) {
24
+ const candidates = [record?.lastUpdateTime, record?.updatedAt, record?.publicTime, record?.createdAt];
25
+ for (const value of candidates) {
26
+ const parsed = toDate(value);
27
+ if (parsed) return parsed;
28
+ }
29
+ return new Date();
30
+ }
31
+
32
+ function toDatasetLink(name) {
33
+ const normalized = normalizeText(name);
34
+ if (!normalized) return null;
35
+ const parts = normalized.split("/").map((x) => normalizeText(x)).filter(Boolean);
36
+ if (parts.length < 2) return null;
37
+ const encodedPath = parts.map((x) => encodeURIComponent(x)).join("/");
38
+ return `${OPENDATALAB_ORIGIN}/${encodedPath}`;
39
+ }
40
+
41
+ function toFeedItem(record) {
42
+ if (!record || typeof record !== "object") return null;
43
+ const title = normalizeText(record.displayName || record.name || "");
44
+ const link = toDatasetLink(record.name);
45
+ if (!title || !link) return null;
46
+
47
+ const summary = normalizeText(record?.introduction?.zh || record?.introduction?.en || "");
48
+ const author = normalizeText(record?.createdBy?.name || record?.updatedBy || "");
49
+
50
+ return {
51
+ guid: hashGuid(link),
52
+ title,
53
+ link,
54
+ pubDate: pickPubDate(record),
55
+ author: author || undefined,
56
+ summary: summary || undefined,
57
+ sourceId: "opendatalab",
58
+ };
59
+ }
60
+
61
+ function parsePaginationFromSourceId(sourceId) {
62
+ const defaults = { pageNo: 1, pageSize: 30 };
63
+ try {
64
+ const url = new URL(sourceId);
65
+ const pageNo = Number(url.searchParams.get("pageNo") ?? defaults.pageNo);
66
+ const pageSize = Number(url.searchParams.get("pageSize") ?? defaults.pageSize);
67
+ const safePageNo = Number.isInteger(pageNo) && pageNo > 0 ? pageNo : defaults.pageNo;
68
+ const safePageSize = Number.isInteger(pageSize) && pageSize >= 1 && pageSize <= 100 ? pageSize : defaults.pageSize;
69
+ return { pageNo: safePageNo, pageSize: safePageSize };
70
+ } catch {
71
+ return defaults;
72
+ }
73
+ }
74
+
75
+ async function fetchItems(sourceId, ctx) {
76
+ _deps = ctx.deps;
77
+ const { pageNo, pageSize } = parsePaginationFromSourceId(sourceId);
78
+ const response = await fetch(OPENDATALAB_LIST_API, {
79
+ method: "POST",
80
+ headers: {
81
+ "Content-Type": "application/json",
82
+ "Accept": "application/json",
83
+ },
84
+ body: JSON.stringify({ pageNo, pageSize }),
85
+ });
86
+
87
+ if (!response.ok) {
88
+ throw new Error(`[opendatalab] 请求列表接口失败: HTTP ${response.status}`);
89
+ }
90
+
91
+ const payload = await response.json().catch(() => null);
92
+ const list = payload?.data?.list;
93
+ if (!Array.isArray(list)) {
94
+ const msg = normalizeText(payload?.msg) || "返回结构异常";
95
+ throw new Error(`[opendatalab] 列表接口响应不可用: ${msg}`);
96
+ }
97
+
98
+ const items = list.map((record) => toFeedItem(record)).filter(Boolean);
99
+ if (items.length === 0) {
100
+ throw new Error("[opendatalab] 未解析到条目,接口结构可能已变化");
101
+ }
102
+ return items;
103
+ }
104
+
105
+ export default {
106
+ id: "opendatalab",
107
+ listUrlPattern: /^https?:\/\/(www\.)?opendatalab\.(org\.cn|com)\/?(?:datasets\/?)?(?:\?.*)?$/i,
108
+ fetchItems,
109
+ };
@@ -0,0 +1,114 @@
1
+ let _deps;
2
+
3
+ // OpenDriveLab Autonomous Driving 插件:抓取时间线条目并输出 FeedItem(不含 enrich)
4
+
5
+
6
+ function normalizeText(text) {
7
+ return (text ?? "").replace(/\s+/g, " ").trim();
8
+ }
9
+
10
+ function hashGuid(input) {
11
+ return _deps.createHash("sha256").update(input).digest("hex");
12
+ }
13
+
14
+ function toAbsoluteHttpUrl(rawHref, baseUrl) {
15
+ if (!rawHref) return null;
16
+ const href = rawHref.trim();
17
+ if (!href || href.startsWith("#") || href.startsWith("javascript:")) return null;
18
+ try {
19
+ const url = new URL(href, baseUrl);
20
+ if (!/^https?:$/i.test(url.protocol)) return null;
21
+ return url.href;
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+
27
+ function parsePubDate(dateText) {
28
+ const normalized = normalizeText(dateText);
29
+ const m = normalized.match(/(\d{4})[./-](\d{1,2})[./-](\d{1,2})/);
30
+ if (!m) return new Date();
31
+ const year = Number(m[1]);
32
+ const month = Number(m[2]);
33
+ const day = Number(m[3]);
34
+ if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) {
35
+ return new Date();
36
+ }
37
+ // 统一使用 UTC 中午,避免仅日期时的时区偏移问题。
38
+ return new Date(Date.UTC(year, month - 1, day, 12, 0, 0));
39
+ }
40
+
41
+ const AUX_LINK_TEXTS = new Set([
42
+ "paper",
43
+ "page",
44
+ "github",
45
+ "dataset",
46
+ "hugging face",
47
+ "video",
48
+ "blog",
49
+ "poster",
50
+ "slides",
51
+ "arxiv",
52
+ "code",
53
+ "demo",
54
+ "解讀",
55
+ ]);
56
+
57
+ function findTitleAnchor(li, finalUrl) {
58
+ const anchors = li.querySelectorAll("a[href]");
59
+ let fallback = null;
60
+
61
+ for (const anchor of anchors) {
62
+ const title = normalizeText(anchor.textContent);
63
+ const link = toAbsoluteHttpUrl(anchor.getAttribute("href"), finalUrl);
64
+ if (!title || !link) continue;
65
+
66
+ if (!fallback) fallback = { title, link };
67
+ if (AUX_LINK_TEXTS.has(title.toLowerCase())) continue;
68
+ if (title.length < 8) continue;
69
+ return { title, link };
70
+ }
71
+
72
+ return fallback;
73
+ }
74
+
75
+ async function fetchItems(sourceId, ctx) {
76
+ _deps = ctx.deps;
77
+ const { html, finalUrl } = await ctx.fetchHtml(sourceId, { waitMs: 3500 });
78
+ const root = _deps.parseHtml(html);
79
+
80
+ const seenLinks = new Set();
81
+ const items = [];
82
+ const rows = root.querySelectorAll("li");
83
+ for (const row of rows) {
84
+ const dateText = normalizeText(row.querySelector("time")?.textContent);
85
+ if (!dateText) continue;
86
+
87
+ const titleAnchor = findTitleAnchor(row, finalUrl);
88
+ if (!titleAnchor) continue;
89
+ if (seenLinks.has(titleAnchor.link)) continue;
90
+ seenLinks.add(titleAnchor.link);
91
+
92
+ const summaryText = normalizeText(row.querySelector("i")?.textContent);
93
+ items.push({
94
+ guid: hashGuid(titleAnchor.link),
95
+ title: titleAnchor.title,
96
+ link: titleAnchor.link,
97
+ pubDate: parsePubDate(dateText),
98
+ author: "OpenDriveLab",
99
+ summary: summaryText || undefined,
100
+ sourceId: "opendrivelab-autonomous-driving",
101
+ });
102
+ }
103
+
104
+ if (items.length === 0) {
105
+ throw new Error("[opendrivelab-autonomous-driving] 未解析到条目,页面结构可能已变化");
106
+ }
107
+ return items;
108
+ }
109
+
110
+ export default {
111
+ id: "opendrivelab-autonomous-driving",
112
+ listUrlPattern: /^https?:\/\/(www\.)?opendrivelab\.com\/AutonomousDriving\/?(\?.*)?$/i,
113
+ fetchItems,
114
+ };
@@ -0,0 +1,114 @@
1
+ let _deps;
2
+
3
+
4
+ const SITE_ID = "opendrivelab-embodiedai";
5
+ const DATE_RE = /\b(20\d{2})[./-](\d{1,2})[./-](\d{1,2})\b/;
6
+ const ACTION_LINK_LABELS = new Set([
7
+ "paper",
8
+ "page",
9
+ "blog",
10
+ "github",
11
+ "video",
12
+ "dataset",
13
+ "challenge",
14
+ "hugging face",
15
+ "hardware guide",
16
+ ]);
17
+
18
+ function normalizeText(text) {
19
+ return (text ?? "").replace(/\s+/g, " ").trim();
20
+ }
21
+
22
+ function hashGuid(input) {
23
+ return _deps.createHash("sha256").update(input).digest("hex");
24
+ }
25
+
26
+ function toAbsoluteHttpUrl(rawHref, baseUrl) {
27
+ if (!rawHref) return null;
28
+ const href = rawHref.trim();
29
+ if (!href || href.startsWith("#") || href.startsWith("javascript:")) return null;
30
+ try {
31
+ const url = new URL(href, baseUrl);
32
+ if (!/^https?:$/i.test(url.protocol)) return null;
33
+ return url.href;
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ function parseDate(dateText) {
40
+ const text = normalizeText(dateText);
41
+ const m = text.match(DATE_RE);
42
+ if (!m) return undefined;
43
+ const [, y, mm, dd] = m;
44
+ const date = new Date(Date.UTC(Number(y), Number(mm) - 1, Number(dd), 12, 0, 0));
45
+ return Number.isNaN(date.getTime()) ? undefined : date;
46
+ }
47
+
48
+ function isActionLabel(text) {
49
+ const normalized = normalizeText(text).toLowerCase();
50
+ return ACTION_LINK_LABELS.has(normalized);
51
+ }
52
+
53
+ function findTitleAnchor(liNode, finalUrl) {
54
+ const anchors = liNode.querySelectorAll("a[href]");
55
+ let fallback = null;
56
+
57
+ for (const anchor of anchors) {
58
+ const title = normalizeText(anchor.textContent);
59
+ if (!title || isActionLabel(title)) continue;
60
+ const link = toAbsoluteHttpUrl(anchor.getAttribute("href"), finalUrl);
61
+ if (!link) continue;
62
+ if (!fallback) fallback = { anchor, link, title };
63
+ if (title.length >= 12) return { anchor, link, title };
64
+ }
65
+
66
+ return fallback;
67
+ }
68
+
69
+ function buildItemsFromHtml(html, finalUrl) {
70
+ const root = _deps.parseHtml(html);
71
+ const items = [];
72
+ const seen = new Set();
73
+ const liNodes = root.querySelectorAll("li");
74
+
75
+ for (const li of liNodes) {
76
+ const dateText = normalizeText(li.querySelector("time")?.textContent);
77
+ if (!dateText) continue;
78
+
79
+ const titleAnchor = findTitleAnchor(li, finalUrl);
80
+ if (!titleAnchor) continue;
81
+ if (seen.has(titleAnchor.link)) continue;
82
+
83
+ const summary = normalizeText(li.querySelector("i")?.textContent);
84
+ items.push({
85
+ guid: hashGuid(titleAnchor.link),
86
+ title: titleAnchor.title,
87
+ link: titleAnchor.link,
88
+ pubDate: parseDate(dateText) ?? new Date(),
89
+ summary: summary || undefined,
90
+ });
91
+ seen.add(titleAnchor.link);
92
+ }
93
+
94
+ return items;
95
+ }
96
+
97
+ async function fetchItems(sourceId, ctx) {
98
+ _deps = ctx.deps;
99
+ const { html, finalUrl } = await ctx.fetchHtml(sourceId, { waitMs: 3500 });
100
+ const items = buildItemsFromHtml(html, finalUrl);
101
+ if (items.length > 0) return items;
102
+
103
+ const text = normalizeText(_deps.parseHtml(html).textContent).toLowerCase();
104
+ if (text.includes("just a moment") || text.includes("checking your browser")) {
105
+ throw new Error(`[${SITE_ID}] 命中站点风控验证页,当前会话无法稳定抓取`);
106
+ }
107
+ throw new Error(`[${SITE_ID}] 未解析到 Embodied AI 条目,页面结构可能已变化`);
108
+ }
109
+
110
+ export default {
111
+ id: SITE_ID,
112
+ listUrlPattern: /^https?:\/\/(www\.)?opendrivelab\.com\/EmbodiedAI\/?(\?.*)?$/i,
113
+ fetchItems,
114
+ };
@@ -0,0 +1,130 @@
1
+ let _deps;
2
+
3
+
4
+
5
+ const VENUE_HINT_RE = /(20\d{2}|cvpr|iccv|eccv|neurips|iclr|aaai|ijcv|tpami|icra|rss|corl|preprint|arxiv)/i;
6
+ const BLOCKED_HOST_RE = /(^|\.)scholar\.google\.com$|(^|\.)github\.com$|(^|\.)img\.shields\.io$|(^|\.)youtube\.com$|(^|\.)youtu\.be$|(^|\.)zhihu\.com$|(^|\.)mp\.weixin\.qq\.com$|(^|\.)cvpr\d{4}\.thecvf\.com$/i;
7
+ const AWARD_RE = /\baward\b/i;
8
+
9
+
10
+ function normalizeText(text) {
11
+ return (text ?? "").replace(/\s+/g, " ").trim();
12
+ }
13
+
14
+
15
+ function toHttpUrl(rawHref, baseUrl) {
16
+ if (!rawHref) return null;
17
+ try {
18
+ const url = new URL(rawHref, baseUrl);
19
+ if (!/^https?:$/i.test(url.protocol)) return null;
20
+ return url;
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+
26
+
27
+ function hashGuid(input) {
28
+ return _deps.createHash("sha256").update(input).digest("hex");
29
+ }
30
+
31
+
32
+ function isLikelyPaperTitle(title) {
33
+ if (!title || title.length < 20) return false;
34
+ if (AWARD_RE.test(title)) return false;
35
+ const words = title.match(/[A-Za-z0-9][A-Za-z0-9-]*/g) ?? [];
36
+ return words.length >= 4;
37
+ }
38
+
39
+
40
+ function findYear(text) {
41
+ const m = normalizeText(text).match(/\b(20\d{2})\b/);
42
+ if (!m) return undefined;
43
+ const year = Number(m[1]);
44
+ if (year < 2000 || year > 2099) return undefined;
45
+ return year;
46
+ }
47
+
48
+
49
+ function extractContext(anchor) {
50
+ let node = anchor;
51
+ let summary;
52
+ let category;
53
+ let year;
54
+
55
+ for (let i = 0; i < 8 && node; i += 1) {
56
+ if (!summary) {
57
+ const summaryNode = node.querySelector?.("i");
58
+ const summaryText = normalizeText(summaryNode?.textContent);
59
+ if (summaryText && summaryText.length >= 24) summary = summaryText;
60
+ }
61
+
62
+ const spanTexts = (node.querySelectorAll?.("span") ?? [])
63
+ .map((el) => normalizeText(el.textContent))
64
+ .filter(Boolean);
65
+
66
+ if (!category) {
67
+ category = spanTexts.find((text) => VENUE_HINT_RE.test(text) && !AWARD_RE.test(text));
68
+ }
69
+ if (year == null) {
70
+ for (const text of spanTexts) {
71
+ const parsed = findYear(text);
72
+ if (parsed != null) {
73
+ year = parsed;
74
+ break;
75
+ }
76
+ }
77
+ }
78
+
79
+ node = node.parentNode ?? null;
80
+ }
81
+
82
+ return { summary, category, year };
83
+ }
84
+
85
+
86
+ async function fetchItems(sourceId, ctx) {
87
+ _deps = ctx.deps;
88
+ const { html, finalUrl } = await ctx.fetchHtml(sourceId, { waitMs: 3500 });
89
+ const root = _deps.parseHtml(html);
90
+ const main = root.querySelector("main") ?? root;
91
+ const anchors = main.querySelectorAll("a[href]");
92
+ const seen = new Set();
93
+ const items = [];
94
+
95
+ for (const anchor of anchors) {
96
+ const title = normalizeText(anchor.textContent);
97
+ if (!isLikelyPaperTitle(title)) continue;
98
+
99
+ const url = toHttpUrl(anchor.getAttribute("href"), finalUrl);
100
+ if (!url) continue;
101
+ if (BLOCKED_HOST_RE.test(url.hostname)) continue;
102
+
103
+ const link = url.href;
104
+ if (seen.has(link)) continue;
105
+ seen.add(link);
106
+
107
+ const { summary, category, year } = extractContext(anchor);
108
+ const pubDate = year != null ? new Date(Date.UTC(year, 0, 1)) : new Date();
109
+
110
+ items.push({
111
+ guid: hashGuid(link),
112
+ title,
113
+ link,
114
+ pubDate,
115
+ summary: summary || undefined,
116
+ });
117
+ }
118
+
119
+ if (items.length === 0) {
120
+ throw new Error("[opendrivelab-publications] 未解析到论文条目,页面结构可能已变化");
121
+ }
122
+ return items;
123
+ }
124
+
125
+
126
+ export default {
127
+ id: "opendrivelab-publications",
128
+ listUrlPattern: /^https?:\/\/(www\.)?opendrivelab\.com\/publications\/?(\?.*)?$/i,
129
+ fetchItems,
130
+ };