@jackwener/opencli 1.5.5 → 1.5.7
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/CHANGELOG.md +26 -0
- package/README.md +31 -4
- package/README.zh-CN.md +40 -5
- package/SKILL.md +1 -1
- package/dist/browser/cdp.d.ts +1 -0
- package/dist/browser/cdp.js +30 -27
- package/dist/browser/daemon-client.d.ts +11 -1
- package/dist/browser/daemon-client.js +3 -0
- package/dist/browser/dom-helpers.js +1 -0
- package/dist/browser/dom-helpers.test.js +14 -1
- package/dist/browser/mcp.js +18 -13
- package/dist/browser/page.d.ts +6 -0
- package/dist/browser/page.js +37 -2
- package/dist/browser/page.test.d.ts +1 -0
- package/dist/browser/page.test.js +44 -0
- package/dist/browser/stealth.js +198 -0
- package/dist/browser/stealth.test.d.ts +1 -0
- package/dist/browser/stealth.test.js +134 -0
- package/dist/browser.test.js +1 -1
- package/dist/build-manifest.d.ts +1 -0
- package/dist/build-manifest.js +5 -1
- package/dist/build-manifest.test.js +2 -0
- package/dist/cli-manifest.json +1821 -252
- package/dist/cli.js +20 -3
- package/dist/clis/antigravity/serve.d.ts +1 -1
- package/dist/clis/antigravity/serve.js +5 -8
- package/dist/clis/band/bands.d.ts +1 -0
- package/dist/clis/band/bands.js +72 -0
- package/dist/clis/band/mentions.d.ts +1 -0
- package/dist/clis/band/mentions.js +127 -0
- package/dist/clis/band/post.d.ts +1 -0
- package/dist/clis/band/post.js +175 -0
- package/dist/clis/band/posts.d.ts +1 -0
- package/dist/clis/band/posts.js +94 -0
- package/dist/clis/bilibili/subtitle.js +4 -0
- package/dist/clis/bilibili/subtitle.test.d.ts +1 -0
- package/dist/clis/bilibili/subtitle.test.js +48 -0
- package/dist/clis/chatwise/ask.js +0 -2
- package/dist/clis/chatwise/export.js +0 -2
- package/dist/clis/chatwise/history.js +0 -2
- package/dist/clis/chatwise/model.js +0 -2
- package/dist/clis/chatwise/new.js +1 -2
- package/dist/clis/chatwise/read.js +0 -2
- package/dist/clis/chatwise/screenshot.js +1 -2
- package/dist/clis/chatwise/send.js +0 -2
- package/dist/clis/chatwise/status.js +1 -2
- package/dist/clis/ctrip/search.d.ts +13 -0
- package/dist/clis/ctrip/search.js +73 -48
- package/dist/clis/ctrip/search.test.d.ts +1 -0
- package/dist/clis/ctrip/search.test.js +64 -0
- package/dist/clis/doubao/detail.d.ts +1 -0
- package/dist/clis/doubao/detail.js +33 -0
- package/dist/clis/doubao/detail.test.d.ts +1 -0
- package/dist/clis/doubao/detail.test.js +42 -0
- package/dist/clis/doubao/history.d.ts +1 -0
- package/dist/clis/doubao/history.js +28 -0
- package/dist/clis/doubao/history.test.d.ts +1 -0
- package/dist/clis/doubao/history.test.js +37 -0
- package/dist/clis/doubao/meeting-summary.d.ts +1 -0
- package/dist/clis/doubao/meeting-summary.js +39 -0
- package/dist/clis/doubao/meeting-transcript.d.ts +1 -0
- package/dist/clis/doubao/meeting-transcript.js +36 -0
- package/dist/clis/doubao/utils.d.ts +27 -0
- package/dist/clis/doubao/utils.js +317 -0
- package/dist/clis/doubao/utils.test.d.ts +1 -0
- package/dist/clis/doubao/utils.test.js +24 -0
- package/dist/clis/douyin/_shared/public-api.d.ts +33 -0
- package/dist/clis/douyin/_shared/public-api.js +29 -0
- package/dist/clis/douyin/_shared/sts2.js +8 -2
- package/dist/clis/douyin/_shared/sts2.test.d.ts +1 -0
- package/dist/clis/douyin/_shared/sts2.test.js +27 -0
- package/dist/clis/douyin/activities.js +4 -2
- package/dist/clis/douyin/activities.test.js +34 -1
- package/dist/clis/douyin/collections.js +1 -1
- package/dist/clis/douyin/collections.test.js +24 -2
- package/dist/clis/douyin/draft.d.ts +8 -11
- package/dist/clis/douyin/draft.js +302 -185
- package/dist/clis/douyin/draft.test.d.ts +1 -1
- package/dist/clis/douyin/draft.test.js +357 -2
- package/dist/clis/douyin/hashtag.js +9 -2
- package/dist/clis/douyin/hashtag.test.js +35 -2
- package/dist/clis/douyin/profile.js +1 -1
- package/dist/clis/douyin/profile.test.js +36 -1
- package/dist/clis/douyin/user-videos.d.ts +5 -0
- package/dist/clis/douyin/user-videos.js +74 -0
- package/dist/clis/douyin/user-videos.test.d.ts +1 -0
- package/dist/clis/douyin/user-videos.test.js +108 -0
- package/dist/clis/douyin/videos.js +22 -5
- package/dist/clis/douyin/videos.test.js +45 -2
- package/dist/clis/facebook/search.test.d.ts +5 -0
- package/dist/clis/facebook/search.test.js +60 -0
- package/dist/clis/facebook/search.yaml +4 -3
- package/dist/clis/instagram/download.d.ts +16 -0
- package/dist/clis/instagram/download.js +225 -0
- package/dist/clis/instagram/download.test.d.ts +1 -0
- package/dist/clis/instagram/download.test.js +118 -0
- package/dist/clis/notebooklm/bind-current.d.ts +1 -0
- package/dist/clis/notebooklm/bind-current.js +29 -0
- package/dist/clis/notebooklm/bind-current.test.d.ts +1 -0
- package/dist/clis/notebooklm/bind-current.test.js +35 -0
- package/dist/clis/notebooklm/binding.test.d.ts +1 -0
- package/dist/clis/notebooklm/binding.test.js +44 -0
- package/dist/clis/notebooklm/compat.test.d.ts +3 -0
- package/dist/clis/notebooklm/compat.test.js +16 -0
- package/dist/clis/notebooklm/current.d.ts +1 -0
- package/dist/clis/notebooklm/current.js +28 -0
- package/dist/clis/notebooklm/get.d.ts +1 -0
- package/dist/clis/notebooklm/get.js +37 -0
- package/dist/clis/notebooklm/history.d.ts +1 -0
- package/dist/clis/notebooklm/history.js +25 -0
- package/dist/clis/notebooklm/history.test.d.ts +1 -0
- package/dist/clis/notebooklm/history.test.js +58 -0
- package/dist/clis/notebooklm/list.d.ts +1 -0
- package/dist/clis/notebooklm/list.js +35 -0
- package/dist/clis/notebooklm/note-list.d.ts +1 -0
- package/dist/clis/notebooklm/note-list.js +28 -0
- package/dist/clis/notebooklm/note-list.test.d.ts +1 -0
- package/dist/clis/notebooklm/note-list.test.js +56 -0
- package/dist/clis/notebooklm/notes-get.d.ts +1 -0
- package/dist/clis/notebooklm/notes-get.js +47 -0
- package/dist/clis/notebooklm/notes-get.test.d.ts +1 -0
- package/dist/clis/notebooklm/notes-get.test.js +72 -0
- package/dist/clis/notebooklm/rpc.d.ts +36 -0
- package/dist/clis/notebooklm/rpc.js +189 -0
- package/dist/clis/notebooklm/rpc.test.d.ts +1 -0
- package/dist/clis/notebooklm/rpc.test.js +105 -0
- package/dist/clis/notebooklm/shared.d.ts +87 -0
- package/dist/clis/notebooklm/shared.js +3 -0
- package/dist/clis/notebooklm/source-fulltext.d.ts +1 -0
- package/dist/clis/notebooklm/source-fulltext.js +44 -0
- package/dist/clis/notebooklm/source-fulltext.test.d.ts +1 -0
- package/dist/clis/notebooklm/source-fulltext.test.js +106 -0
- package/dist/clis/notebooklm/source-get.d.ts +1 -0
- package/dist/clis/notebooklm/source-get.js +40 -0
- package/dist/clis/notebooklm/source-get.test.d.ts +1 -0
- package/dist/clis/notebooklm/source-get.test.js +84 -0
- package/dist/clis/notebooklm/source-guide.d.ts +1 -0
- package/dist/clis/notebooklm/source-guide.js +44 -0
- package/dist/clis/notebooklm/source-guide.test.d.ts +1 -0
- package/dist/clis/notebooklm/source-guide.test.js +104 -0
- package/dist/clis/notebooklm/source-list.d.ts +1 -0
- package/dist/clis/notebooklm/source-list.js +30 -0
- package/dist/clis/notebooklm/status.d.ts +1 -0
- package/dist/clis/notebooklm/status.js +31 -0
- package/dist/clis/notebooklm/summary.d.ts +1 -0
- package/dist/clis/notebooklm/summary.js +30 -0
- package/dist/clis/notebooklm/summary.test.d.ts +1 -0
- package/dist/clis/notebooklm/summary.test.js +78 -0
- package/dist/clis/notebooklm/utils.d.ts +37 -0
- package/dist/clis/notebooklm/utils.js +739 -0
- package/dist/clis/notebooklm/utils.test.d.ts +1 -0
- package/dist/clis/notebooklm/utils.test.js +390 -0
- package/dist/clis/ones/common.d.ts +32 -0
- package/dist/clis/ones/common.js +144 -0
- package/dist/clis/ones/enrich-tasks.d.ts +5 -0
- package/dist/clis/ones/enrich-tasks.js +37 -0
- package/dist/clis/ones/login.d.ts +1 -0
- package/dist/clis/ones/login.js +80 -0
- package/dist/clis/ones/logout.d.ts +1 -0
- package/dist/clis/ones/logout.js +17 -0
- package/dist/clis/ones/me.d.ts +1 -0
- package/dist/clis/ones/me.js +30 -0
- package/dist/clis/ones/my-tasks.d.ts +1 -0
- package/dist/clis/ones/my-tasks.js +120 -0
- package/dist/clis/ones/resolve-labels.d.ts +10 -0
- package/dist/clis/ones/resolve-labels.js +64 -0
- package/dist/clis/ones/task-helpers.d.ts +29 -0
- package/dist/clis/ones/task-helpers.js +212 -0
- package/dist/clis/ones/task-helpers.test.d.ts +1 -0
- package/dist/clis/ones/task-helpers.test.js +12 -0
- package/dist/clis/ones/task.d.ts +1 -0
- package/dist/clis/ones/task.js +66 -0
- package/dist/clis/ones/tasks.d.ts +1 -0
- package/dist/clis/ones/tasks.js +79 -0
- package/dist/clis/ones/token-info.d.ts +1 -0
- package/dist/clis/ones/token-info.js +42 -0
- package/dist/clis/ones/worklog.d.ts +11 -0
- package/dist/clis/ones/worklog.js +267 -0
- package/dist/clis/ones/worklog.test.d.ts +1 -0
- package/dist/clis/ones/worklog.test.js +20 -0
- package/dist/clis/spotify/spotify.d.ts +1 -0
- package/dist/clis/spotify/spotify.js +316 -0
- package/dist/clis/spotify/utils.d.ts +21 -0
- package/dist/clis/spotify/utils.js +66 -0
- package/dist/clis/spotify/utils.test.d.ts +1 -0
- package/dist/clis/spotify/utils.test.js +67 -0
- package/dist/clis/substack/utils.d.ts +4 -0
- package/dist/clis/substack/utils.js +8 -2
- package/dist/clis/substack/utils.test.d.ts +1 -0
- package/dist/clis/substack/utils.test.js +46 -0
- package/dist/clis/tieba/commands.test.d.ts +4 -0
- package/dist/clis/tieba/commands.test.js +79 -0
- package/dist/clis/tieba/hot.d.ts +1 -0
- package/dist/clis/tieba/hot.js +48 -0
- package/dist/clis/tieba/posts.d.ts +1 -0
- package/dist/clis/tieba/posts.js +85 -0
- package/dist/clis/tieba/read.d.ts +1 -0
- package/dist/clis/tieba/read.js +140 -0
- package/dist/clis/tieba/search.d.ts +1 -0
- package/dist/clis/tieba/search.js +108 -0
- package/dist/clis/tieba/utils.d.ts +101 -0
- package/dist/clis/tieba/utils.js +240 -0
- package/dist/clis/tieba/utils.test.d.ts +1 -0
- package/dist/clis/tieba/utils.test.js +290 -0
- package/dist/clis/v2ex/hot.yaml +4 -1
- package/dist/clis/v2ex/latest.yaml +4 -1
- package/dist/clis/v2ex/topic.yaml +6 -1
- package/dist/clis/weixin/download.d.ts +9 -0
- package/dist/clis/weixin/download.js +76 -6
- package/dist/clis/weread/book.js +206 -13
- package/dist/clis/weread/commands.test.js +331 -0
- package/dist/clis/weread/private-api-regression.test.d.ts +1 -0
- package/dist/{weread-private-api-regression.test.js → clis/weread/private-api-regression.test.js} +92 -30
- package/dist/clis/weread/search-regression.test.d.ts +1 -0
- package/dist/clis/weread/search-regression.test.js +407 -0
- package/dist/clis/weread/search.js +143 -7
- package/dist/clis/weread/shelf.js +13 -95
- package/dist/clis/weread/utils.d.ts +56 -0
- package/dist/clis/weread/utils.js +234 -7
- package/dist/clis/weread/utils.test.js +71 -1
- package/dist/clis/xiaohongshu/comments.d.ts +3 -0
- package/dist/clis/xiaohongshu/comments.js +76 -17
- package/dist/clis/xiaohongshu/comments.test.js +70 -9
- package/dist/clis/xiaohongshu/download.d.ts +4 -1
- package/dist/clis/xiaohongshu/download.js +83 -22
- package/dist/clis/xiaohongshu/download.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/download.test.js +75 -0
- package/dist/clis/xiaohongshu/note-helpers.d.ts +12 -0
- package/dist/clis/xiaohongshu/note-helpers.js +23 -0
- package/dist/clis/xiaohongshu/note.d.ts +7 -0
- package/dist/clis/xiaohongshu/note.js +76 -0
- package/dist/clis/xiaohongshu/note.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/note.test.js +136 -0
- package/dist/clis/xiaohongshu/publish.d.ts +1 -1
- package/dist/clis/xiaohongshu/publish.js +78 -31
- package/dist/clis/xiaohongshu/publish.test.js +66 -1
- package/dist/clis/xiaohongshu/search.js +9 -0
- package/dist/clis/xiaohongshu/search.test.js +10 -4
- package/dist/clis/xiaohongshu/user-helpers.d.ts +1 -0
- package/dist/clis/xiaohongshu/user-helpers.js +2 -0
- package/dist/clis/xiaohongshu/user-helpers.test.js +18 -0
- package/dist/clis/xueqiu/comments.d.ts +118 -0
- package/dist/clis/xueqiu/comments.js +354 -0
- package/dist/clis/xueqiu/comments.test.d.ts +1 -0
- package/dist/clis/xueqiu/comments.test.js +696 -0
- package/dist/clis/youtube/search.js +57 -17
- package/dist/clis/youtube/transcript.js +2 -4
- package/dist/clis/youtube/utils.d.ts +9 -0
- package/dist/clis/youtube/utils.js +67 -3
- package/dist/clis/youtube/utils.test.d.ts +1 -0
- package/dist/clis/youtube/utils.test.js +37 -0
- package/dist/clis/youtube/video.js +16 -15
- package/dist/clis/zhihu/question.js +19 -17
- package/dist/clis/zhihu/question.test.d.ts +1 -0
- package/dist/clis/zhihu/question.test.js +54 -0
- package/dist/clis/zsxq/dynamics.d.ts +1 -0
- package/dist/clis/zsxq/dynamics.js +47 -0
- package/dist/clis/zsxq/groups.d.ts +1 -0
- package/dist/clis/zsxq/groups.js +32 -0
- package/dist/clis/zsxq/search.d.ts +1 -0
- package/dist/clis/zsxq/search.js +43 -0
- package/dist/clis/zsxq/search.test.d.ts +1 -0
- package/dist/clis/zsxq/search.test.js +24 -0
- package/dist/clis/zsxq/topic.d.ts +1 -0
- package/dist/clis/zsxq/topic.js +47 -0
- package/dist/clis/zsxq/topic.test.d.ts +1 -0
- package/dist/clis/zsxq/topic.test.js +29 -0
- package/dist/clis/zsxq/topics.d.ts +1 -0
- package/dist/clis/zsxq/topics.js +25 -0
- package/dist/clis/zsxq/topics.test.d.ts +1 -0
- package/dist/clis/zsxq/topics.test.js +24 -0
- package/dist/clis/zsxq/utils.d.ts +97 -0
- package/dist/clis/zsxq/utils.js +230 -0
- package/dist/commanderAdapter.js +10 -1
- package/dist/commanderAdapter.test.js +64 -0
- package/dist/commands/daemon.d.ts +9 -0
- package/dist/commands/daemon.js +124 -0
- package/dist/commands/daemon.test.d.ts +1 -0
- package/dist/commands/daemon.test.js +185 -0
- package/dist/completion.js +3 -1
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/daemon.d.ts +1 -1
- package/dist/daemon.js +25 -14
- package/dist/daemon.test.d.ts +1 -0
- package/dist/daemon.test.js +65 -0
- package/dist/discovery.d.ts +9 -0
- package/dist/discovery.js +47 -2
- package/dist/electron-apps.d.ts +29 -0
- package/dist/electron-apps.js +65 -0
- package/dist/electron-apps.test.d.ts +1 -0
- package/dist/electron-apps.test.js +43 -0
- package/dist/engine.test.js +41 -9
- package/dist/execution.js +20 -16
- package/dist/external-clis.yaml +17 -0
- package/dist/idle-manager.d.ts +19 -0
- package/dist/idle-manager.js +54 -0
- package/dist/launcher.d.ts +36 -0
- package/dist/launcher.js +152 -0
- package/dist/launcher.test.d.ts +1 -0
- package/dist/launcher.test.js +57 -0
- package/dist/main.js +3 -3
- package/dist/registry.d.ts +1 -0
- package/dist/registry.js +31 -3
- package/dist/registry.test.js +13 -0
- package/dist/runtime.d.ts +5 -3
- package/dist/runtime.js +12 -5
- package/dist/serialization.d.ts +1 -0
- package/dist/serialization.js +3 -0
- package/dist/serialization.test.js +17 -1
- package/dist/tui.d.ts +7 -0
- package/dist/tui.js +52 -0
- package/dist/tui.test.d.ts +1 -0
- package/dist/tui.test.js +19 -0
- package/dist/types.d.ts +5 -0
- package/dist/weixin-download.test.js +14 -0
- package/docs/.vitepress/config.mts +4 -0
- package/docs/adapters/browser/band.md +63 -0
- package/docs/adapters/browser/notebooklm.md +69 -0
- package/docs/adapters/browser/ones.md +59 -0
- package/docs/adapters/browser/spotify.md +62 -0
- package/docs/adapters/browser/tieba.md +45 -0
- package/docs/adapters/browser/xiaohongshu.md +19 -10
- package/docs/adapters/browser/xueqiu.md +5 -0
- package/docs/adapters/browser/zsxq.md +49 -0
- package/docs/adapters/index.md +67 -63
- package/docs/adapters-doc/ones.md +32 -0
- package/docs/guide/browser-bridge.md +12 -0
- package/docs/guide/troubleshooting.md +9 -4
- package/docs/superpowers/plans/2026-03-31-daemon-lifecycle-redesign.md +857 -0
- package/docs/superpowers/specs/2026-03-31-daemon-lifecycle-redesign.md +208 -0
- package/docs/zh/guide/browser-bridge.md +12 -0
- package/extension/dist/background.js +794 -513
- package/extension/src/background.test.ts +202 -2
- package/extension/src/background.ts +189 -10
- package/extension/src/cdp.ts +54 -0
- package/extension/src/protocol.ts +11 -5
- package/package.json +1 -1
- package/scripts/postinstall.js +16 -0
- package/src/browser/cdp.ts +24 -17
- package/src/browser/daemon-client.ts +11 -1
- package/src/browser/dom-helpers.test.ts +15 -1
- package/src/browser/dom-helpers.ts +1 -0
- package/src/browser/mcp.ts +18 -13
- package/src/browser/page.test.ts +58 -0
- package/src/browser/page.ts +34 -2
- package/src/browser/stealth.test.ts +153 -0
- package/src/browser/stealth.ts +198 -0
- package/src/browser.test.ts +1 -1
- package/src/build-manifest.test.ts +2 -0
- package/src/build-manifest.ts +6 -1
- package/src/cli.ts +21 -3
- package/src/clis/antigravity/SKILL.md +3 -12
- package/src/clis/antigravity/serve.ts +5 -10
- package/src/clis/band/bands.ts +76 -0
- package/src/clis/band/mentions.ts +134 -0
- package/src/clis/band/post.ts +187 -0
- package/src/clis/band/posts.ts +106 -0
- package/src/clis/bilibili/subtitle.test.ts +60 -0
- package/src/clis/bilibili/subtitle.ts +4 -0
- package/src/clis/chatwise/ask.ts +0 -2
- package/src/clis/chatwise/export.ts +0 -2
- package/src/clis/chatwise/history.ts +0 -2
- package/src/clis/chatwise/model.ts +0 -2
- package/src/clis/chatwise/new.ts +1 -2
- package/src/clis/chatwise/read.ts +0 -2
- package/src/clis/chatwise/screenshot.ts +1 -2
- package/src/clis/chatwise/send.ts +0 -2
- package/src/clis/chatwise/status.ts +1 -2
- package/src/clis/ctrip/search.test.ts +73 -0
- package/src/clis/ctrip/search.ts +97 -47
- package/src/clis/doubao/detail.test.ts +53 -0
- package/src/clis/doubao/detail.ts +41 -0
- package/src/clis/doubao/history.test.ts +45 -0
- package/src/clis/doubao/history.ts +32 -0
- package/src/clis/doubao/meeting-summary.ts +53 -0
- package/src/clis/doubao/meeting-transcript.ts +48 -0
- package/src/clis/doubao/utils.test.ts +45 -0
- package/src/clis/doubao/utils.ts +371 -0
- package/src/clis/douyin/_shared/public-api.ts +84 -0
- package/src/clis/douyin/_shared/sts2.test.ts +31 -0
- package/src/clis/douyin/_shared/sts2.ts +11 -3
- package/src/clis/douyin/activities.test.ts +41 -1
- package/src/clis/douyin/activities.ts +12 -3
- package/src/clis/douyin/collections.test.ts +35 -2
- package/src/clis/douyin/collections.ts +1 -1
- package/src/clis/douyin/draft.test.ts +444 -2
- package/src/clis/douyin/draft.ts +382 -218
- package/src/clis/douyin/hashtag.test.ts +42 -2
- package/src/clis/douyin/hashtag.ts +11 -3
- package/src/clis/douyin/profile.test.ts +43 -1
- package/src/clis/douyin/profile.ts +9 -2
- package/src/clis/douyin/user-videos.test.ts +122 -0
- package/src/clis/douyin/user-videos.ts +101 -0
- package/src/clis/douyin/videos.test.ts +52 -2
- package/src/clis/douyin/videos.ts +49 -15
- package/src/clis/facebook/search.test.ts +70 -0
- package/src/clis/facebook/search.yaml +4 -3
- package/src/clis/instagram/download.test.ts +159 -0
- package/src/clis/instagram/download.ts +286 -0
- package/src/clis/notebooklm/bind-current.test.ts +43 -0
- package/src/clis/notebooklm/bind-current.ts +36 -0
- package/src/clis/notebooklm/binding.test.ts +53 -0
- package/src/clis/notebooklm/compat.test.ts +19 -0
- package/src/clis/notebooklm/current.ts +38 -0
- package/src/clis/notebooklm/get.ts +53 -0
- package/src/clis/notebooklm/history.test.ts +70 -0
- package/src/clis/notebooklm/history.ts +36 -0
- package/src/clis/notebooklm/list.ts +40 -0
- package/src/clis/notebooklm/note-list.test.ts +64 -0
- package/src/clis/notebooklm/note-list.ts +42 -0
- package/src/clis/notebooklm/notes-get.test.ts +88 -0
- package/src/clis/notebooklm/notes-get.ts +67 -0
- package/src/clis/notebooklm/rpc.test.ts +126 -0
- package/src/clis/notebooklm/rpc.ts +286 -0
- package/src/clis/notebooklm/shared.ts +98 -0
- package/src/clis/notebooklm/source-fulltext.test.ts +123 -0
- package/src/clis/notebooklm/source-fulltext.ts +69 -0
- package/src/clis/notebooklm/source-get.test.ts +100 -0
- package/src/clis/notebooklm/source-get.ts +60 -0
- package/src/clis/notebooklm/source-guide.test.ts +121 -0
- package/src/clis/notebooklm/source-guide.ts +69 -0
- package/src/clis/notebooklm/source-list.ts +45 -0
- package/src/clis/notebooklm/status.ts +34 -0
- package/src/clis/notebooklm/summary.test.ts +94 -0
- package/src/clis/notebooklm/summary.ts +45 -0
- package/src/clis/notebooklm/utils.test.ts +446 -0
- package/src/clis/notebooklm/utils.ts +893 -0
- package/src/clis/ones/common.ts +187 -0
- package/src/clis/ones/enrich-tasks.ts +47 -0
- package/src/clis/ones/login.ts +103 -0
- package/src/clis/ones/logout.ts +19 -0
- package/src/clis/ones/me.ts +34 -0
- package/src/clis/ones/my-tasks.ts +148 -0
- package/src/clis/ones/resolve-labels.ts +80 -0
- package/src/clis/ones/task-helpers.test.ts +14 -0
- package/src/clis/ones/task-helpers.ts +214 -0
- package/src/clis/ones/task.ts +79 -0
- package/src/clis/ones/tasks.ts +92 -0
- package/src/clis/ones/token-info.ts +46 -0
- package/src/clis/ones/worklog.test.ts +24 -0
- package/src/clis/ones/worklog.ts +306 -0
- package/src/clis/spotify/spotify.ts +328 -0
- package/src/clis/spotify/utils.test.ts +87 -0
- package/src/clis/spotify/utils.ts +92 -0
- package/src/clis/substack/utils.test.ts +54 -0
- package/src/clis/substack/utils.ts +10 -2
- package/src/clis/tieba/commands.test.ts +86 -0
- package/src/clis/tieba/hot.ts +52 -0
- package/src/clis/tieba/posts.ts +108 -0
- package/src/clis/tieba/read.ts +158 -0
- package/src/clis/tieba/search.ts +119 -0
- package/src/clis/tieba/utils.test.ts +322 -0
- package/src/clis/tieba/utils.ts +348 -0
- package/src/clis/v2ex/hot.yaml +4 -1
- package/src/clis/v2ex/latest.yaml +4 -1
- package/src/clis/v2ex/topic.yaml +6 -1
- package/src/clis/weixin/download.ts +95 -6
- package/src/clis/weread/book.ts +256 -13
- package/src/clis/weread/commands.test.ts +409 -0
- package/src/{weread-private-api-regression.test.ts → clis/weread/private-api-regression.test.ts} +108 -30
- package/src/clis/weread/search-regression.test.ts +440 -0
- package/src/clis/weread/search.ts +189 -9
- package/src/clis/weread/shelf.ts +20 -122
- package/src/clis/weread/utils.test.ts +81 -1
- package/src/clis/weread/utils.ts +293 -7
- package/src/clis/xiaohongshu/comments.test.ts +85 -9
- package/src/clis/xiaohongshu/comments.ts +76 -17
- package/src/clis/xiaohongshu/download.test.ts +96 -0
- package/src/clis/xiaohongshu/download.ts +83 -22
- package/src/clis/xiaohongshu/note-helpers.ts +25 -0
- package/src/clis/xiaohongshu/note.test.ts +164 -0
- package/src/clis/xiaohongshu/note.ts +86 -0
- package/src/clis/xiaohongshu/publish.test.ts +79 -1
- package/src/clis/xiaohongshu/publish.ts +84 -30
- package/src/clis/xiaohongshu/search.test.ts +11 -4
- package/src/clis/xiaohongshu/search.ts +13 -0
- package/src/clis/xiaohongshu/user-helpers.test.ts +23 -0
- package/src/clis/xiaohongshu/user-helpers.ts +4 -0
- package/src/clis/xueqiu/comments.test.ts +823 -0
- package/src/clis/xueqiu/comments.ts +461 -0
- package/src/clis/youtube/search.ts +57 -17
- package/src/clis/youtube/transcript.ts +2 -4
- package/src/clis/youtube/utils.test.ts +43 -0
- package/src/clis/youtube/utils.ts +69 -0
- package/src/clis/youtube/video.ts +16 -15
- package/src/clis/zhihu/question.test.ts +71 -0
- package/src/clis/zhihu/question.ts +27 -15
- package/src/clis/zsxq/dynamics.ts +60 -0
- package/src/clis/zsxq/groups.ts +41 -0
- package/src/clis/zsxq/search.test.ts +29 -0
- package/src/clis/zsxq/search.ts +54 -0
- package/src/clis/zsxq/topic.test.ts +34 -0
- package/src/clis/zsxq/topic.ts +68 -0
- package/src/clis/zsxq/topics.test.ts +29 -0
- package/src/clis/zsxq/topics.ts +36 -0
- package/src/clis/zsxq/utils.ts +351 -0
- package/src/commanderAdapter.test.ts +77 -0
- package/src/commanderAdapter.ts +8 -1
- package/src/commands/daemon.test.ts +238 -0
- package/src/commands/daemon.ts +135 -0
- package/src/completion.ts +2 -1
- package/src/constants.ts +3 -0
- package/src/daemon.test.ts +88 -0
- package/src/daemon.ts +26 -14
- package/src/discovery.ts +52 -2
- package/src/electron-apps.test.ts +50 -0
- package/src/electron-apps.ts +89 -0
- package/src/engine.test.ts +45 -9
- package/src/execution.ts +24 -19
- package/src/external-clis.yaml +17 -0
- package/src/idle-manager.ts +60 -0
- package/src/launcher.test.ts +67 -0
- package/src/launcher.ts +185 -0
- package/src/main.ts +3 -2
- package/src/registry.test.ts +15 -0
- package/src/registry.ts +32 -3
- package/src/runtime.ts +13 -7
- package/src/serialization.test.ts +19 -1
- package/src/serialization.ts +2 -0
- package/src/tui.test.ts +23 -0
- package/src/tui.ts +65 -0
- package/src/types.ts +5 -0
- package/src/weixin-download.test.ts +27 -0
- package/tests/e2e/band-auth.test.ts +20 -0
- package/tests/e2e/browser-auth-helpers.ts +18 -0
- package/tests/e2e/browser-auth.test.ts +35 -47
- package/tests/e2e/browser-public-extended.test.ts +6 -2
- package/tests/e2e/browser-public.test.ts +288 -0
- package/tests/e2e/management.test.ts +1 -1
- package/tests/e2e/plugin-management.test.ts +1 -1
- package/vitest.config.ts +1 -0
- package/chatwise-opencli.ps1 +0 -82
- package/dist/clis/chatwise/shared.d.ts +0 -2
- package/dist/clis/chatwise/shared.js +0 -6
- package/dist/weread-private-api-regression.test.d.ts +0 -1
- package/dist/weread-search-regression.test.d.ts +0 -1
- package/dist/weread-search-regression.test.js +0 -39
- package/src/clis/chatwise/shared.ts +0 -8
- package/src/weread-search-regression.test.ts +0 -44
package/src/tui.ts
CHANGED
|
@@ -170,3 +170,68 @@ export async function checkboxPrompt(
|
|
|
170
170
|
draw();
|
|
171
171
|
});
|
|
172
172
|
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Simple yes/no confirmation prompt.
|
|
176
|
+
*
|
|
177
|
+
* In non-TTY environments, returns `defaultYes` (defaults to true) without blocking.
|
|
178
|
+
* In TTY, waits for a single keypress: y/Enter → true, n/Esc/q → false.
|
|
179
|
+
*/
|
|
180
|
+
export async function confirmPrompt(
|
|
181
|
+
message: string,
|
|
182
|
+
defaultYes: boolean = true,
|
|
183
|
+
): Promise<boolean> {
|
|
184
|
+
const { stdin, stdout } = process;
|
|
185
|
+
if (!stdin.isTTY) return defaultYes;
|
|
186
|
+
|
|
187
|
+
const hint = defaultYes ? '[Y/n]' : '[y/N]';
|
|
188
|
+
stdout.write(` ${message} ${chalk.dim(hint)} `);
|
|
189
|
+
|
|
190
|
+
return new Promise<boolean>((resolve) => {
|
|
191
|
+
const wasRaw = stdin.isRaw;
|
|
192
|
+
stdin.setRawMode(true);
|
|
193
|
+
stdin.resume();
|
|
194
|
+
|
|
195
|
+
function cleanup() {
|
|
196
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
197
|
+
stdin.pause();
|
|
198
|
+
stdin.removeListener('data', onData);
|
|
199
|
+
stdout.write('\n');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function onData(data: Buffer) {
|
|
203
|
+
const key = data.toString();
|
|
204
|
+
|
|
205
|
+
// Ctrl+C
|
|
206
|
+
if (key === '\x03') {
|
|
207
|
+
cleanup();
|
|
208
|
+
process.exit(EXIT_CODES.INTERRUPTED);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Enter — use default
|
|
212
|
+
if (key === '\r' || key === '\n') {
|
|
213
|
+
cleanup();
|
|
214
|
+
resolve(defaultYes);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// y/Y — yes
|
|
219
|
+
if (key === 'y' || key === 'Y') {
|
|
220
|
+
cleanup();
|
|
221
|
+
resolve(true);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// n/N/q/Esc — no
|
|
226
|
+
if (key === 'n' || key === 'N' || key === 'q' || key === '\x1b') {
|
|
227
|
+
cleanup();
|
|
228
|
+
resolve(false);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Ignore other keys
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
stdin.on('data', onData);
|
|
236
|
+
});
|
|
237
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -67,6 +67,11 @@ export interface IPage {
|
|
|
67
67
|
getInterceptedRequests(): Promise<any[]>;
|
|
68
68
|
waitForCapture(timeout?: number): Promise<void>;
|
|
69
69
|
screenshot(options?: ScreenshotOptions): Promise<string>;
|
|
70
|
+
/**
|
|
71
|
+
* Set local file paths on a file input element via CDP DOM.setFileInputFiles.
|
|
72
|
+
* Chrome reads the files directly — no base64 encoding or payload size limits.
|
|
73
|
+
*/
|
|
74
|
+
setFileInput?(files: string[], selector?: string): Promise<void>;
|
|
70
75
|
closeWindow?(): Promise<void>;
|
|
71
76
|
/** Returns the current page URL, or null if unavailable. */
|
|
72
77
|
getCurrentUrl?(): Promise<string | null>;
|
|
@@ -61,4 +61,31 @@ describe('weixin publish time extraction', () => {
|
|
|
61
61
|
'var create_time = "1711291080";',
|
|
62
62
|
)).toBe('2026年3月24日 22:38');
|
|
63
63
|
});
|
|
64
|
+
|
|
65
|
+
it('detects WeChat verification gate pages', async () => {
|
|
66
|
+
const mod = await loadModule();
|
|
67
|
+
|
|
68
|
+
expect(mod.detectWechatAccessIssue(
|
|
69
|
+
'环境异常 当前环境异常,完成验证后即可继续访问。 去验证',
|
|
70
|
+
'<html><body><a id="js_verify">去验证</a></body></html>',
|
|
71
|
+
)).toBe('environment verification required');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('browser access detector matches the server-side verifier', async () => {
|
|
75
|
+
const mod = await loadModule();
|
|
76
|
+
|
|
77
|
+
const detectInPage = eval(mod.buildDetectWechatAccessIssueJs()) as (pageText: string, htmlStr: string) => string;
|
|
78
|
+
|
|
79
|
+
expect(detectInPage(
|
|
80
|
+
'环境异常 当前环境异常,完成验证后即可继续访问。 去验证',
|
|
81
|
+
'<html>secitptpage/verify.html<a id="js_verify">去验证</a></html>',
|
|
82
|
+
)).toBe('environment verification required');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('picks the first non-empty WeChat metadata field', async () => {
|
|
86
|
+
const mod = await loadModule();
|
|
87
|
+
|
|
88
|
+
expect(mod.pickFirstWechatMetaText('', 'Name cleared', '数字生命卡兹克')).toBe('数字生命卡兹克');
|
|
89
|
+
expect(mod.pickFirstWechatMetaText('', ' 聊聊刚刚上线的PixVerse V6视频模型。 ')).toBe('聊聊刚刚上线的PixVerse V6视频模型。');
|
|
90
|
+
});
|
|
64
91
|
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, it } from 'vitest';
|
|
2
|
+
import { expectGracefulAuthFailure } from './browser-auth-helpers.js';
|
|
3
|
+
|
|
4
|
+
describe('band auth-required commands — graceful failure', () => {
|
|
5
|
+
it('band bands fails gracefully without login', async () => {
|
|
6
|
+
await expectGracefulAuthFailure(['band', 'bands', '-f', 'json']);
|
|
7
|
+
}, 60_000);
|
|
8
|
+
|
|
9
|
+
it('band mentions fails gracefully without login', async () => {
|
|
10
|
+
await expectGracefulAuthFailure(['band', 'mentions', '--limit', '3', '-f', 'json']);
|
|
11
|
+
}, 60_000);
|
|
12
|
+
|
|
13
|
+
it('band posts fails gracefully without login', async () => {
|
|
14
|
+
await expectGracefulAuthFailure(['band', 'posts', '58400480', '--limit', '3', '-f', 'json']);
|
|
15
|
+
}, 60_000);
|
|
16
|
+
|
|
17
|
+
it('band post fails gracefully without login', async () => {
|
|
18
|
+
await expectGracefulAuthFailure(['band', 'post', '58400480', '1', '-f', 'json']);
|
|
19
|
+
}, 60_000);
|
|
20
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { expect } from 'vitest';
|
|
2
|
+
import { runCli } from './helpers.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Verify a login-required command fails gracefully (no crash, no hang).
|
|
6
|
+
* Acceptable outcomes: exit code 1 with error message, OR timeout handled.
|
|
7
|
+
*/
|
|
8
|
+
export async function expectGracefulAuthFailure(args: string[]) {
|
|
9
|
+
const { stdout, stderr, code } = await runCli(args, { timeout: 60_000 });
|
|
10
|
+
// Should either fail with exit code 1 (error message) or succeed with empty data.
|
|
11
|
+
// The key assertion: it should NOT hang forever or crash with unhandled exception.
|
|
12
|
+
if (code !== 0) {
|
|
13
|
+
// Verify stderr has a meaningful error, not an unhandled crash.
|
|
14
|
+
const output = stderr + stdout;
|
|
15
|
+
expect(output.length).toBeGreaterThan(0);
|
|
16
|
+
}
|
|
17
|
+
// If it somehow succeeds (e.g., partial public data), that's fine too.
|
|
18
|
+
}
|
|
@@ -6,148 +6,136 @@
|
|
|
6
6
|
* These tests verify the error handling path, not the data extraction.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { describe, it
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Verify a login-required command fails gracefully (no crash, no hang).
|
|
14
|
-
* Acceptable outcomes: exit code 1 with error message, OR timeout handled.
|
|
15
|
-
*/
|
|
16
|
-
async function expectGracefulAuthFailure(args: string[], label: string) {
|
|
17
|
-
const { stdout, stderr, code } = await runCli(args, { timeout: 60_000 });
|
|
18
|
-
// Should either fail with exit code 1 (error message) or succeed with empty data
|
|
19
|
-
// The key assertion: it should NOT hang forever or crash with unhandled exception
|
|
20
|
-
if (code !== 0) {
|
|
21
|
-
// Verify stderr has a meaningful error, not an unhandled crash
|
|
22
|
-
const output = stderr + stdout;
|
|
23
|
-
expect(output.length).toBeGreaterThan(0);
|
|
24
|
-
}
|
|
25
|
-
// If it somehow succeeds (e.g., partial public data), that's fine too
|
|
26
|
-
}
|
|
9
|
+
import { describe, it } from 'vitest';
|
|
10
|
+
import { expectGracefulAuthFailure } from './browser-auth-helpers.js';
|
|
27
11
|
|
|
28
12
|
describe('login-required commands — graceful failure', () => {
|
|
29
13
|
|
|
30
14
|
// ── bilibili (requires cookie session) ──
|
|
31
15
|
it('bilibili me fails gracefully without login', async () => {
|
|
32
|
-
await expectGracefulAuthFailure(['bilibili', 'me', '-f', 'json']
|
|
16
|
+
await expectGracefulAuthFailure(['bilibili', 'me', '-f', 'json']);
|
|
33
17
|
}, 60_000);
|
|
34
18
|
|
|
35
19
|
it('bilibili dynamic fails gracefully without login', async () => {
|
|
36
|
-
await expectGracefulAuthFailure(['bilibili', 'dynamic', '--limit', '3', '-f', 'json']
|
|
20
|
+
await expectGracefulAuthFailure(['bilibili', 'dynamic', '--limit', '3', '-f', 'json']);
|
|
37
21
|
}, 60_000);
|
|
38
22
|
|
|
39
23
|
it('bilibili favorite fails gracefully without login', async () => {
|
|
40
|
-
await expectGracefulAuthFailure(['bilibili', 'favorite', '--limit', '3', '-f', 'json']
|
|
24
|
+
await expectGracefulAuthFailure(['bilibili', 'favorite', '--limit', '3', '-f', 'json']);
|
|
41
25
|
}, 60_000);
|
|
42
26
|
|
|
43
27
|
it('bilibili history fails gracefully without login', async () => {
|
|
44
|
-
await expectGracefulAuthFailure(['bilibili', 'history', '--limit', '3', '-f', 'json']
|
|
28
|
+
await expectGracefulAuthFailure(['bilibili', 'history', '--limit', '3', '-f', 'json']);
|
|
45
29
|
}, 60_000);
|
|
46
30
|
|
|
47
31
|
it('bilibili following fails gracefully without login', async () => {
|
|
48
|
-
await expectGracefulAuthFailure(['bilibili', 'following', '--limit', '3', '-f', 'json']
|
|
32
|
+
await expectGracefulAuthFailure(['bilibili', 'following', '--limit', '3', '-f', 'json']);
|
|
49
33
|
}, 60_000);
|
|
50
34
|
|
|
51
35
|
// ── twitter (requires login) ──
|
|
52
36
|
it('twitter bookmarks fails gracefully without login', async () => {
|
|
53
|
-
await expectGracefulAuthFailure(['twitter', 'bookmarks', '--limit', '3', '-f', 'json']
|
|
37
|
+
await expectGracefulAuthFailure(['twitter', 'bookmarks', '--limit', '3', '-f', 'json']);
|
|
54
38
|
}, 60_000);
|
|
55
39
|
|
|
56
40
|
it('twitter timeline fails gracefully without login', async () => {
|
|
57
|
-
await expectGracefulAuthFailure(['twitter', 'timeline', '--limit', '3', '-f', 'json']
|
|
41
|
+
await expectGracefulAuthFailure(['twitter', 'timeline', '--limit', '3', '-f', 'json']);
|
|
58
42
|
}, 60_000);
|
|
59
43
|
|
|
60
44
|
it('twitter notifications fails gracefully without login', async () => {
|
|
61
|
-
await expectGracefulAuthFailure(['twitter', 'notifications', '--limit', '3', '-f', 'json']
|
|
45
|
+
await expectGracefulAuthFailure(['twitter', 'notifications', '--limit', '3', '-f', 'json']);
|
|
62
46
|
}, 60_000);
|
|
63
47
|
|
|
64
48
|
// ── v2ex (requires login) ──
|
|
65
49
|
it('v2ex me fails gracefully without login', async () => {
|
|
66
|
-
await expectGracefulAuthFailure(['v2ex', 'me', '-f', 'json']
|
|
50
|
+
await expectGracefulAuthFailure(['v2ex', 'me', '-f', 'json']);
|
|
67
51
|
}, 60_000);
|
|
68
52
|
|
|
69
53
|
it('v2ex notifications fails gracefully without login', async () => {
|
|
70
|
-
await expectGracefulAuthFailure(['v2ex', 'notifications', '--limit', '3', '-f', 'json']
|
|
54
|
+
await expectGracefulAuthFailure(['v2ex', 'notifications', '--limit', '3', '-f', 'json']);
|
|
71
55
|
}, 60_000);
|
|
72
56
|
|
|
73
57
|
// ── xueqiu (requires login) ──
|
|
74
58
|
it('xueqiu feed fails gracefully without login', async () => {
|
|
75
|
-
await expectGracefulAuthFailure(['xueqiu', 'feed', '--limit', '3', '-f', 'json']
|
|
59
|
+
await expectGracefulAuthFailure(['xueqiu', 'feed', '--limit', '3', '-f', 'json']);
|
|
76
60
|
}, 60_000);
|
|
77
61
|
|
|
78
62
|
it('xueqiu watchlist fails gracefully without login', async () => {
|
|
79
|
-
await expectGracefulAuthFailure(['xueqiu', 'watchlist', '-f', 'json']
|
|
63
|
+
await expectGracefulAuthFailure(['xueqiu', 'watchlist', '-f', 'json']);
|
|
64
|
+
}, 60_000);
|
|
65
|
+
|
|
66
|
+
it('xueqiu comments fails gracefully without login', async () => {
|
|
67
|
+
await expectGracefulAuthFailure(['xueqiu', 'comments', 'SH600519', '--limit', '3', '-f', 'json'], 'xueqiu comments');
|
|
80
68
|
}, 60_000);
|
|
81
69
|
|
|
82
70
|
// ── linux-do (requires login — all endpoints need authentication) ──
|
|
83
71
|
it('linux-do feed fails gracefully without login', async () => {
|
|
84
|
-
await expectGracefulAuthFailure(['linux-do', 'feed', '--limit', '3', '-f', 'json']
|
|
72
|
+
await expectGracefulAuthFailure(['linux-do', 'feed', '--limit', '3', '-f', 'json']);
|
|
85
73
|
}, 60_000);
|
|
86
74
|
|
|
87
75
|
it('linux-do categories fails gracefully without login', async () => {
|
|
88
|
-
await expectGracefulAuthFailure(['linux-do', 'categories', '--limit', '3', '-f', 'json']
|
|
76
|
+
await expectGracefulAuthFailure(['linux-do', 'categories', '--limit', '3', '-f', 'json']);
|
|
89
77
|
}, 60_000);
|
|
90
78
|
|
|
91
79
|
it('linux-do tags fails gracefully without login', async () => {
|
|
92
|
-
await expectGracefulAuthFailure(['linux-do', 'tags', '--limit', '3', '-f', 'json']
|
|
80
|
+
await expectGracefulAuthFailure(['linux-do', 'tags', '--limit', '3', '-f', 'json']);
|
|
93
81
|
}, 60_000);
|
|
94
82
|
|
|
95
83
|
it('linux-do topic fails gracefully without login', async () => {
|
|
96
|
-
await expectGracefulAuthFailure(['linux-do', 'topic', '1', '-f', 'json']
|
|
84
|
+
await expectGracefulAuthFailure(['linux-do', 'topic', '1', '-f', 'json']);
|
|
97
85
|
}, 60_000);
|
|
98
86
|
|
|
99
87
|
it('linux-do search fails gracefully without login', async () => {
|
|
100
|
-
await expectGracefulAuthFailure(['linux-do', 'search', 'test', '--limit', '3', '-f', 'json']
|
|
88
|
+
await expectGracefulAuthFailure(['linux-do', 'search', 'test', '--limit', '3', '-f', 'json']);
|
|
101
89
|
}, 60_000);
|
|
102
90
|
|
|
103
91
|
it('linux-do user-topics fails gracefully without login', async () => {
|
|
104
|
-
await expectGracefulAuthFailure(['linux-do', 'user-topics', 'test', '--limit', '3', '-f', 'json']
|
|
92
|
+
await expectGracefulAuthFailure(['linux-do', 'user-topics', 'test', '--limit', '3', '-f', 'json']);
|
|
105
93
|
}, 60_000);
|
|
106
94
|
|
|
107
95
|
it('linux-do user-posts fails gracefully without login', async () => {
|
|
108
|
-
await expectGracefulAuthFailure(['linux-do', 'user-posts', 'test', '--limit', '3', '-f', 'json']
|
|
96
|
+
await expectGracefulAuthFailure(['linux-do', 'user-posts', 'test', '--limit', '3', '-f', 'json']);
|
|
109
97
|
}, 60_000);
|
|
110
98
|
|
|
111
99
|
// ── xiaohongshu (requires login) ──
|
|
112
100
|
it('xiaohongshu feed fails gracefully without login', async () => {
|
|
113
|
-
await expectGracefulAuthFailure(['xiaohongshu', 'feed', '--limit', '3', '-f', 'json']
|
|
101
|
+
await expectGracefulAuthFailure(['xiaohongshu', 'feed', '--limit', '3', '-f', 'json']);
|
|
114
102
|
}, 60_000);
|
|
115
103
|
|
|
116
104
|
it('xiaohongshu notifications fails gracefully without login', async () => {
|
|
117
|
-
await expectGracefulAuthFailure(['xiaohongshu', 'notifications', '--limit', '3', '-f', 'json']
|
|
105
|
+
await expectGracefulAuthFailure(['xiaohongshu', 'notifications', '--limit', '3', '-f', 'json']);
|
|
118
106
|
}, 60_000);
|
|
119
107
|
|
|
120
108
|
// ── pixiv (requires login) ──
|
|
121
109
|
it('pixiv ranking fails gracefully without login', async () => {
|
|
122
|
-
await expectGracefulAuthFailure(['pixiv', 'ranking', '--limit', '3', '-f', 'json']
|
|
110
|
+
await expectGracefulAuthFailure(['pixiv', 'ranking', '--limit', '3', '-f', 'json']);
|
|
123
111
|
}, 60_000);
|
|
124
112
|
|
|
125
113
|
it('pixiv search fails gracefully without login', async () => {
|
|
126
|
-
await expectGracefulAuthFailure(['pixiv', 'search', '初音ミク', '--limit', '3', '-f', 'json']
|
|
114
|
+
await expectGracefulAuthFailure(['pixiv', 'search', '初音ミク', '--limit', '3', '-f', 'json']);
|
|
127
115
|
}, 60_000);
|
|
128
116
|
|
|
129
117
|
it('pixiv user fails gracefully without login', async () => {
|
|
130
|
-
await expectGracefulAuthFailure(['pixiv', 'user', '11', '-f', 'json']
|
|
118
|
+
await expectGracefulAuthFailure(['pixiv', 'user', '11', '-f', 'json']);
|
|
131
119
|
}, 60_000);
|
|
132
120
|
|
|
133
121
|
it('pixiv illusts fails gracefully without login', async () => {
|
|
134
|
-
await expectGracefulAuthFailure(['pixiv', 'illusts', '11', '--limit', '3', '-f', 'json']
|
|
122
|
+
await expectGracefulAuthFailure(['pixiv', 'illusts', '11', '--limit', '3', '-f', 'json']);
|
|
135
123
|
}, 60_000);
|
|
136
124
|
|
|
137
125
|
it('pixiv detail fails gracefully without login', async () => {
|
|
138
|
-
await expectGracefulAuthFailure(['pixiv', 'detail', '123456', '-f', 'json']
|
|
126
|
+
await expectGracefulAuthFailure(['pixiv', 'detail', '123456', '-f', 'json']);
|
|
139
127
|
}, 60_000);
|
|
140
128
|
|
|
141
129
|
it('pixiv download fails gracefully without login', async () => {
|
|
142
|
-
await expectGracefulAuthFailure(['pixiv', 'download', '123456', '--output', '/tmp/pixiv-e2e-test', '-f', 'json']
|
|
130
|
+
await expectGracefulAuthFailure(['pixiv', 'download', '123456', '--output', '/tmp/pixiv-e2e-test', '-f', 'json']);
|
|
143
131
|
}, 60_000);
|
|
144
132
|
|
|
145
133
|
// ── yollomi (requires login session) ──
|
|
146
134
|
it('yollomi generate fails gracefully without login', async () => {
|
|
147
|
-
await expectGracefulAuthFailure(['yollomi', 'generate', 'a cute cat', '--no-download', '-f', 'json']
|
|
135
|
+
await expectGracefulAuthFailure(['yollomi', 'generate', 'a cute cat', '--no-download', '-f', 'json']);
|
|
148
136
|
}, 60_000);
|
|
149
137
|
|
|
150
138
|
it('yollomi video fails gracefully without login', async () => {
|
|
151
|
-
await expectGracefulAuthFailure(['yollomi', 'video', 'a sunset over the ocean', '--no-download', '-f', 'json']
|
|
139
|
+
await expectGracefulAuthFailure(['yollomi', 'video', 'a sunset over the ocean', '--no-download', '-f', 'json']);
|
|
152
140
|
}, 60_000);
|
|
153
141
|
});
|
|
@@ -126,9 +126,13 @@ describe('browser extended public-data commands E2E', () => {
|
|
|
126
126
|
}, 60_000);
|
|
127
127
|
|
|
128
128
|
// ── ctrip ──
|
|
129
|
-
it('ctrip search returns
|
|
130
|
-
const data = await tryBrowserCommand(['ctrip', 'search', '-f', 'json']);
|
|
129
|
+
it('ctrip search returns destination suggestions', async () => {
|
|
130
|
+
const data = await tryBrowserCommand(['ctrip', 'search', '苏州', '--limit', '3', '-f', 'json']);
|
|
131
131
|
expectDataOrSkip(data, 'ctrip search');
|
|
132
|
+
if (data) {
|
|
133
|
+
expect(data[0]).toHaveProperty('name');
|
|
134
|
+
expect(data[0]).toHaveProperty('type');
|
|
135
|
+
}
|
|
132
136
|
}, 60_000);
|
|
133
137
|
|
|
134
138
|
// ── coupang ──
|
|
@@ -38,6 +38,201 @@ function isBrowserBridgeUnavailable(result: CliResult): boolean {
|
|
|
38
38
|
return /Browser Bridge.*not connected|Extension.*not connected/i.test(text);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
function isBaiduChallengeText(text: string): boolean {
|
|
42
|
+
return /百度安全验证|安全验证|请完成验证|captcha/i.test(text);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isBaiduChallenge(result: CliResult): boolean {
|
|
46
|
+
const text = `${result.stderr}\n${result.stdout}`;
|
|
47
|
+
return isBaiduChallengeText(text);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isTransientBrowserDetach(result: CliResult): boolean {
|
|
51
|
+
const text = `${result.stderr}\n${result.stdout}`;
|
|
52
|
+
return /Detached while handling command|No tab with id|Debugger is not attached to the tab/i.test(text);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function runCliWithTransientRetry(args: string[], timeout: number): Promise<CliResult> {
|
|
56
|
+
let result = await runCli(args, { timeout });
|
|
57
|
+
if (result.code !== 0 && isTransientBrowserDetach(result)) {
|
|
58
|
+
result = await runCli(args, { timeout });
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function runJsonCliOrThrow(args: string[], label: string, timeout: number, opts: { retryTransient?: boolean } = {}): Promise<any[] | null> {
|
|
64
|
+
const result = opts.retryTransient
|
|
65
|
+
? await runCliWithTransientRetry(args, timeout)
|
|
66
|
+
: await runCli(args, { timeout });
|
|
67
|
+
if (result.code !== 0) {
|
|
68
|
+
if (isBrowserBridgeUnavailable(result)) {
|
|
69
|
+
console.warn(`${label}: skipped — Browser Bridge extension is unavailable in this environment`);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
if (isBaiduChallenge(result)) {
|
|
73
|
+
console.warn(`${label}: skipped — Baidu challenge page detected`);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
throw new Error(`${label} failed:\n${result.stderr || result.stdout}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const data = parseJsonOutput(result.stdout);
|
|
80
|
+
if (!Array.isArray(data)) {
|
|
81
|
+
throw new Error(`${label} returned non-array JSON:\n${result.stdout.slice(0, 500)}`);
|
|
82
|
+
}
|
|
83
|
+
return data;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function normalizeTiebaTitle(value: string): string {
|
|
87
|
+
return value.replace(/\s+/g, ' ').trim();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function hasTiebaMainPost(data: any[] | null): boolean {
|
|
91
|
+
return Array.isArray(data) && data.some((item: any) => Number(item.floor) === 1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function expectNonEmptyDataOrSkipEnv(data: any[] | null, label: string): data is any[] {
|
|
95
|
+
if (data === null) {
|
|
96
|
+
console.warn(`${label}: skipped — environment is unavailable for browser assertions`);
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
expect(data.length).toBeGreaterThanOrEqual(1);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function countTiebaReplies(data: any[] | null): number {
|
|
104
|
+
if (!Array.isArray(data)) return 0;
|
|
105
|
+
return data.filter((item: any) => Number(item.floor) > 1).length;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function maxTiebaFloor(data: any[] | null): number {
|
|
109
|
+
if (!Array.isArray(data) || !data.length) return 0;
|
|
110
|
+
return Math.max(...data.map((item: any) => Number(item.floor) || 0));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getTiebaReplyFloors(data: any[] | null): number[] {
|
|
114
|
+
if (!Array.isArray(data)) return [];
|
|
115
|
+
return data
|
|
116
|
+
.map((item: any) => Number(item.floor) || 0)
|
|
117
|
+
.filter((floor) => floor > 1);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function countTiebaReplyFloorOverlap(left: any[] | null, right: any[] | null): number {
|
|
121
|
+
const rightFloors = new Set(getTiebaReplyFloors(right));
|
|
122
|
+
return getTiebaReplyFloors(left).filter((floor) => rightFloors.has(floor)).length;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function pickTiebaReadCandidates(
|
|
126
|
+
posts: any[] | null,
|
|
127
|
+
minReplies: number,
|
|
128
|
+
): Array<{ threadId: string; title: string; replies: number }> {
|
|
129
|
+
if (!Array.isArray(posts) || !posts.length) return [];
|
|
130
|
+
|
|
131
|
+
return [...posts]
|
|
132
|
+
.filter((item: any) => item?.id)
|
|
133
|
+
.map((item: any) => ({
|
|
134
|
+
threadId: String(item.id || '').trim(),
|
|
135
|
+
title: normalizeTiebaTitle(String(item.title || '')),
|
|
136
|
+
replies: Number(item.replies) || 0,
|
|
137
|
+
}))
|
|
138
|
+
.filter((item) => item.threadId && item.title && item.replies >= minReplies)
|
|
139
|
+
.sort((left, right) => right.replies - left.replies);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Pick a live thread that actually exposes enough visible replies for the read assertions.
|
|
144
|
+
*/
|
|
145
|
+
async function getTiebaReadCandidateOrSkip(
|
|
146
|
+
label: string,
|
|
147
|
+
options: { minRepliesOnPage1?: number; requirePage2?: boolean } = {},
|
|
148
|
+
): Promise<{ threadId: string; title: string; replies: number } | null> {
|
|
149
|
+
const minRepliesOnPage1 = Math.max(1, Number(options.minRepliesOnPage1 || 1));
|
|
150
|
+
const requirePage2 = options.requirePage2 === true;
|
|
151
|
+
const posts = await runJsonCliOrThrow(['tieba', 'posts', '李毅', '--limit', '10', '-f', 'json'], `${label} setup`, 90_000, {
|
|
152
|
+
retryTransient: true,
|
|
153
|
+
});
|
|
154
|
+
if (posts === null) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
if (!Array.isArray(posts) || !posts.length) {
|
|
158
|
+
console.warn(`${label}: skipped — could not resolve Tieba posts for setup`);
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const minReplies = requirePage2 ? Math.max(minRepliesOnPage1, 50) : minRepliesOnPage1;
|
|
163
|
+
const candidates = pickTiebaReadCandidates(posts, minReplies).slice(0, 5);
|
|
164
|
+
if (!candidates.length) {
|
|
165
|
+
console.warn(`${label}: skipped — could not find a Tieba thread with enough replies from posts metadata`);
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
for (const candidate of candidates) {
|
|
170
|
+
const page1Preview = await runJsonCliOrThrow(
|
|
171
|
+
['tieba', 'read', candidate.threadId, '--page', '1', '--limit', String(Math.max(minRepliesOnPage1, 2)), '-f', 'json'],
|
|
172
|
+
`${label} preview page 1`,
|
|
173
|
+
90_000,
|
|
174
|
+
{ retryTransient: true },
|
|
175
|
+
);
|
|
176
|
+
if (page1Preview === null) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
if (!hasTiebaMainPost(page1Preview) || countTiebaReplies(page1Preview) < minRepliesOnPage1) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (requirePage2) {
|
|
184
|
+
const page2Preview = await runJsonCliOrThrow(
|
|
185
|
+
['tieba', 'read', candidate.threadId, '--page', '2', '--limit', '1', '-f', 'json'],
|
|
186
|
+
`${label} preview page 2`,
|
|
187
|
+
90_000,
|
|
188
|
+
{ retryTransient: true },
|
|
189
|
+
);
|
|
190
|
+
if (page2Preview === null) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
if (hasTiebaMainPost(page2Preview) || countTiebaReplies(page2Preview) < 1) {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return candidate;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
console.warn(`${label}: skipped — could not find a Tieba thread with enough visible replies`);
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
describe('tieba e2e helper guards', () => {
|
|
206
|
+
it('does not treat generic empty-result errors as a Baidu challenge', () => {
|
|
207
|
+
expect(isBaiduChallengeText('tieba posts returned no data\n→ The page structure may have changed — this adapter may be outdated.')).toBe(false);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('still recognizes actual Baidu challenge text', () => {
|
|
211
|
+
expect(isBaiduChallengeText('百度安全验证,请完成验证后继续')).toBe(true);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('counts partial overlap between read pages', () => {
|
|
215
|
+
expect(countTiebaReplyFloorOverlap(
|
|
216
|
+
[{ floor: 1 }, { floor: 23 }, { floor: 27 }, { floor: 28 }, { floor: 29 }, { floor: 30 }],
|
|
217
|
+
[{ floor: 27 }, { floor: 28 }, { floor: 31 }],
|
|
218
|
+
)).toBe(2);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('picks read fixtures from posts metadata in descending reply order', () => {
|
|
222
|
+
expect(pickTiebaReadCandidates([
|
|
223
|
+
{ id: '1', title: '普通帖', replies: 2 },
|
|
224
|
+
{ id: '2', title: '大帖', replies: 120 },
|
|
225
|
+
{ id: '', title: '无效帖', replies: 999 },
|
|
226
|
+
], 50)).toEqual([{
|
|
227
|
+
threadId: '2',
|
|
228
|
+
title: '大帖',
|
|
229
|
+
replies: 120,
|
|
230
|
+
}]);
|
|
231
|
+
|
|
232
|
+
expect(pickTiebaReadCandidates([{ id: '1', title: '普通帖', replies: 2 }], 50)).toEqual([]);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
41
236
|
async function expectImdbDataOrChallengeSkip(args: string[], label: string): Promise<any[] | null> {
|
|
42
237
|
const result = await runCli(args, { timeout: 60_000 });
|
|
43
238
|
if (result.code !== 0) {
|
|
@@ -103,6 +298,99 @@ describe('browser public-data commands E2E', () => {
|
|
|
103
298
|
expectDataOrSkip(data, 'v2ex daily');
|
|
104
299
|
}, 60_000);
|
|
105
300
|
|
|
301
|
+
// ── tieba ──
|
|
302
|
+
it('tieba hot returns trending topics', async () => {
|
|
303
|
+
const data = await runJsonCliOrThrow(['tieba', 'hot', '--limit', '5', '-f', 'json'], 'tieba hot', 60_000, { retryTransient: true });
|
|
304
|
+
if (expectNonEmptyDataOrSkipEnv(data, 'tieba hot')) {
|
|
305
|
+
expect(data[0]).toHaveProperty('title');
|
|
306
|
+
expect(data[0]).toHaveProperty('discussions');
|
|
307
|
+
}
|
|
308
|
+
}, 60_000);
|
|
309
|
+
|
|
310
|
+
it('tieba posts returns forum threads', async () => {
|
|
311
|
+
const data = await runJsonCliOrThrow(['tieba', 'posts', '李毅', '--limit', '20', '-f', 'json'], 'tieba posts', 90_000, { retryTransient: true });
|
|
312
|
+
if (expectNonEmptyDataOrSkipEnv(data, 'tieba posts')) {
|
|
313
|
+
expect(data[0]).toHaveProperty('title');
|
|
314
|
+
expect(String(data[0].id || '')).toMatch(/^\d+$/);
|
|
315
|
+
expect(String(data[0].url || '')).toContain('/p/');
|
|
316
|
+
expect(Number.isFinite(Number(data[0].replies))).toBe(true);
|
|
317
|
+
expect(data.length).toBeLessThanOrEqual(20);
|
|
318
|
+
}
|
|
319
|
+
}, 90_000);
|
|
320
|
+
|
|
321
|
+
it('tieba posts page 2 returns a different forum slice', async () => {
|
|
322
|
+
const data1 = await runJsonCliOrThrow(['tieba', 'posts', '李毅', '--page', '1', '--limit', '5', '-f', 'json'], 'tieba posts page 1', 60_000, { retryTransient: true });
|
|
323
|
+
const data2 = await runJsonCliOrThrow(['tieba', 'posts', '李毅', '--page', '2', '--limit', '5', '-f', 'json'], 'tieba posts page 2', 60_000, { retryTransient: true });
|
|
324
|
+
if (expectNonEmptyDataOrSkipEnv(data1, 'tieba posts page 1') && expectNonEmptyDataOrSkipEnv(data2, 'tieba posts page 2')) {
|
|
325
|
+
const ids1 = data1.map((item: any) => String(item.id || '')).filter(Boolean);
|
|
326
|
+
const ids2 = data2.map((item: any) => String(item.id || '')).filter(Boolean);
|
|
327
|
+
const newIds = ids2.filter((id) => !ids1.includes(id));
|
|
328
|
+
expect(newIds.length).toBeGreaterThan(0);
|
|
329
|
+
}
|
|
330
|
+
}, 90_000);
|
|
331
|
+
|
|
332
|
+
it('tieba search returns results', async () => {
|
|
333
|
+
const data = await runJsonCliOrThrow(['tieba', 'search', '编程', '--limit', '20', '-f', 'json'], 'tieba search', 90_000, { retryTransient: true });
|
|
334
|
+
if (expectNonEmptyDataOrSkipEnv(data, 'tieba search')) {
|
|
335
|
+
expect(data[0]).toHaveProperty('title');
|
|
336
|
+
expect(String(data[0].id || '')).toMatch(/^\d+$/);
|
|
337
|
+
expect(String(data[0].url || '')).toContain('/p/');
|
|
338
|
+
expect(data.length).toBeLessThanOrEqual(20);
|
|
339
|
+
}
|
|
340
|
+
}, 90_000);
|
|
341
|
+
|
|
342
|
+
it('tieba search rejects unsupported pages above 1', async () => {
|
|
343
|
+
const result = await runCli(['tieba', 'search', '编程', '--page', '2', '--limit', '3', '-f', 'json'], {
|
|
344
|
+
timeout: 60_000,
|
|
345
|
+
});
|
|
346
|
+
expect(result.code).toBe(2);
|
|
347
|
+
expect(`${result.stderr}\n${result.stdout}`).toContain('Argument "page" must be one of: 1');
|
|
348
|
+
}, 60_000);
|
|
349
|
+
|
|
350
|
+
it('tieba read returns thread content', async () => {
|
|
351
|
+
const fixture = await getTiebaReadCandidateOrSkip('tieba read');
|
|
352
|
+
if (!fixture) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const data = await runJsonCliOrThrow(['tieba', 'read', fixture.threadId, '--limit', '5', '-f', 'json'], 'tieba read', 90_000, { retryTransient: true });
|
|
356
|
+
if (expectNonEmptyDataOrSkipEnv(data, 'tieba read')) {
|
|
357
|
+
expect(data[0]).toHaveProperty('floor');
|
|
358
|
+
expect(data[0]).toHaveProperty('content');
|
|
359
|
+
expect(data.some((item: any) => Number(item.floor) === 1)).toBe(true);
|
|
360
|
+
expect(normalizeTiebaTitle(String(data[0].content || ''))).toContain(fixture.title);
|
|
361
|
+
}
|
|
362
|
+
}, 90_000);
|
|
363
|
+
|
|
364
|
+
it('tieba read page 2 omits the main post', async () => {
|
|
365
|
+
const fixture = await getTiebaReadCandidateOrSkip('tieba read page', { requirePage2: true });
|
|
366
|
+
if (!fixture) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const data1 = await runJsonCliOrThrow(['tieba', 'read', fixture.threadId, '--page', '1', '--limit', '5', '-f', 'json'], 'tieba read page 1', 90_000, { retryTransient: true });
|
|
370
|
+
const data2 = await runJsonCliOrThrow(['tieba', 'read', fixture.threadId, '--page', '2', '--limit', '5', '-f', 'json'], 'tieba read page 2', 90_000, { retryTransient: true });
|
|
371
|
+
if (expectNonEmptyDataOrSkipEnv(data1, 'tieba read page 1') && expectNonEmptyDataOrSkipEnv(data2, 'tieba read page 2')) {
|
|
372
|
+
const overlap = countTiebaReplyFloorOverlap(data1, data2);
|
|
373
|
+
expect(normalizeTiebaTitle(String(data1[0].content || ''))).toContain(fixture.title);
|
|
374
|
+
expect(hasTiebaMainPost(data1)).toBe(true);
|
|
375
|
+
expect(hasTiebaMainPost(data2)).toBe(false);
|
|
376
|
+
expect(overlap).toBe(0);
|
|
377
|
+
expect(maxTiebaFloor(data2)).toBeGreaterThan(maxTiebaFloor(data1));
|
|
378
|
+
}
|
|
379
|
+
}, 90_000);
|
|
380
|
+
|
|
381
|
+
it('tieba read limit counts replies instead of consuming the main post slot', async () => {
|
|
382
|
+
const fixture = await getTiebaReadCandidateOrSkip('tieba read limit semantics', { minRepliesOnPage1: 2 });
|
|
383
|
+
if (!fixture) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const data = await runJsonCliOrThrow(['tieba', 'read', fixture.threadId, '--page', '1', '--limit', '2', '-f', 'json'], 'tieba read limit semantics', 90_000, { retryTransient: true });
|
|
387
|
+
if (expectNonEmptyDataOrSkipEnv(data, 'tieba read limit semantics')) {
|
|
388
|
+
expect(normalizeTiebaTitle(String(data[0].content || ''))).toContain(fixture.title);
|
|
389
|
+
expect(hasTiebaMainPost(data)).toBe(true);
|
|
390
|
+
expect(countTiebaReplies(data)).toBe(2);
|
|
391
|
+
}
|
|
392
|
+
}, 90_000);
|
|
393
|
+
|
|
106
394
|
// ── imdb ──
|
|
107
395
|
it('imdb top returns chart data', async () => {
|
|
108
396
|
const data = await expectImdbDataOrChallengeSkip(['imdb', 'top', '--limit', '3', '-f', 'json'], 'imdb top');
|