@jackwener/opencli 1.1.0 → 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/.agents/skills/cross-project-adapter-migration/SKILL.md +2 -2
- package/.github/pull_request_template.md +7 -0
- package/.github/workflows/doc-check.yml +36 -0
- package/.github/workflows/docs.yml +7 -42
- package/CHANGELOG.md +23 -0
- package/CLI-EXPLORER.md +9 -8
- package/README.md +25 -10
- package/README.zh-CN.md +26 -11
- package/SKILL.md +95 -31
- package/dist/browser/cdp.js +6 -1
- package/dist/browser/page.d.ts +4 -1
- package/dist/browser/page.js +7 -1
- package/dist/build-manifest.js +23 -16
- package/dist/cli-manifest.json +431 -276
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +189 -162
- package/dist/clis/apple-podcasts/commands.test.d.ts +2 -0
- package/dist/clis/apple-podcasts/commands.test.js +76 -0
- package/dist/clis/apple-podcasts/search.js +2 -2
- package/dist/clis/apple-podcasts/top.js +9 -2
- package/dist/clis/arxiv/search.js +1 -1
- package/dist/clis/bilibili/dynamic.js +1 -1
- package/dist/clis/bilibili/favorite.js +1 -1
- package/dist/clis/bilibili/feed.js +1 -1
- package/dist/clis/bilibili/following.js +1 -1
- package/dist/clis/bilibili/history.js +1 -1
- package/dist/clis/bilibili/me.js +1 -1
- package/dist/clis/bilibili/ranking.js +1 -1
- package/dist/clis/bilibili/search.js +3 -3
- package/dist/clis/bilibili/subtitle.js +1 -1
- package/dist/clis/bilibili/user-videos.js +1 -1
- package/dist/{bilibili.d.ts → clis/bilibili/utils.d.ts} +1 -1
- package/dist/clis/bloomberg/businessweek.js +17 -0
- package/dist/clis/bloomberg/economics.js +17 -0
- package/dist/clis/bloomberg/feeds.d.ts +1 -0
- package/dist/clis/bloomberg/feeds.js +15 -0
- package/dist/clis/bloomberg/industries.d.ts +1 -0
- package/dist/clis/bloomberg/industries.js +17 -0
- package/dist/clis/bloomberg/main.d.ts +1 -0
- package/dist/clis/bloomberg/main.js +17 -0
- package/dist/clis/bloomberg/markets.d.ts +1 -0
- package/dist/clis/bloomberg/markets.js +17 -0
- package/dist/clis/bloomberg/news.d.ts +1 -0
- package/dist/clis/bloomberg/news.js +105 -0
- package/dist/clis/bloomberg/opinions.d.ts +1 -0
- package/dist/clis/bloomberg/opinions.js +17 -0
- package/dist/clis/bloomberg/politics.d.ts +1 -0
- package/dist/clis/bloomberg/politics.js +17 -0
- package/dist/clis/bloomberg/tech.d.ts +1 -0
- package/dist/clis/bloomberg/tech.js +17 -0
- package/dist/clis/bloomberg/utils.d.ts +34 -0
- package/dist/clis/bloomberg/utils.js +364 -0
- package/dist/clis/bloomberg/utils.test.d.ts +1 -0
- package/dist/clis/bloomberg/utils.test.js +129 -0
- package/dist/clis/boss/batchgreet.js +2 -2
- package/dist/clis/boss/chatlist.js +2 -2
- package/dist/clis/boss/detail.js +2 -2
- package/dist/clis/boss/greet.js +4 -4
- package/dist/clis/boss/search.js +1 -1
- package/dist/clis/boss/send.js +1 -1
- package/dist/clis/boss/stats.js +2 -2
- package/dist/clis/chaoxing/assignments.js +1 -1
- package/dist/clis/chaoxing/exams.js +1 -1
- package/dist/{chaoxing.d.ts → clis/chaoxing/utils.d.ts} +1 -1
- package/dist/{chaoxing.js → clis/chaoxing/utils.js} +0 -2
- package/dist/clis/chaoxing/utils.test.d.ts +1 -0
- package/dist/{chaoxing.test.js → clis/chaoxing/utils.test.js} +1 -1
- package/dist/clis/chatgpt/read.js +1 -1
- package/dist/clis/chatwise/export.js +1 -1
- package/dist/clis/chatwise/model.js +2 -2
- package/dist/clis/chatwise/screenshot.js +1 -1
- package/dist/clis/codex/export.js +1 -1
- package/dist/clis/codex/model.js +2 -2
- package/dist/clis/codex/screenshot.js +1 -1
- package/dist/clis/coupang/add-to-cart.js +3 -4
- package/dist/clis/coupang/search.js +2 -4
- package/dist/clis/coupang/utils.test.d.ts +1 -0
- package/dist/{coupang.test.js → clis/coupang/utils.test.js} +1 -1
- package/dist/clis/ctrip/search.js +1 -1
- package/dist/clis/cursor/export.js +1 -1
- package/dist/clis/cursor/model.js +2 -2
- package/dist/clis/cursor/screenshot.js +1 -1
- package/dist/clis/jike/comment.js +2 -3
- package/dist/clis/jike/create.js +1 -2
- package/dist/clis/jike/feed.js +0 -1
- package/dist/clis/jike/like.js +1 -2
- package/dist/clis/jike/notifications.js +0 -1
- package/dist/clis/jike/post.yaml +1 -0
- package/dist/clis/jike/repost.js +1 -2
- package/dist/clis/jike/search.js +2 -3
- package/dist/clis/jike/topic.yaml +1 -0
- package/dist/clis/jike/user.yaml +1 -0
- package/dist/clis/jimeng/history.yaml +0 -1
- package/dist/clis/linkedin/search.js +7 -7
- package/dist/clis/linux-do/category.yaml +1 -0
- package/dist/clis/linux-do/search.yaml +4 -3
- package/dist/clis/linux-do/topic.yaml +1 -0
- package/dist/clis/notion/export.js +1 -1
- package/dist/clis/reddit/comment.js +3 -4
- package/dist/clis/reddit/read.js +4 -5
- package/dist/clis/reddit/save.js +2 -3
- package/dist/clis/reddit/saved.js +0 -1
- package/dist/clis/reddit/search.yaml +1 -0
- package/dist/clis/reddit/subscribe.js +0 -1
- package/dist/clis/reddit/upvote.js +2 -3
- package/dist/clis/reddit/upvoted.js +0 -1
- package/dist/clis/reddit/user-comments.yaml +1 -0
- package/dist/clis/reddit/user-posts.yaml +1 -0
- package/dist/clis/reddit/user.yaml +1 -0
- package/dist/clis/reuters/search.js +1 -1
- package/dist/clis/smzdm/search.js +2 -3
- package/dist/clis/stackoverflow/search.yaml +1 -0
- package/dist/clis/steam/top-sellers.yaml +29 -0
- package/dist/clis/twitter/accept.js +2 -2
- package/dist/clis/twitter/article.js +2 -2
- package/dist/clis/twitter/block.d.ts +1 -0
- package/dist/clis/twitter/block.js +88 -0
- package/dist/clis/twitter/delete.js +1 -1
- package/dist/clis/twitter/hide-reply.d.ts +1 -0
- package/dist/clis/twitter/hide-reply.js +66 -0
- package/dist/clis/twitter/like.js +1 -1
- package/dist/clis/twitter/post.js +1 -1
- package/dist/clis/twitter/reply-dm.js +1 -1
- package/dist/clis/twitter/reply.js +2 -2
- package/dist/clis/twitter/search.js +1 -1
- package/dist/clis/twitter/thread.js +2 -2
- package/dist/clis/twitter/trending.d.ts +1 -0
- package/dist/clis/twitter/trending.js +91 -0
- package/dist/clis/twitter/unblock.d.ts +1 -0
- package/dist/clis/twitter/unblock.js +71 -0
- package/dist/clis/v2ex/topic.yaml +1 -0
- package/dist/clis/weibo/hot.js +0 -1
- package/dist/clis/weread/book.js +1 -1
- package/dist/clis/weread/highlights.js +1 -1
- package/dist/clis/weread/notes.js +1 -1
- package/dist/clis/weread/search.js +1 -1
- package/dist/clis/wikipedia/search.js +1 -1
- package/dist/clis/xiaohongshu/creator-note-detail.d.ts +15 -0
- package/dist/clis/xiaohongshu/creator-note-detail.js +69 -5
- package/dist/clis/xiaohongshu/creator-note-detail.test.js +80 -33
- package/dist/clis/xiaohongshu/creator-notes.js +35 -5
- package/dist/clis/xiaohongshu/creator-notes.test.js +35 -6
- package/dist/clis/xiaohongshu/creator-profile.js +0 -1
- package/dist/clis/xiaohongshu/creator-stats.js +0 -1
- package/dist/clis/xiaohongshu/download.js +2 -3
- package/dist/clis/xiaohongshu/feed.yaml +0 -1
- package/dist/clis/xiaohongshu/notifications.yaml +0 -1
- package/dist/clis/xiaohongshu/search.js +2 -2
- package/dist/clis/xiaohongshu/user.js +1 -2
- package/dist/clis/yahoo-finance/quote.js +0 -1
- package/dist/clis/youtube/search.js +1 -1
- package/dist/clis/youtube/transcript.js +1 -1
- package/dist/clis/youtube/video.js +1 -1
- package/dist/clis/zhihu/download.js +1 -2
- package/dist/clis/zhihu/question.js +1 -1
- package/dist/clis/zhihu/search.yaml +4 -3
- package/dist/commanderAdapter.d.ts +21 -0
- package/dist/commanderAdapter.js +111 -0
- package/dist/{engine.d.ts → discovery.d.ts} +0 -6
- package/dist/{engine.js → discovery.js} +1 -98
- package/dist/download/index.d.ts +2 -6
- package/dist/download/index.js +19 -46
- package/dist/engine.test.d.ts +1 -1
- package/dist/engine.test.js +8 -7
- package/dist/execution.d.ts +22 -0
- package/dist/execution.js +129 -0
- package/dist/explore.js +121 -107
- package/dist/external-clis.yaml +48 -0
- package/dist/external.d.ts +7 -2
- package/dist/external.js +11 -14
- package/dist/main.js +1 -1
- package/dist/pipeline/steps/browser.js +8 -2
- package/dist/registry.d.ts +2 -0
- package/dist/registry.js +2 -0
- package/dist/runtime.d.ts +5 -0
- package/dist/runtime.js +8 -0
- package/dist/serialization.d.ts +34 -0
- package/dist/serialization.js +63 -0
- package/dist/types.d.ts +4 -1
- package/docs/.vitepress/config.mts +14 -3
- package/docs/adapters/browser/arxiv.md +27 -0
- package/docs/adapters/browser/barchart.md +32 -0
- package/docs/adapters/browser/bloomberg.md +70 -0
- package/docs/adapters/browser/chaoxing.md +39 -0
- package/docs/adapters/browser/grok.md +35 -0
- package/docs/adapters/browser/hf.md +42 -0
- package/docs/adapters/browser/jike.md +45 -0
- package/docs/adapters/browser/jimeng.md +39 -0
- package/docs/adapters/browser/linux-do.md +45 -0
- package/docs/adapters/browser/sinafinance.md +35 -0
- package/docs/adapters/browser/stackoverflow.md +35 -0
- package/docs/adapters/browser/steam.md +26 -0
- package/docs/adapters/browser/twitter.md +3 -0
- package/docs/adapters/browser/weread.md +48 -0
- package/docs/adapters/browser/wikipedia.md +30 -0
- package/docs/adapters/browser/xiaohongshu.md +5 -1
- package/docs/adapters/desktop/chatgpt.md +3 -3
- package/docs/adapters/index.md +13 -0
- package/docs/advanced/download.md +4 -4
- package/docs/developer/architecture.md +17 -4
- package/package.json +1 -1
- package/scripts/check-doc-coverage.sh +69 -0
- package/scripts/copy-yaml.cjs +7 -0
- package/src/browser/cdp.ts +6 -1
- package/src/browser/page.ts +7 -1
- package/src/build-manifest.ts +25 -19
- package/src/cli.ts +218 -139
- package/src/clis/apple-podcasts/commands.test.ts +95 -0
- package/src/clis/apple-podcasts/search.ts +2 -2
- package/src/clis/apple-podcasts/top.ts +12 -2
- package/src/clis/arxiv/search.ts +1 -1
- package/src/clis/bilibili/dynamic.ts +1 -1
- package/src/clis/bilibili/favorite.ts +1 -1
- package/src/clis/bilibili/feed.ts +1 -1
- package/src/clis/bilibili/following.ts +1 -1
- package/src/clis/bilibili/history.ts +1 -1
- package/src/clis/bilibili/me.ts +1 -1
- package/src/clis/bilibili/ranking.ts +1 -1
- package/src/clis/bilibili/search.ts +3 -3
- package/src/clis/bilibili/subtitle.ts +1 -1
- package/src/clis/bilibili/user-videos.ts +1 -1
- package/src/{bilibili.ts → clis/bilibili/utils.ts} +1 -1
- package/src/clis/bloomberg/businessweek.ts +18 -0
- package/src/clis/bloomberg/economics.ts +18 -0
- package/src/clis/bloomberg/feeds.ts +16 -0
- package/src/clis/bloomberg/industries.ts +18 -0
- package/src/clis/bloomberg/main.ts +18 -0
- package/src/clis/bloomberg/markets.ts +18 -0
- package/src/clis/bloomberg/news.ts +136 -0
- package/src/clis/bloomberg/opinions.ts +18 -0
- package/src/clis/bloomberg/politics.ts +18 -0
- package/src/clis/bloomberg/tech.ts +18 -0
- package/src/clis/bloomberg/utils.test.ts +135 -0
- package/src/clis/bloomberg/utils.ts +429 -0
- package/src/clis/boss/batchgreet.ts +2 -2
- package/src/clis/boss/chatlist.ts +2 -2
- package/src/clis/boss/detail.ts +2 -2
- package/src/clis/boss/greet.ts +4 -4
- package/src/clis/boss/search.ts +1 -1
- package/src/clis/boss/send.ts +1 -1
- package/src/clis/boss/stats.ts +2 -2
- package/src/clis/chaoxing/assignments.ts +1 -1
- package/src/clis/chaoxing/exams.ts +1 -1
- package/src/{chaoxing.test.ts → clis/chaoxing/utils.test.ts} +1 -1
- package/src/{chaoxing.ts → clis/chaoxing/utils.ts} +1 -3
- package/src/clis/chatgpt/README.zh-CN.md +3 -3
- package/src/clis/chatgpt/read.ts +1 -1
- package/src/clis/chatwise/export.ts +1 -1
- package/src/clis/chatwise/model.ts +2 -2
- package/src/clis/chatwise/screenshot.ts +1 -1
- package/src/clis/codex/export.ts +1 -1
- package/src/clis/codex/model.ts +2 -2
- package/src/clis/codex/screenshot.ts +1 -1
- package/src/clis/coupang/add-to-cart.ts +3 -4
- package/src/clis/coupang/search.ts +2 -4
- package/src/{coupang.test.ts → clis/coupang/utils.test.ts} +1 -1
- package/src/clis/ctrip/search.ts +1 -1
- package/src/clis/cursor/export.ts +1 -1
- package/src/clis/cursor/model.ts +2 -2
- package/src/clis/cursor/screenshot.ts +1 -1
- package/src/clis/jike/comment.ts +2 -3
- package/src/clis/jike/create.ts +1 -2
- package/src/clis/jike/feed.ts +0 -1
- package/src/clis/jike/like.ts +1 -2
- package/src/clis/jike/notifications.ts +0 -1
- package/src/clis/jike/post.yaml +1 -0
- package/src/clis/jike/repost.ts +1 -2
- package/src/clis/jike/search.ts +2 -3
- package/src/clis/jike/topic.yaml +1 -0
- package/src/clis/jike/user.yaml +1 -0
- package/src/clis/jimeng/history.yaml +0 -1
- package/src/clis/linkedin/search.ts +7 -7
- package/src/clis/linux-do/category.yaml +1 -0
- package/src/clis/linux-do/search.yaml +4 -3
- package/src/clis/linux-do/topic.yaml +1 -0
- package/src/clis/notion/export.ts +1 -1
- package/src/clis/reddit/comment.ts +3 -4
- package/src/clis/reddit/read.ts +4 -5
- package/src/clis/reddit/save.ts +2 -3
- package/src/clis/reddit/saved.ts +0 -1
- package/src/clis/reddit/search.yaml +1 -0
- package/src/clis/reddit/subscribe.ts +0 -1
- package/src/clis/reddit/upvote.ts +2 -3
- package/src/clis/reddit/upvoted.ts +0 -1
- package/src/clis/reddit/user-comments.yaml +1 -0
- package/src/clis/reddit/user-posts.yaml +1 -0
- package/src/clis/reddit/user.yaml +1 -0
- package/src/clis/reuters/search.ts +1 -1
- package/src/clis/smzdm/search.ts +2 -3
- package/src/clis/stackoverflow/search.yaml +1 -0
- package/src/clis/steam/top-sellers.yaml +29 -0
- package/src/clis/twitter/accept.ts +2 -2
- package/src/clis/twitter/article.ts +2 -2
- package/src/clis/twitter/block.ts +92 -0
- package/src/clis/twitter/delete.ts +1 -1
- package/src/clis/twitter/hide-reply.ts +70 -0
- package/src/clis/twitter/like.ts +1 -1
- package/src/clis/twitter/post.ts +1 -1
- package/src/clis/twitter/reply-dm.ts +1 -1
- package/src/clis/twitter/reply.ts +2 -2
- package/src/clis/twitter/search.ts +1 -1
- package/src/clis/twitter/thread.ts +2 -2
- package/src/clis/twitter/trending.ts +113 -0
- package/src/clis/twitter/unblock.ts +75 -0
- package/src/clis/v2ex/topic.yaml +1 -0
- package/src/clis/weibo/hot.ts +0 -1
- package/src/clis/weread/book.ts +1 -1
- package/src/clis/weread/highlights.ts +1 -1
- package/src/clis/weread/notes.ts +1 -1
- package/src/clis/weread/search.ts +1 -1
- package/src/clis/wikipedia/search.ts +1 -1
- package/src/clis/xiaohongshu/creator-note-detail.test.ts +82 -33
- package/src/clis/xiaohongshu/creator-note-detail.ts +89 -5
- package/src/clis/xiaohongshu/creator-notes.test.ts +39 -6
- package/src/clis/xiaohongshu/creator-notes.ts +44 -5
- package/src/clis/xiaohongshu/creator-profile.ts +0 -1
- package/src/clis/xiaohongshu/creator-stats.ts +0 -1
- package/src/clis/xiaohongshu/download.ts +2 -3
- package/src/clis/xiaohongshu/feed.yaml +0 -1
- package/src/clis/xiaohongshu/notifications.yaml +0 -1
- package/src/clis/xiaohongshu/search.ts +2 -2
- package/src/clis/xiaohongshu/user.ts +1 -2
- package/src/clis/yahoo-finance/quote.ts +0 -1
- package/src/clis/youtube/search.ts +1 -1
- package/src/clis/youtube/transcript.ts +1 -1
- package/src/clis/youtube/video.ts +1 -1
- package/src/clis/zhihu/download.ts +1 -2
- package/src/clis/zhihu/question.ts +1 -1
- package/src/clis/zhihu/search.yaml +4 -3
- package/src/commanderAdapter.ts +113 -0
- package/src/{engine.ts → discovery.ts} +1 -108
- package/src/download/index.ts +21 -54
- package/src/engine.test.ts +8 -7
- package/src/execution.ts +138 -0
- package/src/explore.ts +135 -109
- package/src/external-clis.yaml +9 -0
- package/src/external.ts +15 -12
- package/src/main.ts +1 -1
- package/src/pipeline/steps/browser.ts +7 -2
- package/src/registry.ts +5 -0
- package/src/runtime.ts +9 -0
- package/src/serialization.ts +79 -0
- package/src/types.ts +1 -1
- package/tests/e2e/browser-public.test.ts +25 -0
- package/tests/e2e/public-commands.test.ts +55 -1
- package/dist/clis/twitter/trending.yaml +0 -46
- package/docs/public/CNAME +0 -1
- package/src/clis/twitter/trending.yaml +0 -46
- /package/dist/{bilibili.js → clis/bilibili/utils.js} +0 -0
- /package/dist/{chaoxing.test.d.ts → clis/bloomberg/businessweek.d.ts} +0 -0
- /package/dist/{coupang.test.d.ts → clis/bloomberg/economics.d.ts} +0 -0
- /package/dist/{coupang.d.ts → clis/coupang/utils.d.ts} +0 -0
- /package/dist/{coupang.js → clis/coupang/utils.js} +0 -0
- /package/src/{coupang.ts → clis/coupang/utils.ts} +0 -0
|
@@ -22,6 +22,7 @@ const NOTE_DETAIL_METRICS = [
|
|
|
22
22
|
{ label: '分享数', section: '互动数据' },
|
|
23
23
|
];
|
|
24
24
|
const NOTE_DETAIL_METRIC_LABELS = new Set(NOTE_DETAIL_METRICS.map((metric) => metric.label));
|
|
25
|
+
const NOTE_DETAIL_SECTIONS = new Set(NOTE_DETAIL_METRICS.map((metric) => metric.section));
|
|
25
26
|
const NOTE_DETAIL_NOISE_LINES = new Set([
|
|
26
27
|
'切换笔记',
|
|
27
28
|
'笔记诊断',
|
|
@@ -75,6 +76,10 @@ function findMetricValue(lines, startIndex) {
|
|
|
75
76
|
}
|
|
76
77
|
return { value, extra };
|
|
77
78
|
}
|
|
79
|
+
function findPublishedAt(text) {
|
|
80
|
+
const match = text.match(/\b\d{4}-\d{2}-\d{2} \d{2}:\d{2}\b/);
|
|
81
|
+
return match?.[0] ?? '';
|
|
82
|
+
}
|
|
78
83
|
export function parseCreatorNoteDetailText(bodyText, noteId) {
|
|
79
84
|
const lines = bodyText
|
|
80
85
|
.split('\n')
|
|
@@ -101,6 +106,34 @@ export function parseCreatorNoteDetailText(bodyText, noteId) {
|
|
|
101
106
|
}
|
|
102
107
|
return rows;
|
|
103
108
|
}
|
|
109
|
+
export function parseCreatorNoteDetailDomData(dom, noteId) {
|
|
110
|
+
if (!dom)
|
|
111
|
+
return [];
|
|
112
|
+
const title = typeof dom.title === 'string' ? dom.title.trim() : '';
|
|
113
|
+
const infoText = typeof dom.infoText === 'string' ? dom.infoText : '';
|
|
114
|
+
const sections = Array.isArray(dom.sections) ? dom.sections : [];
|
|
115
|
+
const rows = [
|
|
116
|
+
{ section: '笔记信息', metric: 'note_id', value: noteId, extra: '' },
|
|
117
|
+
{ section: '笔记信息', metric: 'title', value: title, extra: '' },
|
|
118
|
+
{ section: '笔记信息', metric: 'published_at', value: findPublishedAt(infoText), extra: '' },
|
|
119
|
+
];
|
|
120
|
+
for (const section of sections) {
|
|
121
|
+
if (!NOTE_DETAIL_SECTIONS.has(section.title))
|
|
122
|
+
continue;
|
|
123
|
+
for (const metric of section.metrics) {
|
|
124
|
+
if (!NOTE_DETAIL_METRIC_LABELS.has(metric.label))
|
|
125
|
+
continue;
|
|
126
|
+
rows.push({
|
|
127
|
+
section: section.title,
|
|
128
|
+
metric: metric.label,
|
|
129
|
+
value: metric.value,
|
|
130
|
+
extra: metric.extra,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const hasMetric = rows.some((row) => row.section !== '笔记信息' && row.value);
|
|
135
|
+
return hasMetric ? rows : [];
|
|
136
|
+
}
|
|
104
137
|
function toPercentString(value) {
|
|
105
138
|
return value == null ? '' : `${value}%`;
|
|
106
139
|
}
|
|
@@ -244,11 +277,42 @@ async function captureNoteDetailPayload(page, noteId) {
|
|
|
244
277
|
}
|
|
245
278
|
return captured > 0 ? payload : null;
|
|
246
279
|
}
|
|
280
|
+
async function captureNoteDetailDomData(page) {
|
|
281
|
+
const result = await page.evaluate(`() => {
|
|
282
|
+
const norm = (value) => (value || '').trim();
|
|
283
|
+
const sections = Array.from(document.querySelectorAll('.shell-container')).map((container) => {
|
|
284
|
+
const containerText = norm(container.innerText);
|
|
285
|
+
const title = containerText.startsWith('互动数据')
|
|
286
|
+
? '互动数据'
|
|
287
|
+
: containerText.includes('基础数据')
|
|
288
|
+
? '基础数据'
|
|
289
|
+
: '';
|
|
290
|
+
const metrics = Array.from(container.querySelectorAll('.block-container.block')).map((block) => ({
|
|
291
|
+
label: norm(block.querySelector('.des')?.innerText),
|
|
292
|
+
value: norm(block.querySelector('.content')?.innerText),
|
|
293
|
+
extra: norm(block.querySelector('.text-with-fans')?.innerText),
|
|
294
|
+
})).filter((metric) => metric.label && metric.value);
|
|
295
|
+
return { title, metrics };
|
|
296
|
+
}).filter((section) => section.title && section.metrics.length > 0);
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
title: norm(document.querySelector('.note-title')?.innerText),
|
|
300
|
+
infoText: norm(document.querySelector('.note-info-content')?.innerText),
|
|
301
|
+
sections,
|
|
302
|
+
};
|
|
303
|
+
}`);
|
|
304
|
+
if (!result || typeof result !== 'object')
|
|
305
|
+
return null;
|
|
306
|
+
return result;
|
|
307
|
+
}
|
|
247
308
|
export async function fetchCreatorNoteDetailRows(page, noteId) {
|
|
248
309
|
await page.goto(`https://creator.xiaohongshu.com/statistics/note-detail?noteId=${encodeURIComponent(noteId)}`);
|
|
249
|
-
await page.
|
|
250
|
-
|
|
251
|
-
|
|
310
|
+
const domData = await captureNoteDetailDomData(page).catch(() => null);
|
|
311
|
+
let rows = parseCreatorNoteDetailDomData(domData, noteId);
|
|
312
|
+
if (rows.length === 0) {
|
|
313
|
+
const bodyText = await page.evaluate('() => document.body.innerText');
|
|
314
|
+
rows = parseCreatorNoteDetailText(typeof bodyText === 'string' ? bodyText : '', noteId);
|
|
315
|
+
}
|
|
252
316
|
const apiPayload = await captureNoteDetailPayload(page, noteId).catch(() => null);
|
|
253
317
|
appendTrendRows(rows, apiPayload ?? undefined);
|
|
254
318
|
appendAudienceRows(rows, apiPayload ?? undefined);
|
|
@@ -262,11 +326,11 @@ cli({
|
|
|
262
326
|
strategy: Strategy.COOKIE,
|
|
263
327
|
browser: true,
|
|
264
328
|
args: [
|
|
265
|
-
{ name: '
|
|
329
|
+
{ name: 'note-id', type: 'string', required: true, help: 'Note ID (from creator-notes or note-detail page URL)' },
|
|
266
330
|
],
|
|
267
331
|
columns: ['section', 'metric', 'value', 'extra'],
|
|
268
332
|
func: async (page, kwargs) => {
|
|
269
|
-
const noteId = kwargs
|
|
333
|
+
const noteId = kwargs['note-id'];
|
|
270
334
|
const rows = await fetchCreatorNoteDetailRows(page, noteId);
|
|
271
335
|
const hasCoreMetric = rows.some((row) => row.section !== '笔记信息' && row.value);
|
|
272
336
|
if (!hasCoreMetric) {
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import { getRegistry } from '../../registry.js';
|
|
3
|
-
import { appendAudienceRows, appendTrendRows, parseCreatorNoteDetailText } from './creator-note-detail.js';
|
|
3
|
+
import { appendAudienceRows, appendTrendRows, parseCreatorNoteDetailDomData, parseCreatorNoteDetailText } from './creator-note-detail.js';
|
|
4
4
|
import './creator-note-detail.js';
|
|
5
5
|
function createPageMock(evaluateResult) {
|
|
6
|
+
const evaluate = Array.isArray(evaluateResult)
|
|
7
|
+
? vi.fn()
|
|
8
|
+
.mockResolvedValueOnce(evaluateResult[0])
|
|
9
|
+
.mockResolvedValue(evaluateResult[evaluateResult.length - 1])
|
|
10
|
+
: vi.fn().mockResolvedValue(evaluateResult);
|
|
6
11
|
return {
|
|
7
12
|
goto: vi.fn().mockResolvedValue(undefined),
|
|
8
|
-
evaluate
|
|
13
|
+
evaluate,
|
|
9
14
|
snapshot: vi.fn().mockResolvedValue(undefined),
|
|
10
15
|
click: vi.fn().mockResolvedValue(undefined),
|
|
11
16
|
typeText: vi.fn().mockResolvedValue(undefined),
|
|
@@ -88,6 +93,46 @@ describe('xiaohongshu creator-note-detail', () => {
|
|
|
88
93
|
{ section: '互动数据', metric: '分享数', value: '6', extra: '粉丝占比 0%' },
|
|
89
94
|
]);
|
|
90
95
|
});
|
|
96
|
+
it('parses structured note detail dom data into rows', () => {
|
|
97
|
+
expect(parseCreatorNoteDetailDomData({
|
|
98
|
+
title: '神雕侠侣战力金字塔',
|
|
99
|
+
infoText: '神雕侠侣战力金字塔\n#武侠\n2025-12-04 19:45\n切换笔记',
|
|
100
|
+
sections: [
|
|
101
|
+
{
|
|
102
|
+
title: '基础数据',
|
|
103
|
+
metrics: [
|
|
104
|
+
{ label: '曝光数', value: '898204', extra: '粉丝占比 0.5%' },
|
|
105
|
+
{ label: '观看数', value: '148284', extra: '粉丝占比 0.6%' },
|
|
106
|
+
{ label: '封面点击率', value: '17.1%', extra: '粉丝 19.1%' },
|
|
107
|
+
{ label: '平均观看时长', value: '30.1秒', extra: '粉丝 17.7秒' },
|
|
108
|
+
{ label: '涨粉数', value: '101', extra: '' },
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
title: '互动数据',
|
|
113
|
+
metrics: [
|
|
114
|
+
{ label: '点赞数', value: '2280', extra: '粉丝占比 3.6%' },
|
|
115
|
+
{ label: '评论数', value: '319', extra: '粉丝占比 9.4%' },
|
|
116
|
+
{ label: '收藏数', value: '466', extra: '粉丝占比 9.4%' },
|
|
117
|
+
{ label: '分享数', value: '33', extra: '粉丝占比 17.7%' },
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
}, '693155fc000000000d03b42c')).toEqual([
|
|
122
|
+
{ section: '笔记信息', metric: 'note_id', value: '693155fc000000000d03b42c', extra: '' },
|
|
123
|
+
{ section: '笔记信息', metric: 'title', value: '神雕侠侣战力金字塔', extra: '' },
|
|
124
|
+
{ section: '笔记信息', metric: 'published_at', value: '2025-12-04 19:45', extra: '' },
|
|
125
|
+
{ section: '基础数据', metric: '曝光数', value: '898204', extra: '粉丝占比 0.5%' },
|
|
126
|
+
{ section: '基础数据', metric: '观看数', value: '148284', extra: '粉丝占比 0.6%' },
|
|
127
|
+
{ section: '基础数据', metric: '封面点击率', value: '17.1%', extra: '粉丝 19.1%' },
|
|
128
|
+
{ section: '基础数据', metric: '平均观看时长', value: '30.1秒', extra: '粉丝 17.7秒' },
|
|
129
|
+
{ section: '基础数据', metric: '涨粉数', value: '101', extra: '' },
|
|
130
|
+
{ section: '互动数据', metric: '点赞数', value: '2280', extra: '粉丝占比 3.6%' },
|
|
131
|
+
{ section: '互动数据', metric: '评论数', value: '319', extra: '粉丝占比 9.4%' },
|
|
132
|
+
{ section: '互动数据', metric: '收藏数', value: '466', extra: '粉丝占比 9.4%' },
|
|
133
|
+
{ section: '互动数据', metric: '分享数', value: '33', extra: '粉丝占比 17.7%' },
|
|
134
|
+
]);
|
|
135
|
+
});
|
|
91
136
|
it('appends audience source and portrait rows from API payloads', () => {
|
|
92
137
|
const rows = appendAudienceRows([], {
|
|
93
138
|
audienceSource: {
|
|
@@ -161,38 +206,40 @@ describe('xiaohongshu creator-note-detail', () => {
|
|
|
161
206
|
it('navigates to the note detail page and returns parsed rows', async () => {
|
|
162
207
|
const cmd = getRegistry().get('xiaohongshu/creator-note-detail');
|
|
163
208
|
expect(cmd?.func).toBeTypeOf('function');
|
|
164
|
-
const page = createPageMock(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
粉丝占比
|
|
173
|
-
|
|
174
|
-
12%
|
|
175
|
-
粉丝
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
8
|
|
183
|
-
粉丝占比
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
209
|
+
const page = createPageMock([
|
|
210
|
+
{
|
|
211
|
+
title: '示例笔记',
|
|
212
|
+
infoText: '示例笔记\n2026-03-19 12:00\n切换笔记',
|
|
213
|
+
sections: [
|
|
214
|
+
{
|
|
215
|
+
title: '基础数据',
|
|
216
|
+
metrics: [
|
|
217
|
+
{ label: '曝光数', value: '100', extra: '粉丝占比 10%' },
|
|
218
|
+
{ label: '观看数', value: '50', extra: '粉丝占比 20%' },
|
|
219
|
+
{ label: '封面点击率', value: '12%', extra: '粉丝 11%' },
|
|
220
|
+
{ label: '平均观看时长', value: '30秒', extra: '粉丝 31秒' },
|
|
221
|
+
{ label: '涨粉数', value: '2', extra: '' },
|
|
222
|
+
],
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
title: '互动数据',
|
|
226
|
+
metrics: [
|
|
227
|
+
{ label: '点赞数', value: '8', extra: '粉丝占比 25%' },
|
|
228
|
+
{ label: '评论数', value: '1', extra: '粉丝占比 0%' },
|
|
229
|
+
{ label: '收藏数', value: '3', extra: '粉丝占比 50%' },
|
|
230
|
+
{ label: '分享数', value: '0', extra: '粉丝占比 0%' },
|
|
231
|
+
],
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
},
|
|
235
|
+
null,
|
|
236
|
+
null,
|
|
237
|
+
null,
|
|
238
|
+
null,
|
|
239
|
+
]);
|
|
240
|
+
const result = await cmd.func(page, { 'note-id': 'demo-note-id' });
|
|
194
241
|
expect(page.goto.mock.calls[0][0]).toBe('https://creator.xiaohongshu.com/statistics/note-detail?noteId=demo-note-id');
|
|
195
|
-
expect(page.evaluate.mock.calls[0][0]).
|
|
242
|
+
expect(page.evaluate.mock.calls[0][0]).toContain("document.querySelector('.note-title')");
|
|
196
243
|
expect(result).toEqual([
|
|
197
244
|
{ section: '笔记信息', metric: 'note_id', value: 'demo-note-id', extra: '' },
|
|
198
245
|
{ section: '笔记信息', metric: 'title', value: '示例笔记', extra: '' },
|
|
@@ -59,9 +59,9 @@ export function parseCreatorNotesText(bodyText) {
|
|
|
59
59
|
title,
|
|
60
60
|
date: dateMatch[1],
|
|
61
61
|
views: metrics[0] ?? 0,
|
|
62
|
-
likes: metrics[
|
|
63
|
-
collects: metrics[
|
|
64
|
-
comments: metrics[
|
|
62
|
+
likes: metrics[2] ?? 0,
|
|
63
|
+
collects: metrics[3] ?? 0,
|
|
64
|
+
comments: metrics[1] ?? 0,
|
|
65
65
|
url: '',
|
|
66
66
|
});
|
|
67
67
|
i = cursor - 1;
|
|
@@ -80,6 +80,18 @@ export function parseCreatorNoteIdsFromHtml(bodyHtml) {
|
|
|
80
80
|
}
|
|
81
81
|
return ids;
|
|
82
82
|
}
|
|
83
|
+
function mapDomCards(cards) {
|
|
84
|
+
return cards.map((card) => ({
|
|
85
|
+
id: card.id,
|
|
86
|
+
title: card.title,
|
|
87
|
+
date: card.date,
|
|
88
|
+
views: card.metrics[0] ?? 0,
|
|
89
|
+
likes: card.metrics[2] ?? 0,
|
|
90
|
+
collects: card.metrics[3] ?? 0,
|
|
91
|
+
comments: card.metrics[1] ?? 0,
|
|
92
|
+
url: buildNoteDetailUrl(card.id),
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
83
95
|
function mapAnalyzeItems(items) {
|
|
84
96
|
return (items ?? []).map((item) => ({
|
|
85
97
|
id: item.id ?? '',
|
|
@@ -97,7 +109,6 @@ async function fetchCreatorNotesByApi(page, limit) {
|
|
|
97
109
|
const maxPages = Math.max(1, Math.ceil(limit / pageSize));
|
|
98
110
|
const notes = [];
|
|
99
111
|
await page.goto(`https://creator.xiaohongshu.com/statistics/data-analysis?type=0&page_size=${pageSize}&page_num=1`);
|
|
100
|
-
await page.wait(4);
|
|
101
112
|
for (let pageNum = 1; pageNum <= maxPages && notes.length < limit; pageNum++) {
|
|
102
113
|
const apiPath = `${NOTE_ANALYZE_API_PATH}?type=0&page_size=${pageSize}&page_num=${pageNum}`;
|
|
103
114
|
const fetched = await page.evaluate(`
|
|
@@ -139,9 +150,28 @@ export async function fetchCreatorNotes(page, limit) {
|
|
|
139
150
|
let notes = await fetchCreatorNotesByApi(page, limit);
|
|
140
151
|
if (notes.length === 0) {
|
|
141
152
|
await page.goto('https://creator.xiaohongshu.com/new/note-manager');
|
|
142
|
-
await page.wait(4);
|
|
143
153
|
const maxPageDowns = Math.max(0, Math.ceil(limit / 10) + 1);
|
|
144
154
|
for (let i = 0; i <= maxPageDowns; i++) {
|
|
155
|
+
const domCards = await page.evaluate(`() => {
|
|
156
|
+
const noteIdRe = /"noteId":"([0-9a-f]{24})"/;
|
|
157
|
+
return Array.from(document.querySelectorAll('div.note[data-impression], div.note')).map((card) => {
|
|
158
|
+
const impression = card.getAttribute('data-impression') || '';
|
|
159
|
+
const id = impression.match(noteIdRe)?.[1] || '';
|
|
160
|
+
const title = (card.querySelector('.title, .raw')?.innerText || '').trim();
|
|
161
|
+
const dateText = (card.querySelector('.time_status, .time')?.innerText || '').trim();
|
|
162
|
+
const date = dateText.replace(/^发布于\\s*/, '');
|
|
163
|
+
const metrics = Array.from(card.querySelectorAll('.icon_list .icon'))
|
|
164
|
+
.map((el) => parseInt((el.innerText || '').trim(), 10))
|
|
165
|
+
.filter((value) => Number.isFinite(value));
|
|
166
|
+
return { id, title, date, metrics };
|
|
167
|
+
});
|
|
168
|
+
}`);
|
|
169
|
+
const parsedDomNotes = mapDomCards(Array.isArray(domCards) ? domCards : []).filter((note) => note.title && note.date);
|
|
170
|
+
if (parsedDomNotes.length > 0) {
|
|
171
|
+
notes = parsedDomNotes;
|
|
172
|
+
}
|
|
173
|
+
if (notes.length >= limit || (notes.length > 0 && i === 0))
|
|
174
|
+
break;
|
|
145
175
|
const body = await page.evaluate('() => ({ text: document.body.innerText, html: document.body.innerHTML })');
|
|
146
176
|
const bodyText = typeof body?.text === 'string' ? body.text : '';
|
|
147
177
|
const bodyHtml = typeof body?.html === 'string' ? body.html : '';
|
|
@@ -64,9 +64,9 @@ describe('xiaohongshu creator-notes', () => {
|
|
|
64
64
|
title: '神雕侠侣战力金字塔',
|
|
65
65
|
date: '2025年12月04日 19:45',
|
|
66
66
|
views: 148208,
|
|
67
|
-
likes:
|
|
68
|
-
collects:
|
|
69
|
-
comments:
|
|
67
|
+
likes: 2279,
|
|
68
|
+
collects: 465,
|
|
69
|
+
comments: 324,
|
|
70
70
|
url: '',
|
|
71
71
|
},
|
|
72
72
|
{
|
|
@@ -107,13 +107,42 @@ describe('xiaohongshu creator-notes', () => {
|
|
|
107
107
|
title: '示例笔记',
|
|
108
108
|
date: '2026年03月19日 12:00',
|
|
109
109
|
views: 10,
|
|
110
|
-
likes:
|
|
111
|
-
collects:
|
|
112
|
-
comments:
|
|
110
|
+
likes: 3,
|
|
111
|
+
collects: 4,
|
|
112
|
+
comments: 2,
|
|
113
113
|
url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=69ba940500000000200384db',
|
|
114
114
|
},
|
|
115
115
|
]);
|
|
116
116
|
});
|
|
117
|
+
it('prefers note card dom data when the analyze api is unavailable', async () => {
|
|
118
|
+
const cmd = getRegistry().get('xiaohongshu/creator-notes');
|
|
119
|
+
expect(cmd?.func).toBeTypeOf('function');
|
|
120
|
+
const page = createPageMock([
|
|
121
|
+
undefined,
|
|
122
|
+
[
|
|
123
|
+
{
|
|
124
|
+
id: '693155fc000000000d03b42c',
|
|
125
|
+
title: '神雕侠侣战力金字塔',
|
|
126
|
+
date: '2025年12月04日 19:45',
|
|
127
|
+
metrics: [148284, 319, 2280, 466, 33],
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
]);
|
|
131
|
+
const result = await cmd.func(page, { limit: 1 });
|
|
132
|
+
expect(result).toEqual([
|
|
133
|
+
{
|
|
134
|
+
rank: 1,
|
|
135
|
+
id: '693155fc000000000d03b42c',
|
|
136
|
+
title: '神雕侠侣战力金字塔',
|
|
137
|
+
date: '2025年12月04日 19:45',
|
|
138
|
+
views: 148284,
|
|
139
|
+
likes: 2280,
|
|
140
|
+
collects: 466,
|
|
141
|
+
comments: 319,
|
|
142
|
+
url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=693155fc000000000d03b42c',
|
|
143
|
+
},
|
|
144
|
+
]);
|
|
145
|
+
});
|
|
117
146
|
it('prefers the creator analyze API and preserves note ids', async () => {
|
|
118
147
|
const cmd = getRegistry().get('xiaohongshu/creator-notes');
|
|
119
148
|
expect(cmd?.func).toBeTypeOf('function');
|
|
@@ -16,16 +16,15 @@ cli({
|
|
|
16
16
|
domain: 'www.xiaohongshu.com',
|
|
17
17
|
strategy: Strategy.COOKIE,
|
|
18
18
|
args: [
|
|
19
|
-
{ name: '
|
|
19
|
+
{ name: 'note-id', required: true, help: 'Note ID (from URL)' },
|
|
20
20
|
{ name: 'output', default: './xiaohongshu-downloads', help: 'Output directory' },
|
|
21
21
|
],
|
|
22
22
|
columns: ['index', 'type', 'status', 'size'],
|
|
23
23
|
func: async (page, kwargs) => {
|
|
24
|
-
const noteId = kwargs
|
|
24
|
+
const noteId = kwargs['note-id'];
|
|
25
25
|
const output = kwargs.output;
|
|
26
26
|
// Navigate to note page
|
|
27
27
|
await page.goto(`https://www.xiaohongshu.com/explore/${noteId}`);
|
|
28
|
-
await page.wait(3);
|
|
29
28
|
// Extract note info and media URLs
|
|
30
29
|
const data = await page.evaluate(`
|
|
31
30
|
(() => {
|
|
@@ -13,12 +13,12 @@ cli({
|
|
|
13
13
|
domain: 'www.xiaohongshu.com',
|
|
14
14
|
strategy: Strategy.COOKIE,
|
|
15
15
|
args: [
|
|
16
|
-
{ name: '
|
|
16
|
+
{ name: 'query', required: true, positional: true, help: 'Search keyword' },
|
|
17
17
|
{ name: 'limit', type: 'int', default: 20, help: 'Number of results' },
|
|
18
18
|
],
|
|
19
19
|
columns: ['rank', 'title', 'author', 'likes'],
|
|
20
20
|
func: async (page, kwargs) => {
|
|
21
|
-
const keyword = encodeURIComponent(kwargs.
|
|
21
|
+
const keyword = encodeURIComponent(kwargs.query);
|
|
22
22
|
await page.goto(`https://www.xiaohongshu.com/search_result?keyword=${keyword}&source=web_search_result_notes`);
|
|
23
23
|
await page.wait(3);
|
|
24
24
|
// Scroll a couple of times to load more results
|
|
@@ -27,7 +27,7 @@ cli({
|
|
|
27
27
|
strategy: Strategy.COOKIE,
|
|
28
28
|
browser: true,
|
|
29
29
|
args: [
|
|
30
|
-
{ name: 'id', type: 'string', required: true, help: 'User id or profile URL' },
|
|
30
|
+
{ name: 'id', type: 'string', required: true, positional: true, help: 'User id or profile URL' },
|
|
31
31
|
{ name: 'limit', type: 'int', default: 15, help: 'Number of notes to return' },
|
|
32
32
|
],
|
|
33
33
|
columns: ['id', 'title', 'type', 'likes', 'url'],
|
|
@@ -35,7 +35,6 @@ cli({
|
|
|
35
35
|
const userId = normalizeXhsUserId(String(kwargs.id));
|
|
36
36
|
const limit = Math.max(1, Number(kwargs.limit ?? 15));
|
|
37
37
|
await page.goto(`https://www.xiaohongshu.com/user/profile/${userId}`);
|
|
38
|
-
await page.wait(3);
|
|
39
38
|
let snapshot = await readUserSnapshot(page);
|
|
40
39
|
let results = extractXhsUserNotes(snapshot ?? {}, userId);
|
|
41
40
|
let previousCount = results.length;
|
|
@@ -16,7 +16,6 @@ cli({
|
|
|
16
16
|
func: async (page, kwargs) => {
|
|
17
17
|
const symbol = kwargs.symbol.toUpperCase().trim();
|
|
18
18
|
await page.goto(`https://finance.yahoo.com/quote/${encodeURIComponent(symbol)}/`);
|
|
19
|
-
await page.wait(3);
|
|
20
19
|
const data = await page.evaluate(`
|
|
21
20
|
(async () => {
|
|
22
21
|
const sym = '${symbol}';
|
|
@@ -10,7 +10,7 @@ cli({
|
|
|
10
10
|
domain: 'www.youtube.com',
|
|
11
11
|
strategy: Strategy.COOKIE,
|
|
12
12
|
args: [
|
|
13
|
-
{ name: 'query', required: true, help: 'Search query' },
|
|
13
|
+
{ name: 'query', required: true, positional: true, help: 'Search query' },
|
|
14
14
|
{ name: 'limit', type: 'int', default: 20, help: 'Max results (max 50)' },
|
|
15
15
|
],
|
|
16
16
|
columns: ['rank', 'title', 'channel', 'views', 'duration', 'url'],
|
|
@@ -19,7 +19,7 @@ cli({
|
|
|
19
19
|
domain: 'www.youtube.com',
|
|
20
20
|
strategy: Strategy.COOKIE,
|
|
21
21
|
args: [
|
|
22
|
-
{ name: 'url', required: true, help: 'YouTube video URL or video ID' },
|
|
22
|
+
{ name: 'url', required: true, positional: true, help: 'YouTube video URL or video ID' },
|
|
23
23
|
{ name: 'lang', required: false, help: 'Language code (e.g. en, zh-Hans). Omit to auto-select' },
|
|
24
24
|
{ name: 'mode', required: false, default: 'grouped', help: 'Output mode: grouped (readable paragraphs) or raw (every segment)' },
|
|
25
25
|
],
|
|
@@ -10,7 +10,7 @@ cli({
|
|
|
10
10
|
domain: 'www.youtube.com',
|
|
11
11
|
strategy: Strategy.COOKIE,
|
|
12
12
|
args: [
|
|
13
|
-
{ name: 'url', required: true, help: 'YouTube video URL or video ID' },
|
|
13
|
+
{ name: 'url', required: true, positional: true, help: 'YouTube video URL or video ID' },
|
|
14
14
|
],
|
|
15
15
|
columns: ['field', 'value'],
|
|
16
16
|
func: async (page, kwargs) => {
|
|
@@ -72,7 +72,7 @@ cli({
|
|
|
72
72
|
domain: 'zhuanlan.zhihu.com',
|
|
73
73
|
strategy: Strategy.COOKIE,
|
|
74
74
|
args: [
|
|
75
|
-
{ name: 'url', required: true, help: 'Article URL (zhuanlan.zhihu.com/p/xxx)' },
|
|
75
|
+
{ name: 'url', required: true, positional: true, help: 'Article URL (zhuanlan.zhihu.com/p/xxx)' },
|
|
76
76
|
{ name: 'output', default: './zhihu-articles', help: 'Output directory' },
|
|
77
77
|
{ name: 'download-images', type: 'boolean', default: false, help: 'Download images locally' },
|
|
78
78
|
],
|
|
@@ -83,7 +83,6 @@ cli({
|
|
|
83
83
|
const downloadImages = kwargs['download-images'];
|
|
84
84
|
// Navigate to article page
|
|
85
85
|
await page.goto(url);
|
|
86
|
-
await page.wait(3);
|
|
87
86
|
// Extract article content
|
|
88
87
|
const data = await page.evaluate(`
|
|
89
88
|
(() => {
|
|
@@ -6,7 +6,7 @@ cli({
|
|
|
6
6
|
domain: 'www.zhihu.com',
|
|
7
7
|
strategy: Strategy.COOKIE,
|
|
8
8
|
args: [
|
|
9
|
-
{ name: 'id', required: true, help: 'Question ID (numeric)' },
|
|
9
|
+
{ name: 'id', required: true, positional: true, help: 'Question ID (numeric)' },
|
|
10
10
|
{ name: 'limit', type: 'int', default: 5, help: 'Number of answers' },
|
|
11
11
|
],
|
|
12
12
|
columns: ['rank', 'author', 'votes', 'content'],
|
|
@@ -4,10 +4,11 @@ description: 知乎搜索
|
|
|
4
4
|
domain: www.zhihu.com
|
|
5
5
|
|
|
6
6
|
args:
|
|
7
|
-
|
|
7
|
+
query:
|
|
8
|
+
positional: true
|
|
8
9
|
type: str
|
|
9
10
|
required: true
|
|
10
|
-
description: Search
|
|
11
|
+
description: Search query
|
|
11
12
|
limit:
|
|
12
13
|
type: int
|
|
13
14
|
default: 10
|
|
@@ -19,7 +20,7 @@ pipeline:
|
|
|
19
20
|
- evaluate: |
|
|
20
21
|
(async () => {
|
|
21
22
|
const strip = (html) => (html || '').replace(/<[^>]+>/g, '').replace(/ /g, ' ').replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&').replace(/<em>/g, '').replace(/<\/em>/g, '').trim();
|
|
22
|
-
const keyword = ${{ args.
|
|
23
|
+
const keyword = ${{ args.query | json }};
|
|
23
24
|
const limit = ${{ args.limit }};
|
|
24
25
|
const res = await fetch('https://www.zhihu.com/api/v4/search_v3?q=' + encodeURIComponent(keyword) + '&t=general&offset=0&limit=' + limit, {
|
|
25
26
|
credentials: 'include'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commander adapter: bridges Registry commands to Commander subcommands.
|
|
3
|
+
*
|
|
4
|
+
* This is a THIN adapter — it only handles:
|
|
5
|
+
* 1. Commander arg/option registration
|
|
6
|
+
* 2. Collecting kwargs from Commander's action args
|
|
7
|
+
* 3. Calling executeCommand (which handles browser sessions, validation, etc.)
|
|
8
|
+
* 4. Rendering output and errors
|
|
9
|
+
*
|
|
10
|
+
* All execution logic lives in execution.ts.
|
|
11
|
+
*/
|
|
12
|
+
import { Command } from 'commander';
|
|
13
|
+
import { type CliCommand } from './registry.js';
|
|
14
|
+
/**
|
|
15
|
+
* Register a single CliCommand as a Commander subcommand.
|
|
16
|
+
*/
|
|
17
|
+
export declare function registerCommandToProgram(siteCmd: Command, cmd: CliCommand): void;
|
|
18
|
+
/**
|
|
19
|
+
* Register all commands from the registry onto a Commander program.
|
|
20
|
+
*/
|
|
21
|
+
export declare function registerAllCommands(program: Command, siteGroups: Map<string, Command>): void;
|