@jackwener/opencli 1.1.1 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/build-extension.yml +3 -3
- package/.github/workflows/ci.yml +6 -6
- package/.github/workflows/doc-check.yml +3 -3
- package/.github/workflows/e2e-headed.yml +2 -2
- package/.github/workflows/pkg-pr-new.yml +2 -2
- package/.github/workflows/release.yml +3 -3
- package/.github/workflows/security.yml +2 -2
- package/CONTRIBUTING.md +39 -1
- package/README.md +13 -10
- package/README.zh-CN.md +43 -17
- package/SKILL.md +10 -5
- package/dist/browser/cdp.d.ts +4 -4
- package/dist/browser/cdp.js +39 -16
- package/dist/browser/daemon-client.d.ts +4 -2
- package/dist/browser/daemon-client.js +17 -4
- package/dist/browser/dom-helpers.js +38 -7
- package/dist/browser/dom-snapshot.d.ts +86 -0
- package/dist/browser/dom-snapshot.js +729 -0
- package/dist/browser/dom-snapshot.test.d.ts +11 -0
- package/dist/browser/dom-snapshot.test.js +212 -0
- package/dist/browser/index.d.ts +2 -0
- package/dist/browser/index.js +1 -0
- package/dist/browser/mcp.js +3 -1
- package/dist/browser/page.d.ts +14 -24
- package/dist/browser/page.js +46 -6
- package/dist/build-manifest.d.ts +11 -4
- package/dist/build-manifest.js +59 -21
- package/dist/build-manifest.test.js +58 -2
- package/dist/cli-manifest.json +3856 -1509
- package/dist/cli.js +66 -0
- package/dist/clis/barchart/greeks.js +1 -1
- package/dist/clis/barchart/options.js +1 -1
- package/dist/clis/barchart/quote.js +1 -1
- package/dist/clis/bilibili/download.js +1 -1
- package/dist/clis/bilibili/following.js +1 -1
- package/dist/clis/bilibili/subtitle.js +1 -1
- package/dist/clis/bilibili/user-videos.js +1 -1
- package/dist/clis/boss/batchgreet.js +10 -97
- package/dist/clis/boss/chatlist.js +8 -25
- package/dist/clis/boss/chatmsg.js +11 -42
- package/dist/clis/boss/common.d.ts +92 -0
- package/dist/clis/boss/common.js +223 -0
- package/dist/clis/boss/detail.js +7 -49
- package/dist/clis/boss/exchange.js +13 -79
- package/dist/clis/boss/greet.js +18 -145
- package/dist/clis/boss/invite.js +26 -121
- package/dist/clis/boss/joblist.js +6 -31
- package/dist/clis/boss/mark.js +12 -85
- package/dist/clis/boss/recommend.js +10 -49
- package/dist/clis/boss/resume.js +18 -118
- package/dist/clis/boss/search.js +12 -60
- package/dist/clis/boss/send.js +17 -151
- package/dist/clis/boss/stats.js +18 -69
- package/dist/clis/coupang/add-to-cart.js +1 -1
- package/dist/clis/devto/tag.yaml +34 -0
- package/dist/clis/devto/top.yaml +29 -0
- package/dist/clis/devto/user.yaml +33 -0
- package/dist/clis/douban/book-hot.d.ts +1 -0
- package/dist/clis/douban/book-hot.js +14 -0
- package/dist/clis/douban/marks.d.ts +1 -0
- package/dist/clis/douban/marks.js +115 -0
- package/dist/clis/douban/movie-hot.d.ts +1 -0
- package/dist/clis/douban/movie-hot.js +14 -0
- package/dist/clis/douban/reviews.d.ts +1 -0
- package/dist/clis/douban/reviews.js +106 -0
- package/dist/clis/douban/search.d.ts +1 -0
- package/dist/clis/douban/search.js +16 -0
- package/dist/clis/douban/shared.d.ts +4 -0
- package/dist/clis/douban/shared.js +155 -0
- package/dist/clis/douban/subject.yaml +76 -0
- package/dist/clis/douban/top250.yaml +70 -0
- package/dist/clis/douban/utils.d.ts +35 -0
- package/dist/clis/douban/utils.js +48 -0
- package/dist/clis/facebook/add-friend.yaml +43 -0
- package/dist/clis/facebook/events.yaml +44 -0
- package/dist/clis/facebook/feed.yaml +63 -0
- package/dist/clis/facebook/friends.yaml +42 -0
- package/dist/clis/facebook/groups.yaml +50 -0
- package/dist/clis/facebook/join-group.yaml +44 -0
- package/dist/clis/facebook/memories.yaml +39 -0
- package/dist/clis/facebook/notifications.yaml +40 -0
- package/dist/clis/facebook/profile.yaml +37 -0
- package/dist/clis/facebook/search.yaml +46 -0
- package/dist/clis/google/news.d.ts +5 -0
- package/dist/clis/google/news.js +58 -0
- package/dist/clis/google/search.d.ts +10 -0
- package/dist/clis/google/search.js +127 -0
- package/dist/clis/google/suggest.d.ts +5 -0
- package/dist/clis/google/suggest.js +34 -0
- package/dist/clis/google/trends.d.ts +5 -0
- package/dist/clis/google/trends.js +38 -0
- package/dist/clis/google/utils.d.ts +9 -0
- package/dist/clis/google/utils.js +23 -0
- package/dist/clis/google/utils.test.d.ts +1 -0
- package/dist/clis/google/utils.test.js +75 -0
- package/dist/clis/grok/ask.d.ts +14 -0
- package/dist/clis/grok/ask.js +257 -65
- package/dist/clis/grok/ask.test.d.ts +1 -0
- package/dist/clis/grok/ask.test.js +36 -0
- package/dist/clis/instagram/comment.yaml +52 -0
- package/dist/clis/instagram/explore.yaml +43 -0
- package/dist/clis/instagram/follow.yaml +41 -0
- package/dist/clis/instagram/followers.yaml +51 -0
- package/dist/clis/instagram/following.yaml +51 -0
- package/dist/clis/instagram/like.yaml +46 -0
- package/dist/clis/instagram/profile.yaml +42 -0
- package/dist/clis/instagram/save.yaml +46 -0
- package/dist/clis/instagram/saved.yaml +40 -0
- package/dist/clis/instagram/search.yaml +43 -0
- package/dist/clis/instagram/unfollow.yaml +38 -0
- package/dist/clis/instagram/unlike.yaml +46 -0
- package/dist/clis/instagram/unsave.yaml +46 -0
- package/dist/clis/instagram/user.yaml +54 -0
- package/dist/clis/jike/repost.js +1 -1
- package/dist/clis/jimeng/generate.yaml +1 -0
- package/dist/clis/linux-do/category.yaml +1 -0
- package/dist/clis/lobsters/active.yaml +29 -0
- package/dist/clis/lobsters/hot.yaml +29 -0
- package/dist/clis/lobsters/newest.yaml +29 -0
- package/dist/clis/lobsters/tag.yaml +34 -0
- package/dist/clis/medium/feed.d.ts +1 -0
- package/dist/clis/medium/feed.js +15 -0
- package/dist/clis/medium/search.d.ts +1 -0
- package/dist/clis/medium/search.js +15 -0
- package/dist/clis/medium/shared.d.ts +5 -0
- package/dist/clis/medium/shared.js +78 -0
- package/dist/clis/medium/user.d.ts +1 -0
- package/dist/clis/medium/user.js +15 -0
- package/dist/clis/reddit/comment.js +1 -1
- package/dist/clis/reddit/read.js +1 -1
- package/dist/clis/reddit/save.js +1 -1
- package/dist/clis/reddit/subreddit.yaml +1 -0
- package/dist/clis/reddit/subscribe.js +1 -1
- package/dist/clis/reddit/upvote.js +1 -1
- package/dist/clis/sinablog/article.d.ts +1 -0
- package/dist/clis/sinablog/article.js +14 -0
- package/dist/clis/sinablog/hot.d.ts +1 -0
- package/dist/clis/sinablog/hot.js +14 -0
- package/dist/clis/sinablog/search.d.ts +1 -0
- package/dist/clis/sinablog/search.js +51 -0
- package/dist/clis/sinablog/shared.d.ts +7 -0
- package/dist/clis/sinablog/shared.js +187 -0
- package/dist/clis/sinablog/user.d.ts +1 -0
- package/dist/clis/sinablog/user.js +15 -0
- package/dist/clis/substack/feed.d.ts +1 -0
- package/dist/clis/substack/feed.js +15 -0
- package/dist/clis/substack/publication.d.ts +1 -0
- package/dist/clis/substack/publication.js +15 -0
- package/dist/clis/substack/search.d.ts +1 -0
- package/dist/clis/substack/search.js +77 -0
- package/dist/clis/substack/shared.d.ts +4 -0
- package/dist/clis/substack/shared.js +129 -0
- package/dist/clis/tiktok/comment.yaml +66 -0
- package/dist/clis/tiktok/explore.yaml +39 -0
- package/dist/clis/tiktok/follow.yaml +39 -0
- package/dist/clis/tiktok/following.yaml +46 -0
- package/dist/clis/tiktok/friends.yaml +47 -0
- package/dist/clis/tiktok/like.yaml +38 -0
- package/dist/clis/tiktok/live.yaml +51 -0
- package/dist/clis/tiktok/notifications.yaml +52 -0
- package/dist/clis/tiktok/profile.yaml +45 -0
- package/dist/clis/tiktok/save.yaml +34 -0
- package/dist/clis/tiktok/search.yaml +46 -0
- package/dist/clis/tiktok/unfollow.yaml +44 -0
- package/dist/clis/tiktok/unlike.yaml +38 -0
- package/dist/clis/tiktok/unsave.yaml +36 -0
- package/dist/clis/tiktok/user.yaml +44 -0
- package/dist/clis/twitter/download.d.ts +1 -1
- package/dist/clis/twitter/download.js +3 -3
- package/dist/clis/twitter/followers.js +1 -1
- package/dist/clis/twitter/following.js +1 -1
- package/dist/clis/twitter/thread.js +1 -1
- package/dist/clis/twitter/timeline.d.ts +23 -0
- package/dist/clis/twitter/timeline.js +42 -14
- package/dist/clis/twitter/timeline.test.d.ts +1 -0
- package/dist/clis/twitter/timeline.test.js +102 -0
- package/dist/clis/wikipedia/random.d.ts +1 -0
- package/dist/clis/wikipedia/random.js +19 -0
- package/dist/clis/wikipedia/search.js +3 -3
- package/dist/clis/wikipedia/summary.js +4 -9
- package/dist/clis/wikipedia/trending.d.ts +1 -0
- package/dist/clis/wikipedia/trending.js +35 -0
- package/dist/clis/wikipedia/utils.d.ts +28 -0
- package/dist/clis/wikipedia/utils.js +13 -0
- package/dist/clis/xiaohongshu/creator-note-detail.js +1 -1
- package/dist/clis/xiaohongshu/creator-note-detail.test.js +2 -0
- package/dist/clis/xiaohongshu/creator-notes.test.js +2 -0
- package/dist/clis/xiaohongshu/download.js +1 -1
- package/dist/clis/xueqiu/earnings-date.yaml +69 -0
- package/dist/clis/xueqiu/search.yaml +2 -1
- package/dist/clis/xueqiu/stock.yaml +2 -0
- package/dist/clis/yahoo-finance/quote.js +1 -1
- package/dist/commanderAdapter.js +13 -7
- package/dist/daemon.js +21 -0
- package/dist/discovery.d.ts +8 -0
- package/dist/discovery.js +105 -19
- package/dist/doctor.js +3 -1
- package/dist/doctor.test.js +46 -2
- package/dist/engine.test.d.ts +0 -3
- package/dist/engine.test.js +74 -6
- package/dist/execution.d.ts +4 -2
- package/dist/execution.js +31 -7
- package/dist/explore.d.ts +76 -3
- package/dist/explore.js +11 -4
- package/dist/generate.d.ts +41 -2
- package/dist/generate.js +5 -4
- package/dist/main.js +2 -1
- package/dist/pipeline/executor.d.ts +4 -2
- package/dist/pipeline/executor.js +54 -15
- package/dist/pipeline/executor.test.js +33 -6
- package/dist/pipeline/registry.d.ts +1 -1
- package/dist/pipeline/steps/browser.d.ts +7 -7
- package/dist/pipeline/steps/browser.js +15 -7
- package/dist/pipeline/steps/fetch.d.ts +1 -1
- package/dist/pipeline/steps/fetch.js +11 -7
- package/dist/pipeline/steps/transform.d.ts +6 -5
- package/dist/pipeline/steps/transform.js +30 -9
- package/dist/pipeline/template.d.ts +6 -6
- package/dist/pipeline/template.js +43 -5
- package/dist/pipeline/template.test.js +18 -0
- package/dist/pipeline/transform.test.js +11 -0
- package/dist/plugin.d.ts +31 -0
- package/dist/plugin.js +216 -0
- package/dist/plugin.test.d.ts +4 -0
- package/dist/plugin.test.js +76 -0
- package/dist/registry-api.d.ts +11 -0
- package/dist/registry-api.js +9 -0
- package/dist/registry.d.ts +11 -0
- package/dist/registry.js +6 -1
- package/dist/synthesize.d.ts +94 -4
- package/dist/synthesize.js +5 -4
- package/dist/types.d.ts +39 -26
- package/dist/validate.js +8 -2
- package/docs/.vitepress/config.mts +6 -4
- package/docs/adapters/browser/barchart.md +6 -5
- package/docs/adapters/browser/bilibili.md +9 -0
- package/docs/adapters/browser/devto.md +35 -0
- package/docs/adapters/browser/douban.md +38 -0
- package/docs/adapters/browser/facebook.md +36 -0
- package/docs/adapters/browser/google.md +62 -0
- package/docs/adapters/browser/grok.md +26 -8
- package/docs/adapters/browser/instagram.md +46 -0
- package/docs/adapters/browser/lobsters.md +32 -0
- package/docs/adapters/browser/medium.md +32 -0
- package/docs/adapters/browser/reddit.md +9 -0
- package/docs/adapters/browser/sinablog.md +36 -0
- package/docs/adapters/browser/substack.md +38 -0
- package/docs/adapters/browser/tiktok.md +68 -0
- package/docs/adapters/browser/wikipedia.md +11 -2
- package/docs/adapters/browser/xueqiu.md +10 -0
- package/docs/adapters/browser/yahoo-finance.md +6 -5
- package/docs/adapters/desktop/antigravity.md +6 -0
- package/docs/adapters/desktop/chatgpt.md +2 -1
- package/docs/adapters/desktop/codex.md +5 -1
- package/docs/adapters/desktop/cursor.md +4 -0
- package/docs/adapters/desktop/discord.md +7 -7
- package/docs/adapters/index.md +1 -4
- package/docs/guide/getting-started.md +1 -0
- package/docs/guide/plugins.md +153 -0
- package/docs/zh/guide/plugins.md +107 -0
- package/extension/dist/background.js +91 -23
- package/extension/src/background.ts +82 -29
- package/extension/src/cdp.ts +42 -1
- package/package.json +10 -5
- package/scripts/clean-dist.cjs +13 -0
- package/src/browser/cdp.ts +71 -31
- package/src/browser/daemon-client.ts +21 -5
- package/src/browser/dom-helpers.ts +38 -7
- package/src/browser/dom-snapshot.test.ts +249 -0
- package/src/browser/dom-snapshot.ts +770 -0
- package/src/browser/index.ts +2 -0
- package/src/browser/mcp.ts +3 -1
- package/src/browser/page.ts +57 -21
- package/src/build-manifest.test.ts +70 -2
- package/src/build-manifest.ts +94 -26
- package/src/cli.ts +71 -2
- package/src/clis/barchart/greeks.ts +1 -1
- package/src/clis/barchart/options.ts +1 -1
- package/src/clis/barchart/quote.ts +1 -1
- package/src/clis/bilibili/download.ts +1 -1
- package/src/clis/bilibili/following.ts +1 -1
- package/src/clis/bilibili/subtitle.ts +1 -1
- package/src/clis/bilibili/user-videos.ts +1 -1
- package/src/clis/boss/batchgreet.ts +14 -106
- package/src/clis/boss/chatlist.ts +12 -26
- package/src/clis/boss/chatmsg.ts +16 -40
- package/src/clis/boss/common.ts +287 -0
- package/src/clis/boss/detail.ts +8 -54
- package/src/clis/boss/exchange.ts +15 -89
- package/src/clis/boss/greet.ts +23 -160
- package/src/clis/boss/invite.ts +36 -133
- package/src/clis/boss/joblist.ts +7 -36
- package/src/clis/boss/mark.ts +13 -94
- package/src/clis/boss/recommend.ts +12 -57
- package/src/clis/boss/resume.ts +19 -124
- package/src/clis/boss/search.ts +13 -66
- package/src/clis/boss/send.ts +21 -161
- package/src/clis/boss/stats.ts +19 -74
- package/src/clis/coupang/add-to-cart.ts +1 -1
- package/src/clis/devto/tag.yaml +34 -0
- package/src/clis/devto/top.yaml +29 -0
- package/src/clis/devto/user.yaml +33 -0
- package/src/clis/douban/book-hot.ts +15 -0
- package/src/clis/douban/marks.ts +135 -0
- package/src/clis/douban/movie-hot.ts +15 -0
- package/src/clis/douban/reviews.ts +127 -0
- package/src/clis/douban/search.ts +17 -0
- package/src/clis/douban/shared.ts +165 -0
- package/src/clis/douban/subject.yaml +76 -0
- package/src/clis/douban/top250.yaml +70 -0
- package/src/clis/douban/utils.ts +81 -0
- package/src/clis/facebook/add-friend.yaml +43 -0
- package/src/clis/facebook/events.yaml +44 -0
- package/src/clis/facebook/feed.yaml +63 -0
- package/src/clis/facebook/friends.yaml +42 -0
- package/src/clis/facebook/groups.yaml +50 -0
- package/src/clis/facebook/join-group.yaml +44 -0
- package/src/clis/facebook/memories.yaml +39 -0
- package/src/clis/facebook/notifications.yaml +40 -0
- package/src/clis/facebook/profile.yaml +37 -0
- package/src/clis/facebook/search.yaml +46 -0
- package/src/clis/google/news.ts +66 -0
- package/src/clis/google/search.ts +133 -0
- package/src/clis/google/suggest.ts +40 -0
- package/src/clis/google/trends.ts +44 -0
- package/src/clis/google/utils.test.ts +82 -0
- package/src/clis/google/utils.ts +24 -0
- package/src/clis/grok/ask.test.ts +53 -0
- package/src/clis/grok/ask.ts +300 -69
- package/src/clis/instagram/comment.yaml +52 -0
- package/src/clis/instagram/explore.yaml +43 -0
- package/src/clis/instagram/follow.yaml +41 -0
- package/src/clis/instagram/followers.yaml +51 -0
- package/src/clis/instagram/following.yaml +51 -0
- package/src/clis/instagram/like.yaml +46 -0
- package/src/clis/instagram/profile.yaml +42 -0
- package/src/clis/instagram/save.yaml +46 -0
- package/src/clis/instagram/saved.yaml +40 -0
- package/src/clis/instagram/search.yaml +43 -0
- package/src/clis/instagram/unfollow.yaml +38 -0
- package/src/clis/instagram/unlike.yaml +46 -0
- package/src/clis/instagram/unsave.yaml +46 -0
- package/src/clis/instagram/user.yaml +54 -0
- package/src/clis/jike/repost.ts +1 -1
- package/src/clis/jimeng/generate.yaml +1 -0
- package/src/clis/linux-do/category.yaml +1 -0
- package/src/clis/lobsters/active.yaml +29 -0
- package/src/clis/lobsters/hot.yaml +29 -0
- package/src/clis/lobsters/newest.yaml +29 -0
- package/src/clis/lobsters/tag.yaml +34 -0
- package/src/clis/medium/feed.ts +16 -0
- package/src/clis/medium/search.ts +16 -0
- package/src/clis/medium/shared.ts +83 -0
- package/src/clis/medium/user.ts +16 -0
- package/src/clis/reddit/comment.ts +1 -1
- package/src/clis/reddit/read.ts +1 -1
- package/src/clis/reddit/save.ts +1 -1
- package/src/clis/reddit/subreddit.yaml +1 -0
- package/src/clis/reddit/subscribe.ts +1 -1
- package/src/clis/reddit/upvote.ts +1 -1
- package/src/clis/sinablog/article.ts +15 -0
- package/src/clis/sinablog/hot.ts +15 -0
- package/src/clis/sinablog/search.ts +56 -0
- package/src/clis/sinablog/shared.ts +198 -0
- package/src/clis/sinablog/user.ts +16 -0
- package/src/clis/substack/feed.ts +16 -0
- package/src/clis/substack/publication.ts +16 -0
- package/src/clis/substack/search.ts +91 -0
- package/src/clis/substack/shared.ts +132 -0
- package/src/clis/tiktok/comment.yaml +66 -0
- package/src/clis/tiktok/explore.yaml +39 -0
- package/src/clis/tiktok/follow.yaml +39 -0
- package/src/clis/tiktok/following.yaml +46 -0
- package/src/clis/tiktok/friends.yaml +47 -0
- package/src/clis/tiktok/like.yaml +38 -0
- package/src/clis/tiktok/live.yaml +51 -0
- package/src/clis/tiktok/notifications.yaml +52 -0
- package/src/clis/tiktok/profile.yaml +45 -0
- package/src/clis/tiktok/save.yaml +34 -0
- package/src/clis/tiktok/search.yaml +46 -0
- package/src/clis/tiktok/unfollow.yaml +44 -0
- package/src/clis/tiktok/unlike.yaml +38 -0
- package/src/clis/tiktok/unsave.yaml +36 -0
- package/src/clis/tiktok/user.yaml +44 -0
- package/src/clis/twitter/download.ts +3 -3
- package/src/clis/twitter/followers.ts +1 -1
- package/src/clis/twitter/following.ts +1 -1
- package/src/clis/twitter/thread.ts +1 -1
- package/src/clis/twitter/timeline.test.ts +109 -0
- package/src/clis/twitter/timeline.ts +59 -19
- package/src/clis/wikipedia/random.ts +19 -0
- package/src/clis/wikipedia/search.ts +10 -4
- package/src/clis/wikipedia/summary.ts +4 -9
- package/src/clis/wikipedia/trending.ts +41 -0
- package/src/clis/wikipedia/utils.ts +31 -0
- package/src/clis/xiaohongshu/creator-note-detail.test.ts +2 -0
- package/src/clis/xiaohongshu/creator-note-detail.ts +1 -1
- package/src/clis/xiaohongshu/creator-notes.test.ts +2 -0
- package/src/clis/xiaohongshu/download.ts +1 -1
- package/src/clis/xueqiu/earnings-date.yaml +69 -0
- package/src/clis/xueqiu/search.yaml +2 -1
- package/src/clis/xueqiu/stock.yaml +2 -0
- package/src/clis/yahoo-finance/quote.ts +1 -1
- package/src/commanderAdapter.ts +17 -10
- package/src/daemon.ts +23 -0
- package/src/discovery.ts +134 -24
- package/src/doctor.test.ts +59 -2
- package/src/doctor.ts +4 -2
- package/src/engine.test.ts +79 -6
- package/src/execution.ts +42 -16
- package/src/explore.ts +77 -9
- package/src/generate.ts +58 -9
- package/src/main.ts +2 -1
- package/src/pipeline/executor.test.ts +35 -6
- package/src/pipeline/executor.ts +68 -19
- package/src/pipeline/registry.ts +3 -3
- package/src/pipeline/steps/browser.ts +24 -15
- package/src/pipeline/steps/fetch.ts +18 -13
- package/src/pipeline/steps/transform.ts +40 -15
- package/src/pipeline/template.test.ts +18 -0
- package/src/pipeline/template.ts +86 -13
- package/src/pipeline/transform.test.ts +15 -2
- package/src/plugin.test.ts +86 -0
- package/src/plugin.ts +254 -0
- package/src/registry-api.ts +12 -0
- package/src/registry.ts +19 -1
- package/src/synthesize.ts +102 -21
- package/src/types.ts +44 -12
- package/src/validate.ts +19 -4
- package/tests/e2e/browser-public.test.ts +11 -0
- package/tests/e2e/public-commands.test.ts +64 -0
- package/dist/clis/feishu/new.d.ts +0 -1
- package/dist/clis/feishu/new.js +0 -27
- package/dist/clis/feishu/read.d.ts +0 -1
- package/dist/clis/feishu/read.js +0 -40
- package/dist/clis/feishu/search.d.ts +0 -1
- package/dist/clis/feishu/search.js +0 -30
- package/dist/clis/feishu/send.d.ts +0 -1
- package/dist/clis/feishu/send.js +0 -39
- package/dist/clis/feishu/status.d.ts +0 -1
- package/dist/clis/feishu/status.js +0 -28
- package/dist/clis/neteasemusic/like.d.ts +0 -1
- package/dist/clis/neteasemusic/like.js +0 -25
- package/dist/clis/neteasemusic/lyrics.d.ts +0 -1
- package/dist/clis/neteasemusic/lyrics.js +0 -47
- package/dist/clis/neteasemusic/next.d.ts +0 -1
- package/dist/clis/neteasemusic/next.js +0 -26
- package/dist/clis/neteasemusic/play.d.ts +0 -1
- package/dist/clis/neteasemusic/play.js +0 -26
- package/dist/clis/neteasemusic/playing.d.ts +0 -1
- package/dist/clis/neteasemusic/playing.js +0 -59
- package/dist/clis/neteasemusic/playlist.d.ts +0 -1
- package/dist/clis/neteasemusic/playlist.js +0 -46
- package/dist/clis/neteasemusic/prev.d.ts +0 -1
- package/dist/clis/neteasemusic/prev.js +0 -25
- package/dist/clis/neteasemusic/search.d.ts +0 -1
- package/dist/clis/neteasemusic/search.js +0 -52
- package/dist/clis/neteasemusic/status.d.ts +0 -1
- package/dist/clis/neteasemusic/status.js +0 -16
- package/dist/clis/neteasemusic/volume.d.ts +0 -1
- package/dist/clis/neteasemusic/volume.js +0 -54
- package/dist/clis/wechat/chats.d.ts +0 -1
- package/dist/clis/wechat/chats.js +0 -28
- package/dist/clis/wechat/contacts.d.ts +0 -1
- package/dist/clis/wechat/contacts.js +0 -28
- package/dist/clis/wechat/read.d.ts +0 -1
- package/dist/clis/wechat/read.js +0 -58
- package/dist/clis/wechat/search.d.ts +0 -1
- package/dist/clis/wechat/search.js +0 -31
- package/dist/clis/wechat/send.d.ts +0 -1
- package/dist/clis/wechat/send.js +0 -42
- package/dist/clis/wechat/status.d.ts +0 -1
- package/dist/clis/wechat/status.js +0 -29
- package/dist/pipeline.d.ts +0 -7
- package/dist/pipeline.js +0 -7
- package/docs/adapters/browser/github.md +0 -26
- package/docs/adapters/desktop/feishu.md +0 -20
- package/docs/adapters/desktop/neteasemusic.md +0 -31
- package/docs/adapters/desktop/wechat.md +0 -28
- package/src/clis/antigravity/README.md +0 -5
- package/src/clis/antigravity/README.zh-CN.md +0 -51
- package/src/clis/chaoxing/README.md +0 -14
- package/src/clis/chaoxing/README.zh-CN.md +0 -35
- package/src/clis/chatgpt/README.md +0 -5
- package/src/clis/chatgpt/README.zh-CN.md +0 -44
- package/src/clis/chatwise/README.md +0 -5
- package/src/clis/chatwise/README.zh-CN.md +0 -38
- package/src/clis/codex/README.md +0 -5
- package/src/clis/codex/README.zh-CN.md +0 -33
- package/src/clis/cursor/README.md +0 -5
- package/src/clis/cursor/README.zh-CN.md +0 -33
- package/src/clis/discord-app/README.md +0 -5
- package/src/clis/discord-app/README.zh-CN.md +0 -28
- package/src/clis/feishu/README.md +0 -5
- package/src/clis/feishu/README.zh-CN.md +0 -20
- package/src/clis/feishu/new.ts +0 -32
- package/src/clis/feishu/read.ts +0 -48
- package/src/clis/feishu/search.ts +0 -35
- package/src/clis/feishu/send.ts +0 -46
- package/src/clis/feishu/status.ts +0 -34
- package/src/clis/neteasemusic/README.md +0 -5
- package/src/clis/neteasemusic/README.zh-CN.md +0 -31
- package/src/clis/neteasemusic/like.ts +0 -28
- package/src/clis/neteasemusic/lyrics.ts +0 -53
- package/src/clis/neteasemusic/next.ts +0 -30
- package/src/clis/neteasemusic/play.ts +0 -30
- package/src/clis/neteasemusic/playing.ts +0 -62
- package/src/clis/neteasemusic/playlist.ts +0 -51
- package/src/clis/neteasemusic/prev.ts +0 -29
- package/src/clis/neteasemusic/search.ts +0 -58
- package/src/clis/neteasemusic/status.ts +0 -18
- package/src/clis/neteasemusic/volume.ts +0 -61
- package/src/clis/notion/README.md +0 -5
- package/src/clis/notion/README.zh-CN.md +0 -29
- package/src/clis/wechat/README.md +0 -5
- package/src/clis/wechat/README.zh-CN.md +0 -28
- package/src/clis/wechat/chats.ts +0 -33
- package/src/clis/wechat/contacts.ts +0 -33
- package/src/clis/wechat/read.ts +0 -72
- package/src/clis/wechat/search.ts +0 -36
- package/src/clis/wechat/send.ts +0 -49
- package/src/clis/wechat/status.ts +0 -35
- package/src/pipeline.ts +0 -8
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* BOSS直聘 batchgreet — batch greet recommended candidates.
|
|
3
|
-
*
|
|
4
|
-
* Combines recommend (greetRecSortList) + greet (UI automation).
|
|
5
|
-
* Sends greeting messages to multiple candidates sequentially.
|
|
6
3
|
*/
|
|
7
4
|
import { cli, Strategy } from '../../registry.js';
|
|
8
|
-
import
|
|
5
|
+
import {
|
|
6
|
+
requirePage, navigateToChat, fetchRecommendList,
|
|
7
|
+
clickCandidateInList, typeAndSendMessage, verbose,
|
|
8
|
+
} from './common.js';
|
|
9
9
|
|
|
10
10
|
cli({
|
|
11
11
|
site: 'boss',
|
|
@@ -13,6 +13,7 @@ cli({
|
|
|
13
13
|
description: 'BOSS直聘批量向推荐候选人发送招呼',
|
|
14
14
|
domain: 'www.zhipin.com',
|
|
15
15
|
strategy: Strategy.COOKIE,
|
|
16
|
+
navigateBefore: false,
|
|
16
17
|
browser: true,
|
|
17
18
|
args: [
|
|
18
19
|
{ name: 'job-id', default: '', help: 'Filter by encrypted job ID (greet all jobs if empty)' },
|
|
@@ -20,44 +21,18 @@ cli({
|
|
|
20
21
|
{ name: 'text', default: '', help: 'Custom greeting message (uses default if empty)' },
|
|
21
22
|
],
|
|
22
23
|
columns: ['name', 'status', 'detail'],
|
|
23
|
-
func: async (page
|
|
24
|
-
|
|
24
|
+
func: async (page, kwargs) => {
|
|
25
|
+
requirePage(page);
|
|
25
26
|
|
|
26
27
|
const filterJobId = kwargs['job-id'] || '';
|
|
27
28
|
const limit = kwargs.limit || 5;
|
|
28
29
|
const text = kwargs.text || '你好,请问您对这个职位感兴趣吗?';
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
console.error(`[opencli:boss] Batch greeting up to ${limit} candidates...`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
await page.goto('https://www.zhipin.com/web/chat/index');
|
|
35
|
-
await page.wait({ time: 3 });
|
|
31
|
+
verbose(`Batch greeting up to ${limit} candidates...`);
|
|
36
32
|
|
|
37
|
-
|
|
38
|
-
const listData: any = await page.evaluate(`
|
|
39
|
-
async () => {
|
|
40
|
-
return new Promise((resolve, reject) => {
|
|
41
|
-
const xhr = new XMLHttpRequest();
|
|
42
|
-
xhr.open('GET', 'https://www.zhipin.com/wapi/zprelation/friend/greetRecSortList', true);
|
|
43
|
-
xhr.withCredentials = true;
|
|
44
|
-
xhr.timeout = 15000;
|
|
45
|
-
xhr.setRequestHeader('Accept', 'application/json');
|
|
46
|
-
xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(e); } };
|
|
47
|
-
xhr.onerror = () => reject(new Error('Network Error'));
|
|
48
|
-
xhr.send();
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
`);
|
|
33
|
+
await navigateToChat(page, 3);
|
|
52
34
|
|
|
53
|
-
|
|
54
|
-
if (listData.code === 7 || listData.code === 37) {
|
|
55
|
-
throw new Error('Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。');
|
|
56
|
-
}
|
|
57
|
-
throw new Error(`获取推荐列表失败: ${listData.message}`);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
let candidates = listData.zpData?.friendList || [];
|
|
35
|
+
let candidates = await fetchRecommendList(page);
|
|
61
36
|
if (filterJobId) {
|
|
62
37
|
candidates = candidates.filter((f: any) => f.encryptJobId === filterJobId);
|
|
63
38
|
}
|
|
@@ -74,88 +49,21 @@ cli({
|
|
|
74
49
|
const friendName = candidate.name || '候选人';
|
|
75
50
|
|
|
76
51
|
try {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
async () => {
|
|
80
|
-
const item = document.querySelector('#_${numericUid}-0') || document.querySelector('[id^="_${numericUid}"]');
|
|
81
|
-
if (item) {
|
|
82
|
-
item.click();
|
|
83
|
-
return { clicked: true };
|
|
84
|
-
}
|
|
85
|
-
const items = document.querySelectorAll('.geek-item');
|
|
86
|
-
for (const el of items) {
|
|
87
|
-
if (el.id && el.id.startsWith('_${numericUid}')) {
|
|
88
|
-
el.click();
|
|
89
|
-
return { clicked: true };
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return { clicked: false };
|
|
93
|
-
}
|
|
94
|
-
`);
|
|
95
|
-
|
|
96
|
-
if (!clicked.clicked) {
|
|
52
|
+
const clicked = await clickCandidateInList(page, numericUid);
|
|
53
|
+
if (!clicked) {
|
|
97
54
|
results.push({ name: friendName, status: '❌ 跳过', detail: '在聊天列表中未找到' });
|
|
98
55
|
continue;
|
|
99
56
|
}
|
|
100
57
|
|
|
101
58
|
await page.wait({ time: 2 });
|
|
102
59
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
async () => {
|
|
106
|
-
const selectors = [
|
|
107
|
-
'.chat-editor [contenteditable="true"]',
|
|
108
|
-
'.chat-input [contenteditable="true"]',
|
|
109
|
-
'[contenteditable="true"]',
|
|
110
|
-
'textarea',
|
|
111
|
-
];
|
|
112
|
-
for (const sel of selectors) {
|
|
113
|
-
const el = document.querySelector(sel);
|
|
114
|
-
if (el && el.offsetParent !== null) {
|
|
115
|
-
el.focus();
|
|
116
|
-
if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
|
|
117
|
-
el.value = ${JSON.stringify(text)};
|
|
118
|
-
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
119
|
-
} else {
|
|
120
|
-
el.textContent = '';
|
|
121
|
-
el.focus();
|
|
122
|
-
document.execCommand('insertText', false, ${JSON.stringify(text)});
|
|
123
|
-
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
124
|
-
}
|
|
125
|
-
return { found: true };
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return { found: false };
|
|
129
|
-
}
|
|
130
|
-
`);
|
|
131
|
-
|
|
132
|
-
if (!typed.found) {
|
|
60
|
+
const sent = await typeAndSendMessage(page, text);
|
|
61
|
+
if (!sent) {
|
|
133
62
|
results.push({ name: friendName, status: '❌ 失败', detail: '找不到消息输入框' });
|
|
134
63
|
continue;
|
|
135
64
|
}
|
|
136
65
|
|
|
137
|
-
await page.wait({ time: 0.5 });
|
|
138
|
-
|
|
139
|
-
// Click send
|
|
140
|
-
const sent: any = await page.evaluate(`
|
|
141
|
-
async () => {
|
|
142
|
-
const btn = document.querySelector('.conversation-editor .submit')
|
|
143
|
-
|| document.querySelector('.submit-content .submit')
|
|
144
|
-
|| document.querySelector('.conversation-operate .submit');
|
|
145
|
-
if (btn) {
|
|
146
|
-
btn.click();
|
|
147
|
-
return { clicked: true };
|
|
148
|
-
}
|
|
149
|
-
return { clicked: false };
|
|
150
|
-
}
|
|
151
|
-
`);
|
|
152
|
-
|
|
153
|
-
if (!sent.clicked) {
|
|
154
|
-
await page.pressKey('Enter');
|
|
155
|
-
}
|
|
156
|
-
|
|
157
66
|
await page.wait({ time: 1.5 });
|
|
158
|
-
|
|
159
67
|
results.push({ name: friendName, status: '✅ 已发送', detail: text });
|
|
160
68
|
} catch (e: any) {
|
|
161
69
|
results.push({ name: friendName, status: '❌ 失败', detail: e.message?.substring(0, 80) || '未知错误' });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
|
-
import
|
|
2
|
+
import { requirePage, navigateToChat, fetchFriendList } from './common.js';
|
|
3
3
|
|
|
4
4
|
cli({
|
|
5
5
|
site: 'boss',
|
|
@@ -7,6 +7,7 @@ cli({
|
|
|
7
7
|
description: 'BOSS直聘查看聊天列表(招聘端)',
|
|
8
8
|
domain: 'www.zhipin.com',
|
|
9
9
|
strategy: Strategy.COOKIE,
|
|
10
|
+
navigateBefore: false,
|
|
10
11
|
browser: true,
|
|
11
12
|
args: [
|
|
12
13
|
{ name: 'page', type: 'int', default: 1, help: 'Page number' },
|
|
@@ -14,31 +15,16 @@ cli({
|
|
|
14
15
|
{ name: 'job-id', default: '0', help: 'Filter by job ID (0=all)' },
|
|
15
16
|
],
|
|
16
17
|
columns: ['name', 'job', 'last_msg', 'last_time', 'uid', 'security_id'],
|
|
17
|
-
func: async (page
|
|
18
|
-
|
|
19
|
-
await page
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return new Promise((resolve, reject) => {
|
|
28
|
-
const xhr = new XMLHttpRequest();
|
|
29
|
-
xhr.open('GET', '${targetUrl}', true);
|
|
30
|
-
xhr.withCredentials = true;
|
|
31
|
-
xhr.timeout = 15000;
|
|
32
|
-
xhr.setRequestHeader('Accept', 'application/json');
|
|
33
|
-
xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(new Error('JSON parse failed')); } };
|
|
34
|
-
xhr.onerror = () => reject(new Error('Network Error'));
|
|
35
|
-
xhr.send();
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
`);
|
|
39
|
-
if (data.code !== 0) throw new Error(`API error: ${data.message} (code=${data.code})`);
|
|
40
|
-
const friends = (data.zpData?.friendList || []).slice(0, limit);
|
|
41
|
-
return friends.map((f: any) => ({
|
|
18
|
+
func: async (page, kwargs) => {
|
|
19
|
+
requirePage(page);
|
|
20
|
+
await navigateToChat(page);
|
|
21
|
+
|
|
22
|
+
const friends = await fetchFriendList(page, {
|
|
23
|
+
pageNum: kwargs.page || 1,
|
|
24
|
+
jobId: kwargs['job-id'] || '0',
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return friends.slice(0, kwargs.limit || 20).map((f: any) => ({
|
|
42
28
|
name: f.name || '',
|
|
43
29
|
job: f.jobName || '',
|
|
44
30
|
last_msg: f.lastMessageInfo?.text || '',
|
package/src/clis/boss/chatmsg.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
|
-
import
|
|
2
|
+
import { requirePage, navigateToChat, bossFetch, findFriendByUid } from './common.js';
|
|
3
3
|
|
|
4
4
|
cli({
|
|
5
5
|
site: 'boss',
|
|
@@ -7,54 +7,30 @@ cli({
|
|
|
7
7
|
description: 'BOSS直聘查看与候选人的聊天消息',
|
|
8
8
|
domain: 'www.zhipin.com',
|
|
9
9
|
strategy: Strategy.COOKIE,
|
|
10
|
+
navigateBefore: false,
|
|
10
11
|
browser: true,
|
|
11
12
|
args: [
|
|
12
|
-
{ name: 'uid', required: true, help: 'Encrypted UID (from chatlist)' },
|
|
13
|
+
{ name: 'uid', required: true, positional: true, help: 'Encrypted UID (from chatlist)' },
|
|
13
14
|
{ name: 'page', type: 'int', default: 1, help: 'Page number' },
|
|
14
15
|
],
|
|
15
16
|
columns: ['from', 'type', 'text', 'time'],
|
|
16
|
-
func: async (page
|
|
17
|
-
|
|
18
|
-
await page
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
const friendData: any = await page.evaluate(`
|
|
22
|
-
async () => {
|
|
23
|
-
return new Promise((resolve, reject) => {
|
|
24
|
-
const xhr = new XMLHttpRequest();
|
|
25
|
-
xhr.open('GET', 'https://www.zhipin.com/wapi/zprelation/friend/getBossFriendListV2.json?page=1&status=0&jobId=0', true);
|
|
26
|
-
xhr.withCredentials = true;
|
|
27
|
-
xhr.timeout = 15000;
|
|
28
|
-
xhr.setRequestHeader('Accept', 'application/json');
|
|
29
|
-
xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(e); } };
|
|
30
|
-
xhr.onerror = () => reject(new Error('Network Error'));
|
|
31
|
-
xhr.send();
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
`);
|
|
35
|
-
if (friendData.code !== 0) throw new Error('获取好友列表失败');
|
|
36
|
-
const friend = (friendData.zpData?.friendList || []).find((f: any) => f.encryptUid === uid);
|
|
17
|
+
func: async (page, kwargs) => {
|
|
18
|
+
requirePage(page);
|
|
19
|
+
await navigateToChat(page);
|
|
20
|
+
|
|
21
|
+
const friend = await findFriendByUid(page, kwargs.uid);
|
|
37
22
|
if (!friend) throw new Error('未找到该候选人');
|
|
23
|
+
|
|
38
24
|
const gid = friend.uid;
|
|
39
25
|
const securityId = encodeURIComponent(friend.securityId);
|
|
40
26
|
const msgUrl = `https://www.zhipin.com/wapi/zpchat/boss/historyMsg?gid=${gid}&securityId=${securityId}&page=${kwargs.page}&c=20&src=0`;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
xhr.setRequestHeader('Accept', 'application/json');
|
|
49
|
-
xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { resolve({raw: xhr.responseText.substring(0,500)}); } };
|
|
50
|
-
xhr.onerror = () => reject(new Error('Network Error'));
|
|
51
|
-
xhr.send();
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
`);
|
|
55
|
-
if (msgData.raw) throw new Error('Non-JSON: ' + msgData.raw);
|
|
56
|
-
if (msgData.code !== 0) throw new Error('API error: ' + (msgData.message || msgData.code));
|
|
57
|
-
const TYPE_MAP: Record<number, string> = {1: '文本', 2: '图片', 3: '招呼', 4: '简历', 5: '系统', 6: '名片', 7: '语音', 8: '视频', 9: '表情'};
|
|
27
|
+
|
|
28
|
+
const msgData = await bossFetch(page, msgUrl);
|
|
29
|
+
|
|
30
|
+
const TYPE_MAP: Record<number, string> = {
|
|
31
|
+
1: '文本', 2: '图片', 3: '招呼', 4: '简历', 5: '系统',
|
|
32
|
+
6: '名片', 7: '语音', 8: '视频', 9: '表情',
|
|
33
|
+
};
|
|
58
34
|
const messages = msgData.zpData?.messages || msgData.zpData?.historyMsgList || [];
|
|
59
35
|
return messages.map((m: any) => {
|
|
60
36
|
const fromObj = m.from || {};
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BOSS直聘 common utilities — shared logic for all boss adapters.
|
|
3
|
+
*
|
|
4
|
+
* Consolidates:
|
|
5
|
+
* - Page navigation with cookie context
|
|
6
|
+
* - XHR-based API calls (GET/POST) with automatic login state detection
|
|
7
|
+
* - Cookie expiry error codes (code 7, 37)
|
|
8
|
+
* - Verbose logging
|
|
9
|
+
*/
|
|
10
|
+
import type { IPage } from '../../types.js';
|
|
11
|
+
|
|
12
|
+
// ── Constants ───────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
const BOSS_DOMAIN = 'www.zhipin.com';
|
|
15
|
+
const CHAT_URL = `https://${BOSS_DOMAIN}/web/chat/index`;
|
|
16
|
+
const COOKIE_EXPIRED_CODES = new Set([7, 37]);
|
|
17
|
+
const COOKIE_EXPIRED_MSG = 'Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。';
|
|
18
|
+
const DEFAULT_TIMEOUT = 15_000;
|
|
19
|
+
|
|
20
|
+
// ── Types ───────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
export interface BossApiResponse {
|
|
23
|
+
code: number;
|
|
24
|
+
message?: string;
|
|
25
|
+
zpData?: any;
|
|
26
|
+
[key: string]: any;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface FetchOptions {
|
|
30
|
+
/** HTTP method, defaults to 'GET' */
|
|
31
|
+
method?: 'GET' | 'POST';
|
|
32
|
+
/** POST body (will be sent as application/x-www-form-urlencoded) */
|
|
33
|
+
body?: string;
|
|
34
|
+
/** XHR timeout in ms, defaults to 15000 */
|
|
35
|
+
timeout?: number;
|
|
36
|
+
/** If true, don't throw on non-zero code — return the raw response */
|
|
37
|
+
allowNonZero?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── Core helpers ────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Assert that page is available (non-null).
|
|
44
|
+
*/
|
|
45
|
+
export function requirePage(page: IPage | null): asserts page is IPage {
|
|
46
|
+
if (!page) throw new Error('Browser page required');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Navigate to BOSS chat page and wait for it to settle.
|
|
51
|
+
* This establishes the cookie context needed for subsequent API calls.
|
|
52
|
+
*/
|
|
53
|
+
export async function navigateToChat(page: IPage, waitSeconds = 2): Promise<void> {
|
|
54
|
+
await page.goto(CHAT_URL);
|
|
55
|
+
await page.wait({ time: waitSeconds });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Navigate to a custom BOSS page (for search/detail that use different pages).
|
|
60
|
+
*/
|
|
61
|
+
export async function navigateTo(page: IPage, url: string, waitSeconds = 1): Promise<void> {
|
|
62
|
+
await page.goto(url);
|
|
63
|
+
await page.wait({ time: waitSeconds });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check if an API response indicates cookie expiry and throw a clear error.
|
|
68
|
+
* Call this after every BOSS API response with a non-zero code.
|
|
69
|
+
*/
|
|
70
|
+
export function checkAuth(data: BossApiResponse): void {
|
|
71
|
+
if (COOKIE_EXPIRED_CODES.has(data.code)) {
|
|
72
|
+
throw new Error(COOKIE_EXPIRED_MSG);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Throw if the API response is not code 0.
|
|
78
|
+
* Checks for cookie expiry first, then throws with the provided message.
|
|
79
|
+
*/
|
|
80
|
+
export function assertOk(data: BossApiResponse, errorPrefix?: string): void {
|
|
81
|
+
if (data.code === 0) return;
|
|
82
|
+
checkAuth(data);
|
|
83
|
+
const prefix = errorPrefix ? `${errorPrefix}: ` : '';
|
|
84
|
+
throw new Error(`${prefix}${data.message || 'Unknown error'} (code=${data.code})`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Make a credentialed XHR request via page.evaluate().
|
|
89
|
+
*
|
|
90
|
+
* This is the single XHR template — no more copy-pasting the same 15-line
|
|
91
|
+
* XMLHttpRequest boilerplate across every adapter.
|
|
92
|
+
*
|
|
93
|
+
* @returns Parsed JSON response
|
|
94
|
+
* @throws On network error, timeout, JSON parse failure, or cookie expiry
|
|
95
|
+
*/
|
|
96
|
+
export async function bossFetch(
|
|
97
|
+
page: IPage,
|
|
98
|
+
url: string,
|
|
99
|
+
opts: FetchOptions = {},
|
|
100
|
+
): Promise<BossApiResponse> {
|
|
101
|
+
const method = opts.method ?? 'GET';
|
|
102
|
+
const timeout = opts.timeout ?? DEFAULT_TIMEOUT;
|
|
103
|
+
const body = opts.body ?? null;
|
|
104
|
+
|
|
105
|
+
// Build the evaluate script. We use JSON.stringify for safe interpolation.
|
|
106
|
+
const script = `
|
|
107
|
+
async () => {
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
const xhr = new XMLHttpRequest();
|
|
110
|
+
xhr.open(${JSON.stringify(method)}, ${JSON.stringify(url)}, true);
|
|
111
|
+
xhr.withCredentials = true;
|
|
112
|
+
xhr.timeout = ${timeout};
|
|
113
|
+
xhr.setRequestHeader('Accept', 'application/json');
|
|
114
|
+
${method === 'POST' ? `xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');` : ''}
|
|
115
|
+
xhr.onload = () => {
|
|
116
|
+
try { resolve(JSON.parse(xhr.responseText)); }
|
|
117
|
+
catch(e) { reject(new Error('JSON parse failed: ' + xhr.responseText.substring(0, 200))); }
|
|
118
|
+
};
|
|
119
|
+
xhr.onerror = () => reject(new Error('Network Error'));
|
|
120
|
+
xhr.ontimeout = () => reject(new Error('Timeout'));
|
|
121
|
+
xhr.send(${body ? JSON.stringify(body) : 'null'});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
const data = await page.evaluate(script) as BossApiResponse;
|
|
127
|
+
|
|
128
|
+
// Auto-check auth unless caller opts out
|
|
129
|
+
if (!opts.allowNonZero && data.code !== 0) {
|
|
130
|
+
assertOk(data);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return data;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── Convenience helpers ─────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Fetch the boss friend (chat) list.
|
|
140
|
+
*/
|
|
141
|
+
export async function fetchFriendList(
|
|
142
|
+
page: IPage,
|
|
143
|
+
opts: { pageNum?: number; jobId?: string } = {},
|
|
144
|
+
): Promise<any[]> {
|
|
145
|
+
const pageNum = opts.pageNum ?? 1;
|
|
146
|
+
const jobId = opts.jobId ?? '0';
|
|
147
|
+
const url = `https://${BOSS_DOMAIN}/wapi/zprelation/friend/getBossFriendListV2.json?page=${pageNum}&status=0&jobId=${jobId}`;
|
|
148
|
+
const data = await bossFetch(page, url);
|
|
149
|
+
return data.zpData?.friendList || [];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Fetch the recommended candidates (greetRecSortList).
|
|
154
|
+
*/
|
|
155
|
+
export async function fetchRecommendList(page: IPage): Promise<any[]> {
|
|
156
|
+
const url = `https://${BOSS_DOMAIN}/wapi/zprelation/friend/greetRecSortList`;
|
|
157
|
+
const data = await bossFetch(page, url);
|
|
158
|
+
return data.zpData?.friendList || [];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Find a friend by encryptUid, searching through friend list and optionally greet list.
|
|
163
|
+
* Returns null if not found.
|
|
164
|
+
*/
|
|
165
|
+
export async function findFriendByUid(
|
|
166
|
+
page: IPage,
|
|
167
|
+
encryptUid: string,
|
|
168
|
+
opts: { maxPages?: number; checkGreetList?: boolean } = {},
|
|
169
|
+
): Promise<any | null> {
|
|
170
|
+
const maxPages = opts.maxPages ?? 1;
|
|
171
|
+
const checkGreetList = opts.checkGreetList ?? false;
|
|
172
|
+
|
|
173
|
+
// Search friend list pages
|
|
174
|
+
for (let p = 1; p <= maxPages; p++) {
|
|
175
|
+
const friends = await fetchFriendList(page, { pageNum: p });
|
|
176
|
+
const found = friends.find((f: any) => f.encryptUid === encryptUid);
|
|
177
|
+
if (found) return found;
|
|
178
|
+
if (friends.length === 0) break;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Optionally check greet list
|
|
182
|
+
if (checkGreetList) {
|
|
183
|
+
const greetList = await fetchRecommendList(page);
|
|
184
|
+
const found = greetList.find((f: any) => f.encryptUid === encryptUid);
|
|
185
|
+
if (found) return found;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ── UI automation helpers ───────────────────────────────────────────────────
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Click on a candidate in the chat list by their numeric UID.
|
|
195
|
+
* @returns true if clicked, false if not found
|
|
196
|
+
*/
|
|
197
|
+
export async function clickCandidateInList(page: IPage, numericUid: string | number): Promise<boolean> {
|
|
198
|
+
const uid = String(numericUid).replace(/[^0-9]/g, ''); // sanitize to digits only
|
|
199
|
+
const result: any = await page.evaluate(`
|
|
200
|
+
async () => {
|
|
201
|
+
const uid = ${JSON.stringify(uid)};
|
|
202
|
+
const item = document.querySelector('#_' + uid + '-0') || document.querySelector('[id^="_' + uid + '"]');
|
|
203
|
+
if (item) {
|
|
204
|
+
item.click();
|
|
205
|
+
return { clicked: true };
|
|
206
|
+
}
|
|
207
|
+
const items = document.querySelectorAll('.geek-item');
|
|
208
|
+
for (const el of items) {
|
|
209
|
+
if (el.id && el.id.startsWith('_' + uid)) {
|
|
210
|
+
el.click();
|
|
211
|
+
return { clicked: true };
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return { clicked: false };
|
|
215
|
+
}
|
|
216
|
+
`);
|
|
217
|
+
return result.clicked;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Type a message into the chat editor and send it.
|
|
222
|
+
* @returns true if sent successfully
|
|
223
|
+
*/
|
|
224
|
+
export async function typeAndSendMessage(page: IPage, text: string): Promise<boolean> {
|
|
225
|
+
const typed: any = await page.evaluate(`
|
|
226
|
+
async () => {
|
|
227
|
+
const selectors = [
|
|
228
|
+
'.chat-editor [contenteditable="true"]',
|
|
229
|
+
'.chat-input [contenteditable="true"]',
|
|
230
|
+
'.message-editor [contenteditable="true"]',
|
|
231
|
+
'.chat-conversation [contenteditable="true"]',
|
|
232
|
+
'[contenteditable="true"]',
|
|
233
|
+
'textarea',
|
|
234
|
+
];
|
|
235
|
+
for (const sel of selectors) {
|
|
236
|
+
const el = document.querySelector(sel);
|
|
237
|
+
if (el && el.offsetParent !== null) {
|
|
238
|
+
el.focus();
|
|
239
|
+
if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
|
|
240
|
+
el.value = ${JSON.stringify(text)};
|
|
241
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
242
|
+
} else {
|
|
243
|
+
el.textContent = '';
|
|
244
|
+
el.focus();
|
|
245
|
+
document.execCommand('insertText', false, ${JSON.stringify(text)});
|
|
246
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
247
|
+
}
|
|
248
|
+
return { found: true };
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return { found: false };
|
|
252
|
+
}
|
|
253
|
+
`);
|
|
254
|
+
|
|
255
|
+
if (!typed.found) return false;
|
|
256
|
+
|
|
257
|
+
await page.wait({ time: 0.5 });
|
|
258
|
+
|
|
259
|
+
// Click send button
|
|
260
|
+
const sent: any = await page.evaluate(`
|
|
261
|
+
async () => {
|
|
262
|
+
const btn = document.querySelector('.conversation-editor .submit')
|
|
263
|
+
|| document.querySelector('.submit-content .submit')
|
|
264
|
+
|| document.querySelector('.conversation-operate .submit');
|
|
265
|
+
if (btn) {
|
|
266
|
+
btn.click();
|
|
267
|
+
return { clicked: true };
|
|
268
|
+
}
|
|
269
|
+
return { clicked: false };
|
|
270
|
+
}
|
|
271
|
+
`);
|
|
272
|
+
|
|
273
|
+
if (!sent.clicked) {
|
|
274
|
+
await page.pressKey('Enter');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Verbose log helper — prints when OPENCLI_VERBOSE or DEBUG=opencli is set.
|
|
282
|
+
*/
|
|
283
|
+
export function verbose(msg: string): void {
|
|
284
|
+
if (process.env.OPENCLI_VERBOSE || process.env.DEBUG?.includes('opencli')) {
|
|
285
|
+
console.error(`[opencli:boss] ${msg}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
package/src/clis/boss/detail.ts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* BOSS直聘 job detail — fetch full job posting details via browser cookie API.
|
|
3
|
-
*
|
|
4
|
-
* Uses securityId from search results to call the detail API.
|
|
5
|
-
* Returns: job description, skills, welfare, boss info, company info, address.
|
|
6
3
|
*/
|
|
7
4
|
import { cli, Strategy } from '../../registry.js';
|
|
8
|
-
import
|
|
5
|
+
import { requirePage, navigateTo, bossFetch, verbose } from './common.js';
|
|
9
6
|
|
|
10
7
|
cli({
|
|
11
8
|
site: 'boss',
|
|
@@ -13,10 +10,10 @@ cli({
|
|
|
13
10
|
description: 'BOSS直聘查看职位详情',
|
|
14
11
|
domain: 'www.zhipin.com',
|
|
15
12
|
strategy: Strategy.COOKIE,
|
|
16
|
-
|
|
13
|
+
navigateBefore: false,
|
|
17
14
|
browser: true,
|
|
18
15
|
args: [
|
|
19
|
-
{ name: 'security-id', required: true, help: 'Security ID from search results (securityId field)' },
|
|
16
|
+
{ name: 'security-id', positional: true, required: true, help: 'Security ID from search results (securityId field)' },
|
|
20
17
|
],
|
|
21
18
|
columns: [
|
|
22
19
|
'name', 'salary', 'experience', 'degree', 'city', 'district',
|
|
@@ -25,60 +22,17 @@ cli({
|
|
|
25
22
|
'company', 'industry', 'scale', 'stage',
|
|
26
23
|
'address', 'url',
|
|
27
24
|
],
|
|
28
|
-
func: async (page
|
|
29
|
-
|
|
25
|
+
func: async (page, kwargs) => {
|
|
26
|
+
requirePage(page);
|
|
30
27
|
|
|
31
28
|
const securityId = kwargs['security-id'];
|
|
29
|
+
verbose('Fetching job detail...');
|
|
32
30
|
|
|
33
31
|
// Navigate to zhipin.com first to establish cookie context (referrer + cookies)
|
|
34
|
-
await page
|
|
35
|
-
await page.wait({ time: 1 });
|
|
32
|
+
await navigateTo(page, 'https://www.zhipin.com/web/geek/job');
|
|
36
33
|
|
|
37
34
|
const targetUrl = `https://www.zhipin.com/wapi/zpgeek/job/detail.json?securityId=${encodeURIComponent(securityId)}`;
|
|
38
|
-
|
|
39
|
-
if (process.env.OPENCLI_VERBOSE || process.env.DEBUG?.includes('opencli')) {
|
|
40
|
-
console.error(`[opencli:boss] Fetching job detail...`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const evaluateScript = `
|
|
44
|
-
async () => {
|
|
45
|
-
return new Promise((resolve, reject) => {
|
|
46
|
-
const xhr = new window.XMLHttpRequest();
|
|
47
|
-
xhr.open('GET', ${JSON.stringify(targetUrl)}, true);
|
|
48
|
-
xhr.withCredentials = true;
|
|
49
|
-
xhr.timeout = 15000;
|
|
50
|
-
xhr.setRequestHeader('Accept', 'application/json, text/plain, */*');
|
|
51
|
-
xhr.onload = () => {
|
|
52
|
-
if (xhr.status >= 200 && xhr.status < 300) {
|
|
53
|
-
try {
|
|
54
|
-
resolve(JSON.parse(xhr.responseText));
|
|
55
|
-
} catch (e) {
|
|
56
|
-
reject(new Error('Failed to parse JSON. Raw (200 chars): ' + xhr.responseText.substring(0, 200)));
|
|
57
|
-
}
|
|
58
|
-
} else {
|
|
59
|
-
reject(new Error('XHR HTTP Status: ' + xhr.status));
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
xhr.onerror = () => reject(new Error('XHR Network Error'));
|
|
63
|
-
xhr.ontimeout = () => reject(new Error('XHR Timeout'));
|
|
64
|
-
xhr.send();
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
`;
|
|
68
|
-
|
|
69
|
-
let data: any;
|
|
70
|
-
try {
|
|
71
|
-
data = await page.evaluate(evaluateScript);
|
|
72
|
-
} catch (e: any) {
|
|
73
|
-
throw new Error('API evaluate failed: ' + e.message);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (data.code !== 0) {
|
|
77
|
-
if (data.code === 37) {
|
|
78
|
-
throw new Error('Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。');
|
|
79
|
-
}
|
|
80
|
-
throw new Error(`BOSS API error: ${data.message || 'Unknown'} (code=${data.code})`);
|
|
81
|
-
}
|
|
35
|
+
const data = await bossFetch(page, targetUrl);
|
|
82
36
|
|
|
83
37
|
const zpData = data.zpData || {};
|
|
84
38
|
const jobInfo = zpData.jobInfo || {};
|