rssany 0.1.4 → 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 (73) hide show
  1. package/app/plugins/builtin/agi-eval-evaluation.rssany.js +188 -0
  2. package/app/plugins/builtin/amii-research-talent.rssany.js +73 -0
  3. package/app/plugins/builtin/anthropic-research.rssany.js +155 -0
  4. package/app/plugins/builtin/appen-resources.rssany.js +155 -0
  5. package/app/plugins/builtin/baai-wudao-paper-article.rssany.js +185 -0
  6. package/app/plugins/builtin/baaidata-csdn.rssany.js +242 -0
  7. package/app/plugins/builtin/baidu-research.rssany.js +222 -0
  8. package/app/plugins/builtin/brightdata-blog.rssany.js +301 -0
  9. package/app/plugins/builtin/bytedance-seed-research.rssany.js +231 -0
  10. package/app/plugins/builtin/five-radar.rssany.js +490 -0
  11. package/app/plugins/builtin/flageval-news.rssany.js +118 -0
  12. package/app/plugins/builtin/google-deepmind-research.rssany.js +223 -0
  13. package/app/plugins/builtin/google-research-datasets.rssany.js +171 -0
  14. package/app/plugins/builtin/google-research.rssany.js +220 -0
  15. package/app/plugins/builtin/google.rssany.js +187 -0
  16. package/app/plugins/builtin/hacker-news-newest.rssany.js +130 -0
  17. package/app/plugins/builtin/harvard-dataverse.rssany.js +166 -0
  18. package/app/plugins/builtin/huaweicloud-bbs-blogs.rssany.js +185 -0
  19. package/app/plugins/builtin/lingowhale.rssany.js +119 -0
  20. package/app/plugins/builtin/meituan-tech.rssany.js +130 -0
  21. package/app/plugins/builtin/meta-ai-publications.rssany.js +221 -0
  22. package/app/plugins/builtin/mila-quebec.rssany.js +199 -0
  23. package/app/plugins/builtin/mit-csail-research.rssany.js +208 -0
  24. package/app/plugins/builtin/moonshot.rssany.js +127 -0
  25. package/app/plugins/builtin/opendatalab-news.rssany.js +174 -0
  26. package/app/plugins/builtin/opendatalab.rssany.js +109 -0
  27. package/app/plugins/builtin/opendrivelab-autonomous-driving.rssany.js +114 -0
  28. package/app/plugins/builtin/opendrivelab-embodiedai.rssany.js +114 -0
  29. package/app/plugins/builtin/opendrivelab-publications.rssany.js +130 -0
  30. package/app/plugins/builtin/opendrivelab.rssany.js +333 -0
  31. package/app/plugins/builtin/paperswithcode.rssany.js +227 -0
  32. package/app/plugins/builtin/pjlab-adg-publications.rssany.js +202 -0
  33. package/app/plugins/builtin/rss.rssany.js +11 -1
  34. package/app/plugins/builtin/selectdataset.rssany.js +206 -0
  35. package/app/plugins/builtin/sensetime-tech-achievements.rssany.js +154 -0
  36. package/app/plugins/builtin/supervisely-blog.rssany.js +159 -0
  37. package/app/plugins/builtin/uci-ml-repository.rssany.js +111 -0
  38. package/app/plugins/builtin/venturebeat.rssany.js +97 -0
  39. package/app/plugins/builtin/worldlabs.rssany.js +129 -0
  40. package/app/plugins/builtin/x.rssany.js +159 -0
  41. package/app/plugins/builtin/xiaohongshu.rssany.js +283 -0
  42. package/app/plugins/builtin/zhipu-research.rssany.js +334 -0
  43. package/dist/index.js +62 -4
  44. package/dist/index.js.map +1 -1
  45. package/package.json +1 -1
  46. package/webui/build/200.html +6 -6
  47. package/webui/build/_app/immutable/assets/{0.DjU2hdCQ.css → 0.BB88QFoe.css} +1 -1
  48. package/webui/build/_app/immutable/assets/{homeFeedPanelStore.BopJZtHu.css → homeFeedPanelStore.iOmfP2qL.css} +1 -1
  49. package/webui/build/_app/immutable/chunks/CZD-YNDw.js +31 -0
  50. package/webui/build/_app/immutable/chunks/{C85CNwD2.js → D6VIKef0.js} +1 -1
  51. package/webui/build/_app/immutable/chunks/{CllQAdvt.js → Dbqx2mXq.js} +1 -1
  52. package/webui/build/_app/immutable/chunks/DeX-oq5W.js +41 -0
  53. package/webui/build/_app/immutable/chunks/{CdMsRjxJ.js → dhB8G5Is.js} +1 -1
  54. package/webui/build/_app/immutable/entry/{app.BcD2eSsQ.js → app.XPso7q7g.js} +2 -2
  55. package/webui/build/_app/immutable/entry/start.Db4snNCd.js +1 -0
  56. package/webui/build/_app/immutable/nodes/0.BKTQePmA.js +11 -0
  57. package/webui/build/_app/immutable/nodes/{1.DU9aYGAb.js → 1.BS3_Rfxm.js} +1 -1
  58. package/webui/build/_app/immutable/nodes/{10.Db6vw7Ih.js → 10.CyyxDCIS.js} +1 -1
  59. package/webui/build/_app/immutable/nodes/{11.BaAcorz3.js → 11.CtYgIaGj.js} +1 -1
  60. package/webui/build/_app/immutable/nodes/{14.DqT4pcrQ.js → 14.D5OEGPR2.js} +1 -1
  61. package/webui/build/_app/immutable/nodes/{15.CCLbjxnH.js → 15.B4dFN1Gk.js} +1 -1
  62. package/webui/build/_app/immutable/nodes/{16.DiigpVdP.js → 16.M7ZII7tl.js} +1 -1
  63. package/webui/build/_app/immutable/nodes/{3.DEcYOQc-.js → 3.7r8v7qkm.js} +1 -1
  64. package/webui/build/_app/immutable/nodes/{5.CvM1TkLG.js → 5.CHIzoGrb.js} +1 -1
  65. package/webui/build/_app/immutable/nodes/{6.Dscr6LkS.js → 6.BDBqx-GY.js} +1 -1
  66. package/webui/build/_app/immutable/nodes/{7.Bp60MobD.js → 7.D5czsDmz.js} +1 -1
  67. package/webui/build/_app/immutable/nodes/{8.DwSg0MHh.js → 8.pjVNsCdV.js} +1 -1
  68. package/webui/build/_app/immutable/nodes/{9.BeYOUjxR.js → 9.CsARv1BH.js} +1 -1
  69. package/webui/build/_app/version.json +1 -1
  70. package/webui/build/_app/immutable/chunks/CtijX1u3.js +0 -31
  71. package/webui/build/_app/immutable/chunks/Dv1VCsiB.js +0 -41
  72. package/webui/build/_app/immutable/entry/start.CbkdJdz1.js +0 -1
  73. package/webui/build/_app/immutable/nodes/0.DSUDmOx2.js +0 -11
@@ -0,0 +1,159 @@
1
+ let _deps;
2
+
3
+ // X (Twitter) 站点插件:用户主页列表抓取与解析
4
+
5
+
6
+
7
+ const X_ORIGIN = "https://x.com";
8
+
9
+
10
+ function getOrigin(url) {
11
+ try {
12
+ return new URL(url).origin;
13
+ } catch {
14
+ return X_ORIGIN;
15
+ }
16
+ }
17
+
18
+
19
+ function normalizeText(text) {
20
+ return (text ?? "").replace(/\s+/g, " ").trim();
21
+ }
22
+
23
+
24
+ function statusPathFromHref(href) {
25
+ if (!href) return null;
26
+ try {
27
+ const normalized = href.startsWith("http") ? new URL(href).pathname : href.split("?")[0];
28
+ const m = normalized.match(/^\/([A-Za-z0-9_]{1,32})\/status\/(\d+)/);
29
+ if (!m) return null;
30
+ return `/${m[1]}/status/${m[2]}`;
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+
36
+
37
+ function extractAuthor(article, statusPath) {
38
+ const nameBlock = article.querySelector('[data-testid="User-Name"]');
39
+ if (nameBlock) {
40
+ const profileAnchors = nameBlock.querySelectorAll('a[href^="/"]');
41
+ for (const a of profileAnchors) {
42
+ const href = a.getAttribute("href") || "";
43
+ if (/^\/[A-Za-z0-9_]{1,32}$/.test(href)) return href.slice(1);
44
+ }
45
+ const text = normalizeText(nameBlock.textContent);
46
+ const mention = text.match(/@([A-Za-z0-9_]{1,32})/);
47
+ if (mention) return mention[1];
48
+ }
49
+ if (statusPath) {
50
+ const m = statusPath.match(/^\/([A-Za-z0-9_]{1,32})\/status\/\d+$/);
51
+ if (m) return m[1];
52
+ }
53
+ return undefined;
54
+ }
55
+
56
+
57
+ function extractTweetText(article) {
58
+ const textNode = article.querySelector('[data-testid="tweetText"]') ?? article.querySelector('[lang]');
59
+ const text = normalizeText(textNode?.textContent);
60
+ const hasShowMore = !!article.querySelector('[data-testid="tweet-text-show-more-link"]');
61
+ if (!text) return hasShowMore ? "推文内容较长,请打开原文查看" : "";
62
+ return hasShowMore ? `${text} ...` : text;
63
+ }
64
+
65
+
66
+ function parseArticles(root, origin) {
67
+ const entries = [];
68
+ const seen = new Set();
69
+ const articles = root.querySelectorAll('article[data-testid="tweet"], article[role="article"]');
70
+ for (const article of articles) {
71
+ const links = article.querySelectorAll('a[href*="/status/"]');
72
+ let statusPath = null;
73
+ for (const a of links) {
74
+ const p = statusPathFromHref(a.getAttribute("href"));
75
+ if (p) {
76
+ statusPath = p;
77
+ break;
78
+ }
79
+ }
80
+ if (!statusPath || seen.has(statusPath)) continue;
81
+ seen.add(statusPath);
82
+ const link = new URL(statusPath, origin).href;
83
+ const text = extractTweetText(article);
84
+ const author = extractAuthor(article, statusPath);
85
+ const pubDate = article.querySelector("time[datetime]")?.getAttribute("datetime") || undefined;
86
+ entries.push({ link, text, author, pubDate });
87
+ }
88
+ return entries;
89
+ }
90
+
91
+
92
+ function extractEntriesFromJson(data, origin) {
93
+ if (typeof data !== "object" || data == null) return [];
94
+ const entries = [];
95
+ const str = JSON.stringify(data);
96
+ const seen = new Set();
97
+ const matches = str.match(/\/([A-Za-z0-9_]{1,32})\/status\/(\d+)/g) || [];
98
+ for (const raw of matches) {
99
+ const m = raw.match(/^\/([A-Za-z0-9_]{1,32})\/status\/(\d+)$/);
100
+ if (!m) continue;
101
+ const statusPath = `/${m[1]}/status/${m[2]}`;
102
+ if (seen.has(statusPath)) continue;
103
+ seen.add(statusPath);
104
+ entries.push({ link: new URL(statusPath, origin).href, text: "", author: m[1], pubDate: undefined });
105
+ }
106
+ return entries;
107
+ }
108
+
109
+
110
+ function entriesToFeedItems(entries) {
111
+ return entries.map(({ link, text, author, pubDate }) => ({
112
+ guid: _deps.createHash("sha256").update(link).digest("hex"),
113
+ title: text || undefined,
114
+ link,
115
+ pubDate: pubDate ? new Date(pubDate) : new Date(),
116
+ author,
117
+ summary: text || undefined,
118
+ }));
119
+ }
120
+
121
+
122
+ async function fetchItems(sourceId, ctx) {
123
+ _deps = ctx.deps;
124
+ const { html, finalUrl } = await ctx.fetchHtml(sourceId, { waitMs: 6000 });
125
+ const root = _deps.parseHtml(html);
126
+ const origin = getOrigin(finalUrl);
127
+
128
+ let entries = parseArticles(root, origin);
129
+ if (entries.length > 0) return entriesToFeedItems(entries);
130
+
131
+ const scripts = root.querySelectorAll('script[type="application/json"]');
132
+ for (const script of scripts) {
133
+ try {
134
+ const data = JSON.parse(script.textContent || "");
135
+ const fromJson = extractEntriesFromJson(data, origin);
136
+ if (fromJson.length > 0) {
137
+ entries = fromJson;
138
+ break;
139
+ }
140
+ } catch {
141
+ // ignore broken JSON blocks
142
+ }
143
+ }
144
+ if (entries.length > 0) return entriesToFeedItems(entries);
145
+
146
+ const bodyText = normalizeText(root.textContent).toLowerCase();
147
+ const isErrorPage = bodyText.includes("something went wrong") || bodyText.includes("try again");
148
+ const message = isErrorPage
149
+ ? "X 页面暂不可用(可能被风控或需登录),请稍后重试或切换为有头模式并确认登录态"
150
+ : "未解析到推文条目,可能被风控或需登录";
151
+ throw new Error(`[X] ${message}`);
152
+ }
153
+
154
+
155
+ export default {
156
+ id: "x",
157
+ listUrlPattern: "https://x.com/{username}",
158
+ fetchItems,
159
+ };
@@ -0,0 +1,283 @@
1
+ let _deps;
2
+
3
+ // 小红书站点插件:用户主页列表抓取、笔记详情提取、认证流程
4
+
5
+
6
+
7
+ const XHS_ORIGIN = "https://www.xiaohongshu.com";
8
+
9
+
10
+ function getOrigin(url) {
11
+ try {
12
+ return new URL(url).origin;
13
+ } catch {
14
+ return XHS_ORIGIN;
15
+ }
16
+ }
17
+
18
+
19
+ function buildExploreLinkWithXsec(profileHref, origin) {
20
+ try {
21
+ const fullUrl = new URL(profileHref.replace(/&/g, "&"), origin);
22
+ const pathSegs = fullUrl.pathname.split("/").filter(Boolean);
23
+ const noteId = pathSegs[pathSegs.length - 1];
24
+ if (!noteId || !/^[0-9a-f]+$/i.test(noteId)) return null;
25
+ const token = fullUrl.searchParams.get("xsec_token");
26
+ const source = fullUrl.searchParams.get("xsec_source") ?? "pc_user";
27
+ if (!token) return null;
28
+ const explore = new URL(`/explore/${noteId}`, origin);
29
+ explore.searchParams.set("xsec_token", token);
30
+ explore.searchParams.set("xsec_source", source);
31
+ return explore.href;
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+
37
+
38
+ function parseListHtml(html, url) {
39
+ const root = _deps.parseHtml(html);
40
+ const origin = getOrigin(url);
41
+ const feed = root.querySelector("#userPostedFeeds");
42
+ if (!feed) return [];
43
+ const sections = feed.querySelectorAll("section[data-v-79abd645][data-index]");
44
+ const items = [];
45
+ for (const section of sections) {
46
+ const profileWithToken = section.querySelector('a[href*="xsec_token="]');
47
+ const profileHref = profileWithToken?.getAttribute("href")?.trim();
48
+ let link;
49
+ if (profileHref && profileHref.includes("/user/profile/")) {
50
+ const withXsec = buildExploreLinkWithXsec(profileHref, origin);
51
+ if (withXsec) link = withXsec;
52
+ else link = new URL(profileHref.replace(/&/g, "&"), origin).href;
53
+ } else {
54
+ const linkEl = section.querySelector('a[href^="/explore/"]');
55
+ const href = linkEl?.getAttribute("href")?.trim();
56
+ if (!href) continue;
57
+ link = new URL(href, origin).href;
58
+ }
59
+ const titleEl = section.querySelector("span[data-v-51ec0135]");
60
+ const title = (titleEl?.textContent ?? "").trim() || "笔记";
61
+ const authorEl = section.querySelector('a[aria-current="page"] span');
62
+ const author = (authorEl?.textContent ?? "").trim() || undefined;
63
+ items.push({
64
+ guid: _deps.createHash("sha256").update(link).digest("hex"),
65
+ title,
66
+ link,
67
+ pubDate: new Date(),
68
+ author,
69
+ summary: title,
70
+ });
71
+ }
72
+ return items;
73
+ }
74
+
75
+
76
+ function descToMarkdown(descEl) {
77
+ if (!descEl) return "";
78
+ const noteText = descEl.querySelector(".note-text");
79
+ if (!noteText) {
80
+ return (descEl.textContent ?? "").trim();
81
+ }
82
+ const parts = [];
83
+ for (const node of noteText.childNodes) {
84
+ if (node.nodeType === 3) {
85
+ const text = (node.textContent ?? "").trim();
86
+ if (text) parts.push(text);
87
+ } else if (node.nodeType === 1) {
88
+ const el = node;
89
+ const tagName = el.tagName?.toLowerCase();
90
+ if (tagName === "img") {
91
+ const alt = el.getAttribute("alt") || "";
92
+ if (alt) parts.push(alt);
93
+ } else if (tagName === "a" && el.classList?.contains("tag")) {
94
+ const txt = (el.textContent ?? "").trim();
95
+ if (txt) parts.push(txt);
96
+ } else {
97
+ const txt = (el.textContent ?? "").trim();
98
+ if (txt) parts.push(txt);
99
+ }
100
+ }
101
+ }
102
+ let result = parts.join(" ").replace(/\s+/g, " ").trim();
103
+ if (!result) result = (descEl.textContent ?? "").trim();
104
+ if (!result && descEl.parentNode) result = (descEl.parentNode.textContent ?? "").trim();
105
+ return result;
106
+ }
107
+
108
+
109
+ function extractUrl(val) {
110
+ if (!val) return null;
111
+ const decoded = val.replace(/"/g, '"').replace(/&/g, "&");
112
+ const m = decoded.match(/url\s*\(\s*["']?([^"')]+)["']?\s*\)/);
113
+ if (m) {
114
+ let url = m[1].trim();
115
+ url = url.replace(/^["']|["']$/g, "");
116
+ return url || null;
117
+ }
118
+ return null;
119
+ }
120
+
121
+
122
+ function collectNoteImages(root) {
123
+ const urls = [];
124
+ const seen = new Set();
125
+ const add = (url) => {
126
+ const u = (url || "").trim();
127
+ if (u && !seen.has(u) && (u.startsWith("http") || u.startsWith("//"))) {
128
+ seen.add(u);
129
+ urls.push(u.startsWith("//") ? "https:" + u : u);
130
+ }
131
+ };
132
+ const imgs = root.querySelectorAll(".img-container img, .note-slider-img img, .note-slider img, .xhs-webplayer img, .note-content img, [class*='note-detail'] img, .media-container img, .video-player-media img");
133
+ for (const el of imgs) {
134
+ const src = el.getAttribute("src") || el.getAttribute("data-src") || el.getAttribute("data-lazy-src");
135
+ if (src) add(src);
136
+ }
137
+ const posterSelectors = ["xg-poster", "[class*='xgplayer-poster']", ".player-container [style*='background-image']", ".render-ssr-image [style*='background-image']", "[class*='player-container'] [style*='background-image']", ".video-player-media [style*='background-image']", ".media-container [style*='background-image']"];
138
+ for (const sel of posterSelectors) {
139
+ const els = root.querySelectorAll(sel);
140
+ for (const el of els) {
141
+ const style = el.getAttribute("style");
142
+ const url = extractUrl(style ?? "");
143
+ if (url) add(url);
144
+ }
145
+ }
146
+ const anyBg = root.querySelectorAll("[style*='background-image']");
147
+ for (const el of anyBg) {
148
+ const url = extractUrl(el.getAttribute("style") ?? "");
149
+ if (url && (url.includes("xhscdn") || url.includes("sns-webpic") || url.includes("sns-avatar"))) add(url);
150
+ }
151
+ return urls;
152
+ }
153
+
154
+
155
+ function parseNoteDate(dateEl) {
156
+ const text = (dateEl?.textContent ?? "").trim();
157
+ if (!text) return undefined;
158
+ const now = new Date();
159
+ const published = text.match(/发布于\s*(\d{4})-(\d{1,2})-(\d{1,2})/);
160
+ if (published) {
161
+ const [, y, m, d] = published;
162
+ return new Date(`${y}-${m.padStart(2, "0")}-${d.padStart(2, "0")}T12:00:00.000Z`);
163
+ }
164
+ const edited = text.match(/编辑于\s*(\d{1,2})-(\d{1,2})/);
165
+ if (edited) {
166
+ const [, m, d] = edited;
167
+ let year = now.getFullYear();
168
+ const month = parseInt(m, 10);
169
+ const day = parseInt(d, 10);
170
+ const built = new Date(year, month - 1, day);
171
+ if (built > now) year -= 1;
172
+ return new Date(`${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}T12:00:00.000Z`);
173
+ }
174
+ const relativeMatch = text.match(/(编辑于|发布于)\s*(\d+)\s*(分钟|小时|天|周|个月)前/);
175
+ if (relativeMatch) {
176
+ const [, , amount, unit] = relativeMatch;
177
+ const num = parseInt(amount, 10);
178
+ const msMap = { 分钟: 60_000, 小时: 3_600_000, 天: 86_400_000, 周: 604_800_000, 个月: 2_592_000_000 };
179
+ return new Date(now.getTime() - (num * (msMap[unit] ?? 0)));
180
+ }
181
+ return undefined;
182
+ }
183
+
184
+
185
+ function extractDetailHtml(html) {
186
+ const root = _deps.parseHtml(html);
187
+ // 作者
188
+ let authorEl = null;
189
+ const authorSelectors = [
190
+ ".author .info a.name .username",
191
+ ".info a.name .username",
192
+ ".info .username",
193
+ "a.name .username",
194
+ ".author-container .username",
195
+ ".author .username",
196
+ ];
197
+ for (const sel of authorSelectors) {
198
+ authorEl = root.querySelector(sel);
199
+ if (authorEl) break;
200
+ }
201
+ if (!authorEl) {
202
+ const containers = root.querySelectorAll(".author, .author-container, .interaction-container, .info");
203
+ for (const c of containers) {
204
+ const u = c.querySelector("a.name .username") ?? c.querySelector(".username");
205
+ if (u) { authorEl = u; break; }
206
+ }
207
+ }
208
+ if (!authorEl) {
209
+ for (const u of root.querySelectorAll(".username")) {
210
+ let p = u.parentNode ?? null;
211
+ for (let i = 0; i < 5 && p; i++) {
212
+ const cls = p.getAttribute?.("class") || "";
213
+ if (typeof cls === "string" && (cls.includes("name") || cls.includes("info") || cls.includes("author"))) {
214
+ authorEl = u; break;
215
+ }
216
+ p = p.parentNode ?? null;
217
+ }
218
+ if (authorEl) break;
219
+ }
220
+ }
221
+ const author = (authorEl?.textContent ?? "").trim() || undefined;
222
+ // 标题
223
+ const titleEl = root.querySelector("#detail-title") ?? root.querySelector(".note-content .title") ?? root.querySelector("h1.title");
224
+ const title = (titleEl?.textContent ?? "").trim() || undefined;
225
+ // 正文
226
+ const descEl = root.querySelector("#detail-desc") ?? root.querySelector(".note-content .desc") ?? root.querySelector(".desc");
227
+ const descText = descToMarkdown(descEl);
228
+ const imgUrls = collectNoteImages(root);
229
+ const imgMd = imgUrls.length > 0 ? imgUrls.map((u) => `\n\n![](${u})`).join("") : "";
230
+ let content = (descText + imgMd).trim() || title || imgMd.trim() || undefined;
231
+ // 发布时间
232
+ let dateEl = root.querySelector(".bottom-container span.date") ?? root.querySelector(".bottom-container .date");
233
+ if (!dateEl) {
234
+ for (const span of root.querySelectorAll("span")) {
235
+ if (/(编辑于|发布于)/.test(span.textContent ?? "")) { dateEl = span; break; }
236
+ }
237
+ }
238
+ const pubDate = parseNoteDate(dateEl);
239
+ return { author, title, content, pubDate };
240
+ }
241
+
242
+
243
+ async function fetchItems(sourceId, ctx) {
244
+ _deps = ctx.deps;
245
+ const { html, finalUrl } = await ctx.fetchHtml(sourceId);
246
+ return parseListHtml(html, finalUrl);
247
+ }
248
+
249
+
250
+ async function enrichItem(item, ctx) {
251
+ const { html } = await ctx.fetchHtml(item.link);
252
+ const detail = extractDetailHtml(html);
253
+ return {
254
+ ...item,
255
+ author: detail.author ?? item.author,
256
+ title: detail.title ?? item.title,
257
+ content: detail.content ? `<p>${detail.content.replace(/\n\n/g, "</p><p>")}</p>` : undefined,
258
+ pubDate: detail.pubDate ?? item.pubDate,
259
+ };
260
+ }
261
+
262
+
263
+ async function checkAuth(page, _url) {
264
+ try {
265
+ const loginButton = await page.$(".reds-button-new.login-btn.large.primary");
266
+ return loginButton == null;
267
+ } catch {
268
+ return false;
269
+ }
270
+ }
271
+
272
+
273
+ export default {
274
+ id: "xiaohongshu",
275
+ listUrlPattern: "https://xiaohongshu.com/user/profile/{userId}",
276
+ fetchItems,
277
+ enrichItem,
278
+ checkAuth,
279
+ loginUrl: "https://www.xiaohongshu.com/",
280
+ domain: "xiaohongshu.com",
281
+ loginTimeoutMs: 30 * 1000,
282
+ pollIntervalMs: 2000,
283
+ };