@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
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
2
|
import { CliError } from '../../errors.js';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
const WEREAD_SHELF_URL = `https://${WEREAD_DOMAIN}/web/shelf`;
|
|
3
|
+
import { log } from '../../logger.js';
|
|
4
|
+
import { buildWebShelfEntries, fetchPrivateApi, loadWebShelfSnapshot, } from './utils.js';
|
|
6
5
|
function normalizeShelfLimit(limit) {
|
|
7
6
|
if (!Number.isFinite(limit))
|
|
8
7
|
return 0;
|
|
@@ -21,99 +20,15 @@ function normalizePrivateApiRows(data, limit) {
|
|
|
21
20
|
function normalizeWebShelfRows(snapshot, limit) {
|
|
22
21
|
if (limit <= 0)
|
|
23
22
|
return [];
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
const orderedBookIds = snapshot.shelfIndexes
|
|
32
|
-
.filter((entry) => String(entry?.role || 'book') === 'book')
|
|
33
|
-
.sort((left, right) => Number(left?.idx ?? Number.MAX_SAFE_INTEGER) - Number(right?.idx ?? Number.MAX_SAFE_INTEGER))
|
|
34
|
-
.map((entry) => String(entry?.bookId || '').trim())
|
|
35
|
-
.filter(Boolean);
|
|
36
|
-
const fallbackOrder = snapshot.rawBooks
|
|
37
|
-
.map((book) => String(book?.bookId || '').trim())
|
|
38
|
-
.filter(Boolean);
|
|
39
|
-
const orderedUniqueBookIds = Array.from(new Set([
|
|
40
|
-
...orderedBookIds,
|
|
41
|
-
...fallbackOrder,
|
|
42
|
-
]));
|
|
43
|
-
return orderedUniqueBookIds
|
|
44
|
-
.map((bookId) => {
|
|
45
|
-
const book = bookById.get(bookId);
|
|
46
|
-
if (!book)
|
|
47
|
-
return null;
|
|
48
|
-
return {
|
|
49
|
-
title: String(book.title || '').trim(),
|
|
50
|
-
author: String(book.author || '').trim(),
|
|
51
|
-
progress: '-',
|
|
52
|
-
bookId,
|
|
53
|
-
};
|
|
54
|
-
})
|
|
55
|
-
.filter((item) => Boolean(item && (item.title || item.bookId)))
|
|
56
|
-
.slice(0, limit);
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Read the structured shelf cache from the web shelf page.
|
|
60
|
-
* The page hydrates localStorage with raw book data plus shelf ordering.
|
|
61
|
-
*/
|
|
62
|
-
async function loadWebShelfSnapshot(page) {
|
|
63
|
-
await page.goto(WEREAD_SHELF_URL);
|
|
64
|
-
const cookies = await page.getCookies({ domain: WEREAD_DOMAIN });
|
|
65
|
-
const currentVid = String(cookies.find((cookie) => cookie.name === 'wr_vid')?.value || '').trim();
|
|
66
|
-
if (!currentVid) {
|
|
67
|
-
return { cacheFound: false, rawBooks: [], shelfIndexes: [] };
|
|
68
|
-
}
|
|
69
|
-
const rawBooksKey = `shelf:rawBooks:${currentVid}`;
|
|
70
|
-
const shelfIndexesKey = `shelf:shelfIndexes:${currentVid}`;
|
|
71
|
-
const result = await page.evaluate(`
|
|
72
|
-
(() => new Promise((resolve) => {
|
|
73
|
-
const deadline = Date.now() + 5000;
|
|
74
|
-
const rawBooksKey = ${JSON.stringify(rawBooksKey)};
|
|
75
|
-
const shelfIndexesKey = ${JSON.stringify(shelfIndexesKey)};
|
|
76
|
-
|
|
77
|
-
const readJson = (raw) => {
|
|
78
|
-
if (typeof raw !== 'string') return null;
|
|
79
|
-
try {
|
|
80
|
-
return JSON.parse(raw);
|
|
81
|
-
} catch {
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const poll = () => {
|
|
87
|
-
const rawBooksRaw = localStorage.getItem(rawBooksKey);
|
|
88
|
-
const shelfIndexesRaw = localStorage.getItem(shelfIndexesKey);
|
|
89
|
-
const rawBooks = readJson(rawBooksRaw);
|
|
90
|
-
const shelfIndexes = readJson(shelfIndexesRaw);
|
|
91
|
-
const cacheFound = Array.isArray(rawBooks);
|
|
92
|
-
|
|
93
|
-
if (cacheFound || Date.now() >= deadline) {
|
|
94
|
-
resolve({
|
|
95
|
-
cacheFound,
|
|
96
|
-
rawBooks: Array.isArray(rawBooks) ? rawBooks : [],
|
|
97
|
-
shelfIndexes: Array.isArray(shelfIndexes) ? shelfIndexes : [],
|
|
98
|
-
});
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
setTimeout(poll, 100);
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
poll();
|
|
23
|
+
return buildWebShelfEntries(snapshot)
|
|
24
|
+
.map((entry) => ({
|
|
25
|
+
title: entry.title,
|
|
26
|
+
author: entry.author,
|
|
27
|
+
progress: '-',
|
|
28
|
+
bookId: entry.bookId,
|
|
106
29
|
}))
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return { cacheFound: false, rawBooks: [], shelfIndexes: [] };
|
|
110
|
-
}
|
|
111
|
-
const snapshot = result;
|
|
112
|
-
return {
|
|
113
|
-
cacheFound: snapshot.cacheFound === true,
|
|
114
|
-
rawBooks: Array.isArray(snapshot.rawBooks) ? snapshot.rawBooks : [],
|
|
115
|
-
shelfIndexes: Array.isArray(snapshot.shelfIndexes) ? snapshot.shelfIndexes : [],
|
|
116
|
-
};
|
|
30
|
+
.filter((item) => Boolean(item.title || item.bookId))
|
|
31
|
+
.slice(0, limit);
|
|
117
32
|
}
|
|
118
33
|
cli({
|
|
119
34
|
site: 'weread',
|
|
@@ -141,6 +56,9 @@ cli({
|
|
|
141
56
|
if (!snapshot.cacheFound) {
|
|
142
57
|
throw error;
|
|
143
58
|
}
|
|
59
|
+
// Make the fallback explicit so users do not mistake cached shelf data
|
|
60
|
+
// for a valid private API session.
|
|
61
|
+
log.warn('WeRead private API auth expired; showing cached shelf data from localStorage. Results may be stale, and detail commands may still require re-login.');
|
|
144
62
|
return normalizeWebShelfRows(snapshot, limit);
|
|
145
63
|
}
|
|
146
64
|
},
|
|
@@ -6,6 +6,35 @@
|
|
|
6
6
|
* - API (i.weread.qq.com/*): private, Node.js fetch with cookies from browser
|
|
7
7
|
*/
|
|
8
8
|
import type { IPage } from '../../types.js';
|
|
9
|
+
export declare const WEREAD_DOMAIN = "weread.qq.com";
|
|
10
|
+
export declare const WEREAD_WEB_ORIGIN = "https://weread.qq.com";
|
|
11
|
+
export declare const WEREAD_SHELF_URL = "https://weread.qq.com/web/shelf";
|
|
12
|
+
export declare const WEREAD_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
|
13
|
+
export interface WebShelfRawBook {
|
|
14
|
+
bookId?: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
author?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface WebShelfIndexEntry {
|
|
19
|
+
bookId?: string;
|
|
20
|
+
idx?: number;
|
|
21
|
+
role?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface WebShelfSnapshot {
|
|
24
|
+
cacheFound: boolean;
|
|
25
|
+
rawBooks: WebShelfRawBook[];
|
|
26
|
+
shelfIndexes: WebShelfIndexEntry[];
|
|
27
|
+
}
|
|
28
|
+
export interface WebShelfEntry {
|
|
29
|
+
bookId: string;
|
|
30
|
+
title: string;
|
|
31
|
+
author: string;
|
|
32
|
+
readerUrl: string;
|
|
33
|
+
}
|
|
34
|
+
export interface WebShelfReaderResolution {
|
|
35
|
+
snapshot: WebShelfSnapshot;
|
|
36
|
+
readerUrl: string | null;
|
|
37
|
+
}
|
|
9
38
|
/**
|
|
10
39
|
* Fetch a public WeRead web endpoint (Node.js direct fetch).
|
|
11
40
|
* Used by search and ranking commands (browser: false).
|
|
@@ -14,7 +43,34 @@ export declare function fetchWebApi(path: string, params?: Record<string, string
|
|
|
14
43
|
/**
|
|
15
44
|
* Fetch a private WeRead API endpoint with cookies extracted from the browser.
|
|
16
45
|
* The HTTP request itself runs in Node.js to avoid page-context CORS failures.
|
|
46
|
+
*
|
|
47
|
+
* Cookies are collected from both the API subdomain (i.weread.qq.com) and the
|
|
48
|
+
* main domain (weread.qq.com). WeRead may set auth cookies as host-only on
|
|
49
|
+
* weread.qq.com, which won't match i.weread.qq.com in a URL-based lookup.
|
|
17
50
|
*/
|
|
18
51
|
export declare function fetchPrivateApi(page: IPage, path: string, params?: Record<string, string>): Promise<any>;
|
|
52
|
+
/**
|
|
53
|
+
* Build stable shelf records from the web cache plus optional rendered reader URLs.
|
|
54
|
+
* We only trust shelfIndexes when it fully covers the same bookId set as rawBooks;
|
|
55
|
+
* otherwise we keep rawBooks order to avoid partial hydration reordering entries.
|
|
56
|
+
*/
|
|
57
|
+
export declare function buildWebShelfEntries(snapshot: WebShelfSnapshot, readerUrls?: string[]): WebShelfEntry[];
|
|
58
|
+
/**
|
|
59
|
+
* Read the structured shelf cache from the WeRead shelf page.
|
|
60
|
+
* The page hydrates localStorage asynchronously, so we poll briefly before
|
|
61
|
+
* giving up and treating the cache as unavailable for the current session.
|
|
62
|
+
*/
|
|
63
|
+
export declare function loadWebShelfSnapshot(page: IPage): Promise<WebShelfSnapshot>;
|
|
64
|
+
/**
|
|
65
|
+
* Resolve a shelf bookId to the current web reader URL by pairing structured
|
|
66
|
+
* shelf cache order with the visible shelf links rendered on the page.
|
|
67
|
+
*/
|
|
68
|
+
export declare function resolveShelfReaderUrl(page: IPage, bookId: string): Promise<string | null>;
|
|
69
|
+
/**
|
|
70
|
+
* Resolve the current reader URL for a shelf entry and return the parsed shelf
|
|
71
|
+
* snapshot used during resolution, so callers can reuse cached title/author
|
|
72
|
+
* metadata without loading the shelf page twice.
|
|
73
|
+
*/
|
|
74
|
+
export declare function resolveShelfReader(page: IPage, bookId: string): Promise<WebShelfReaderResolution>;
|
|
19
75
|
/** Format a Unix timestamp (seconds) to YYYY-MM-DD in UTC+8. Returns '-' for invalid input. */
|
|
20
76
|
export declare function formatDate(ts: number | undefined | null): string;
|
|
@@ -6,9 +6,12 @@
|
|
|
6
6
|
* - API (i.weread.qq.com/*): private, Node.js fetch with cookies from browser
|
|
7
7
|
*/
|
|
8
8
|
import { CliError } from '../../errors.js';
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
9
|
+
export const WEREAD_DOMAIN = 'weread.qq.com';
|
|
10
|
+
export const WEREAD_WEB_ORIGIN = `https://${WEREAD_DOMAIN}`;
|
|
11
|
+
export const WEREAD_SHELF_URL = `${WEREAD_WEB_ORIGIN}/web/shelf`;
|
|
12
|
+
const WEB_API = `${WEREAD_WEB_ORIGIN}/web`;
|
|
13
|
+
const API = `https://i.${WEREAD_DOMAIN}`;
|
|
14
|
+
export const WEREAD_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
|
|
12
15
|
const WEREAD_AUTH_ERRCODES = new Set([-2010, -2012]);
|
|
13
16
|
function buildCookieHeader(cookies) {
|
|
14
17
|
return cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join('; ');
|
|
@@ -16,6 +19,84 @@ function buildCookieHeader(cookies) {
|
|
|
16
19
|
function isAuthErrorResponse(resp, data) {
|
|
17
20
|
return resp.status === 401 || WEREAD_AUTH_ERRCODES.has(Number(data?.errcode));
|
|
18
21
|
}
|
|
22
|
+
function getCurrentVid(cookies) {
|
|
23
|
+
return String(cookies.find((cookie) => cookie.name === 'wr_vid')?.value || '').trim();
|
|
24
|
+
}
|
|
25
|
+
function getWebShelfStorageKeys(currentVid) {
|
|
26
|
+
return {
|
|
27
|
+
rawBooksKey: `shelf:rawBooks:${currentVid}`,
|
|
28
|
+
shelfIndexesKey: `shelf:shelfIndexes:${currentVid}`,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function normalizeWebShelfSnapshot(value) {
|
|
32
|
+
return {
|
|
33
|
+
cacheFound: value?.cacheFound === true,
|
|
34
|
+
rawBooks: Array.isArray(value?.rawBooks) ? value.rawBooks : [],
|
|
35
|
+
shelfIndexes: Array.isArray(value?.shelfIndexes) ? value.shelfIndexes : [],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function buildShelfSnapshotPollScript(storageKeys, requireTrustedIndexes) {
|
|
39
|
+
return `
|
|
40
|
+
(() => new Promise((resolve) => {
|
|
41
|
+
const deadline = Date.now() + 5000;
|
|
42
|
+
const rawBooksKey = ${JSON.stringify(storageKeys.rawBooksKey)};
|
|
43
|
+
const shelfIndexesKey = ${JSON.stringify(storageKeys.shelfIndexesKey)};
|
|
44
|
+
const requireTrustedIndexes = ${JSON.stringify(requireTrustedIndexes)};
|
|
45
|
+
|
|
46
|
+
const readJson = (raw) => {
|
|
47
|
+
if (typeof raw !== 'string') return null;
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(raw);
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const collectBookIds = (items) => Array.isArray(items)
|
|
56
|
+
? Array.from(new Set(items.map((item) => String(item?.bookId || '').trim()).filter(Boolean)))
|
|
57
|
+
: [];
|
|
58
|
+
|
|
59
|
+
// Mirror of getTrustedIndexedBookIds in Node.js — keep in sync
|
|
60
|
+
const hasTrustedIndexes = (rawBooks, shelfIndexes) => {
|
|
61
|
+
const rawBookIds = collectBookIds(rawBooks);
|
|
62
|
+
if (rawBookIds.length === 0) return false;
|
|
63
|
+
|
|
64
|
+
const rawBookIdSet = new Set(rawBookIds);
|
|
65
|
+
const projectedIndexedBookIds = Array.isArray(shelfIndexes)
|
|
66
|
+
? Array.from(new Set(
|
|
67
|
+
shelfIndexes
|
|
68
|
+
.filter((entry) => Number.isFinite(entry?.idx))
|
|
69
|
+
.sort((left, right) => Number(left?.idx ?? Number.MAX_SAFE_INTEGER) - Number(right?.idx ?? Number.MAX_SAFE_INTEGER))
|
|
70
|
+
.map((entry) => String(entry?.bookId || '').trim())
|
|
71
|
+
.filter((bookId) => rawBookIdSet.has(bookId)),
|
|
72
|
+
))
|
|
73
|
+
: [];
|
|
74
|
+
|
|
75
|
+
return projectedIndexedBookIds.length === rawBookIds.length;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const poll = () => {
|
|
79
|
+
const rawBooks = readJson(localStorage.getItem(rawBooksKey));
|
|
80
|
+
const shelfIndexes = readJson(localStorage.getItem(shelfIndexesKey));
|
|
81
|
+
const cacheFound = Array.isArray(rawBooks);
|
|
82
|
+
const ready = cacheFound && (!requireTrustedIndexes || hasTrustedIndexes(rawBooks, shelfIndexes));
|
|
83
|
+
|
|
84
|
+
if (ready || Date.now() >= deadline) {
|
|
85
|
+
resolve({
|
|
86
|
+
cacheFound,
|
|
87
|
+
rawBooks: Array.isArray(rawBooks) ? rawBooks : [],
|
|
88
|
+
shelfIndexes: Array.isArray(shelfIndexes) ? shelfIndexes : [],
|
|
89
|
+
});
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
setTimeout(poll, 100);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
poll();
|
|
97
|
+
}))
|
|
98
|
+
`;
|
|
99
|
+
}
|
|
19
100
|
/**
|
|
20
101
|
* Fetch a public WeRead web endpoint (Node.js direct fetch).
|
|
21
102
|
* Used by search and ranking commands (browser: false).
|
|
@@ -27,7 +108,7 @@ export async function fetchWebApi(path, params) {
|
|
|
27
108
|
url.searchParams.set(k, v);
|
|
28
109
|
}
|
|
29
110
|
const resp = await fetch(url.toString(), {
|
|
30
|
-
headers: { 'User-Agent':
|
|
111
|
+
headers: { 'User-Agent': WEREAD_UA },
|
|
31
112
|
});
|
|
32
113
|
if (!resp.ok) {
|
|
33
114
|
throw new CliError('FETCH_ERROR', `HTTP ${resp.status} for ${path}`, 'WeRead API may be temporarily unavailable');
|
|
@@ -42,6 +123,10 @@ export async function fetchWebApi(path, params) {
|
|
|
42
123
|
/**
|
|
43
124
|
* Fetch a private WeRead API endpoint with cookies extracted from the browser.
|
|
44
125
|
* The HTTP request itself runs in Node.js to avoid page-context CORS failures.
|
|
126
|
+
*
|
|
127
|
+
* Cookies are collected from both the API subdomain (i.weread.qq.com) and the
|
|
128
|
+
* main domain (weread.qq.com). WeRead may set auth cookies as host-only on
|
|
129
|
+
* weread.qq.com, which won't match i.weread.qq.com in a URL-based lookup.
|
|
45
130
|
*/
|
|
46
131
|
export async function fetchPrivateApi(page, path, params) {
|
|
47
132
|
const url = new URL(`${API}${path}`);
|
|
@@ -50,13 +135,22 @@ export async function fetchPrivateApi(page, path, params) {
|
|
|
50
135
|
url.searchParams.set(k, v);
|
|
51
136
|
}
|
|
52
137
|
const urlStr = url.toString();
|
|
53
|
-
|
|
54
|
-
const
|
|
138
|
+
// Merge cookies from both domains; API-domain cookies take precedence on name collision
|
|
139
|
+
const [apiCookies, domainCookies] = await Promise.all([
|
|
140
|
+
page.getCookies({ url: urlStr }),
|
|
141
|
+
page.getCookies({ domain: WEREAD_DOMAIN }),
|
|
142
|
+
]);
|
|
143
|
+
const merged = new Map();
|
|
144
|
+
for (const c of domainCookies)
|
|
145
|
+
merged.set(c.name, c);
|
|
146
|
+
for (const c of apiCookies)
|
|
147
|
+
merged.set(c.name, c);
|
|
148
|
+
const cookieHeader = buildCookieHeader(Array.from(merged.values()));
|
|
55
149
|
let resp;
|
|
56
150
|
try {
|
|
57
151
|
resp = await fetch(urlStr, {
|
|
58
152
|
headers: {
|
|
59
|
-
'User-Agent':
|
|
153
|
+
'User-Agent': WEREAD_UA,
|
|
60
154
|
'Origin': 'https://weread.qq.com',
|
|
61
155
|
'Referer': 'https://weread.qq.com/',
|
|
62
156
|
...(cookieHeader ? { 'Cookie': cookieHeader } : {}),
|
|
@@ -84,6 +178,139 @@ export async function fetchPrivateApi(page, path, params) {
|
|
|
84
178
|
}
|
|
85
179
|
return data;
|
|
86
180
|
}
|
|
181
|
+
function getUniqueRawBookIds(snapshot) {
|
|
182
|
+
return Array.from(new Set(snapshot.rawBooks
|
|
183
|
+
.map((book) => String(book?.bookId || '').trim())
|
|
184
|
+
.filter(Boolean)));
|
|
185
|
+
}
|
|
186
|
+
/** Mirror of hasTrustedIndexes in buildShelfSnapshotPollScript — keep in sync */
|
|
187
|
+
function getTrustedIndexedBookIds(snapshot) {
|
|
188
|
+
const rawBookIds = getUniqueRawBookIds(snapshot);
|
|
189
|
+
if (rawBookIds.length === 0)
|
|
190
|
+
return [];
|
|
191
|
+
const rawBookIdSet = new Set(rawBookIds);
|
|
192
|
+
const projectedIndexedBookIds = Array.from(new Set(snapshot.shelfIndexes
|
|
193
|
+
.filter((entry) => Number.isFinite(entry?.idx))
|
|
194
|
+
.sort((left, right) => Number(left?.idx ?? Number.MAX_SAFE_INTEGER) - Number(right?.idx ?? Number.MAX_SAFE_INTEGER))
|
|
195
|
+
.map((entry) => String(entry?.bookId || '').trim())
|
|
196
|
+
.filter((bookId) => rawBookIdSet.has(bookId))));
|
|
197
|
+
return projectedIndexedBookIds.length === rawBookIds.length ? projectedIndexedBookIds : [];
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Build stable shelf records from the web cache plus optional rendered reader URLs.
|
|
201
|
+
* We only trust shelfIndexes when it fully covers the same bookId set as rawBooks;
|
|
202
|
+
* otherwise we keep rawBooks order to avoid partial hydration reordering entries.
|
|
203
|
+
*/
|
|
204
|
+
export function buildWebShelfEntries(snapshot, readerUrls = []) {
|
|
205
|
+
const rawBookIds = getUniqueRawBookIds(snapshot);
|
|
206
|
+
const trustedIndexedBookIds = getTrustedIndexedBookIds(snapshot);
|
|
207
|
+
const orderedBookIds = trustedIndexedBookIds.length > 0 ? trustedIndexedBookIds : rawBookIds;
|
|
208
|
+
const rawBookById = new Map();
|
|
209
|
+
for (const book of snapshot.rawBooks) {
|
|
210
|
+
const bookId = String(book?.bookId || '').trim();
|
|
211
|
+
if (!bookId || rawBookById.has(bookId))
|
|
212
|
+
continue;
|
|
213
|
+
rawBookById.set(bookId, book);
|
|
214
|
+
}
|
|
215
|
+
return orderedBookIds.map((bookId, index) => {
|
|
216
|
+
const book = rawBookById.get(bookId);
|
|
217
|
+
return {
|
|
218
|
+
bookId,
|
|
219
|
+
title: String(book?.title || '').trim(),
|
|
220
|
+
author: String(book?.author || '').trim(),
|
|
221
|
+
readerUrl: String(readerUrls[index] || '').trim(),
|
|
222
|
+
};
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Internal: load shelf snapshot and return the currentVid alongside it,
|
|
227
|
+
* so callers like resolveShelfReaderUrl can reuse it without a second getCookies.
|
|
228
|
+
*/
|
|
229
|
+
async function loadWebShelfSnapshotWithVid(page) {
|
|
230
|
+
await page.goto(WEREAD_SHELF_URL);
|
|
231
|
+
const cookies = await page.getCookies({ domain: WEREAD_DOMAIN });
|
|
232
|
+
const currentVid = getCurrentVid(cookies);
|
|
233
|
+
if (!currentVid) {
|
|
234
|
+
return { snapshot: { cacheFound: false, rawBooks: [], shelfIndexes: [] }, currentVid: '' };
|
|
235
|
+
}
|
|
236
|
+
const result = await page.evaluate(buildShelfSnapshotPollScript(getWebShelfStorageKeys(currentVid), false));
|
|
237
|
+
return {
|
|
238
|
+
snapshot: normalizeWebShelfSnapshot(result),
|
|
239
|
+
currentVid,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Read the structured shelf cache from the WeRead shelf page.
|
|
244
|
+
* The page hydrates localStorage asynchronously, so we poll briefly before
|
|
245
|
+
* giving up and treating the cache as unavailable for the current session.
|
|
246
|
+
*/
|
|
247
|
+
export async function loadWebShelfSnapshot(page) {
|
|
248
|
+
const { snapshot } = await loadWebShelfSnapshotWithVid(page);
|
|
249
|
+
return snapshot;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* `book` needs a trustworthy `bookId -> readerUrl` mapping, which may lag behind
|
|
253
|
+
* the first rawBooks cache hydration. Keep the fast shelf fallback path separate
|
|
254
|
+
* and only wait here, with a bounded poll, when resolving reader URLs.
|
|
255
|
+
*/
|
|
256
|
+
async function waitForTrustedWebShelfSnapshot(page, snapshot, currentVid) {
|
|
257
|
+
// Cache not available; nothing to wait for
|
|
258
|
+
if (!snapshot.cacheFound)
|
|
259
|
+
return snapshot;
|
|
260
|
+
// Indexes already fully cover rawBooks; no need to re-poll
|
|
261
|
+
if (getTrustedIndexedBookIds(snapshot).length > 0)
|
|
262
|
+
return snapshot;
|
|
263
|
+
if (!currentVid)
|
|
264
|
+
return snapshot;
|
|
265
|
+
const result = await page.evaluate(buildShelfSnapshotPollScript(getWebShelfStorageKeys(currentVid), true));
|
|
266
|
+
return normalizeWebShelfSnapshot(result);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Resolve a shelf bookId to the current web reader URL by pairing structured
|
|
270
|
+
* shelf cache order with the visible shelf links rendered on the page.
|
|
271
|
+
*/
|
|
272
|
+
export async function resolveShelfReaderUrl(page, bookId) {
|
|
273
|
+
const resolution = await resolveShelfReader(page, bookId);
|
|
274
|
+
return resolution.readerUrl;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Resolve the current reader URL for a shelf entry and return the parsed shelf
|
|
278
|
+
* snapshot used during resolution, so callers can reuse cached title/author
|
|
279
|
+
* metadata without loading the shelf page twice.
|
|
280
|
+
*/
|
|
281
|
+
export async function resolveShelfReader(page, bookId) {
|
|
282
|
+
const { snapshot: initialSnapshot, currentVid } = await loadWebShelfSnapshotWithVid(page);
|
|
283
|
+
const snapshot = await waitForTrustedWebShelfSnapshot(page, initialSnapshot, currentVid);
|
|
284
|
+
if (!snapshot.cacheFound) {
|
|
285
|
+
return { snapshot, readerUrl: null };
|
|
286
|
+
}
|
|
287
|
+
const rawBookIds = getUniqueRawBookIds(snapshot);
|
|
288
|
+
const trustedIndexedBookIds = getTrustedIndexedBookIds(snapshot);
|
|
289
|
+
const canUseRawOrderFallback = trustedIndexedBookIds.length === 0
|
|
290
|
+
&& rawBookIds.length > 0
|
|
291
|
+
&& snapshot.shelfIndexes.length === 0;
|
|
292
|
+
if (trustedIndexedBookIds.length === 0 && !canUseRawOrderFallback) {
|
|
293
|
+
return { snapshot, readerUrl: null };
|
|
294
|
+
}
|
|
295
|
+
const readerUrls = await page.evaluate(`
|
|
296
|
+
(() => Array.from(document.querySelectorAll('a.shelfBook[href]'))
|
|
297
|
+
.map((anchor) => {
|
|
298
|
+
const href = anchor.getAttribute('href') || '';
|
|
299
|
+
return href ? new URL(href, location.origin).toString() : '';
|
|
300
|
+
})
|
|
301
|
+
.filter(Boolean))
|
|
302
|
+
`);
|
|
303
|
+
const expectedEntryCount = trustedIndexedBookIds.length > 0 ? trustedIndexedBookIds.length : rawBookIds.length;
|
|
304
|
+
if (readerUrls.length !== expectedEntryCount) {
|
|
305
|
+
return { snapshot, readerUrl: null };
|
|
306
|
+
}
|
|
307
|
+
const entries = buildWebShelfEntries(snapshot, readerUrls);
|
|
308
|
+
const entry = entries.find((candidate) => candidate.bookId === bookId);
|
|
309
|
+
return {
|
|
310
|
+
snapshot,
|
|
311
|
+
readerUrl: entry?.readerUrl || null,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
87
314
|
/** Format a Unix timestamp (seconds) to YYYY-MM-DD in UTC+8. Returns '-' for invalid input. */
|
|
88
315
|
export function formatDate(ts) {
|
|
89
316
|
if (!Number.isFinite(ts) || ts <= 0)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { formatDate, fetchWebApi } from './utils.js';
|
|
2
|
+
import { buildWebShelfEntries, formatDate, fetchWebApi } from './utils.js';
|
|
3
3
|
describe('formatDate', () => {
|
|
4
4
|
it('formats a typical Unix timestamp in UTC+8', () => {
|
|
5
5
|
// 1705276800 = 2024-01-15 00:00:00 UTC = 2024-01-15 08:00:00 Beijing
|
|
@@ -56,3 +56,73 @@ describe('fetchWebApi', () => {
|
|
|
56
56
|
await expect(fetchWebApi('/search/global')).rejects.toThrow('Invalid JSON');
|
|
57
57
|
});
|
|
58
58
|
});
|
|
59
|
+
describe('buildWebShelfEntries', () => {
|
|
60
|
+
it('keeps mixed shelf item reader urls aligned when shelf indexes include non-book roles', () => {
|
|
61
|
+
const result = buildWebShelfEntries({
|
|
62
|
+
cacheFound: true,
|
|
63
|
+
rawBooks: [
|
|
64
|
+
{ bookId: 'MP_WXS_1', title: '公众号文章一', author: '作者甲' },
|
|
65
|
+
{ bookId: 'BOOK_2', title: '普通书二', author: '作者乙' },
|
|
66
|
+
{ bookId: 'MP_WXS_3', title: '公众号文章三', author: '作者丙' },
|
|
67
|
+
],
|
|
68
|
+
shelfIndexes: [
|
|
69
|
+
{ bookId: 'MP_WXS_1', idx: 0, role: 'mp' },
|
|
70
|
+
{ bookId: 'BOOK_2', idx: 1, role: 'book' },
|
|
71
|
+
{ bookId: 'MP_WXS_3', idx: 2, role: 'mp' },
|
|
72
|
+
],
|
|
73
|
+
}, [
|
|
74
|
+
'https://weread.qq.com/web/reader/mp1',
|
|
75
|
+
'https://weread.qq.com/web/reader/book2',
|
|
76
|
+
'https://weread.qq.com/web/reader/mp3',
|
|
77
|
+
]);
|
|
78
|
+
expect(result).toEqual([
|
|
79
|
+
{
|
|
80
|
+
bookId: 'MP_WXS_1',
|
|
81
|
+
title: '公众号文章一',
|
|
82
|
+
author: '作者甲',
|
|
83
|
+
readerUrl: 'https://weread.qq.com/web/reader/mp1',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
bookId: 'BOOK_2',
|
|
87
|
+
title: '普通书二',
|
|
88
|
+
author: '作者乙',
|
|
89
|
+
readerUrl: 'https://weread.qq.com/web/reader/book2',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
bookId: 'MP_WXS_3',
|
|
93
|
+
title: '公众号文章三',
|
|
94
|
+
author: '作者丙',
|
|
95
|
+
readerUrl: 'https://weread.qq.com/web/reader/mp3',
|
|
96
|
+
},
|
|
97
|
+
]);
|
|
98
|
+
});
|
|
99
|
+
it('falls back to raw cache order when shelf indexes are incomplete', () => {
|
|
100
|
+
const result = buildWebShelfEntries({
|
|
101
|
+
cacheFound: true,
|
|
102
|
+
rawBooks: [
|
|
103
|
+
{ bookId: 'BOOK_1', title: '第一本', author: '作者甲' },
|
|
104
|
+
{ bookId: 'BOOK_2', title: '第二本', author: '作者乙' },
|
|
105
|
+
],
|
|
106
|
+
shelfIndexes: [
|
|
107
|
+
{ bookId: 'BOOK_2', idx: 0, role: 'book' },
|
|
108
|
+
],
|
|
109
|
+
}, [
|
|
110
|
+
'https://weread.qq.com/web/reader/book1',
|
|
111
|
+
'https://weread.qq.com/web/reader/book2',
|
|
112
|
+
]);
|
|
113
|
+
expect(result).toEqual([
|
|
114
|
+
{
|
|
115
|
+
bookId: 'BOOK_1',
|
|
116
|
+
title: '第一本',
|
|
117
|
+
author: '作者甲',
|
|
118
|
+
readerUrl: 'https://weread.qq.com/web/reader/book1',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
bookId: 'BOOK_2',
|
|
122
|
+
title: '第二本',
|
|
123
|
+
author: '作者乙',
|
|
124
|
+
readerUrl: 'https://weread.qq.com/web/reader/book2',
|
|
125
|
+
},
|
|
126
|
+
]);
|
|
127
|
+
});
|
|
128
|
+
});
|