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