@jackwener/opencli 1.1.1 → 1.2.0
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/CONTRIBUTING.md +39 -1
- package/README.md +9 -10
- package/README.zh-CN.md +39 -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 +2 -1
- 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/page.d.ts +14 -24
- package/dist/browser/page.js +37 -4
- 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/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 +2 -2
- package/dist/pipeline/executor.js +2 -2
- 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/src/background.ts +18 -11
- 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 +2 -1
- 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/page.ts +50 -19
- 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/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 +11 -7
- 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
package/dist/clis/boss/resume.js
CHANGED
|
@@ -2,25 +2,25 @@
|
|
|
2
2
|
* BOSS直聘 resume — view candidate resume/profile via chat page UI scraping (boss side).
|
|
3
3
|
*
|
|
4
4
|
* Flow: navigate to chat page → click on candidate → scrape the right panel info.
|
|
5
|
-
* The chat page loads candidate basic info, work experience, and education
|
|
6
|
-
* in the right panel when a candidate is selected.
|
|
7
5
|
*
|
|
8
|
-
* HTML structure
|
|
6
|
+
* Right panel HTML structure:
|
|
9
7
|
* .base-info-single-detial → name, gender, age, experience, degree
|
|
10
8
|
* .experience-content.time-list → time ranges (icon-base-info-work / icon-base-info-edu)
|
|
11
9
|
* .experience-content.detail-list → details (company·position / school·major·degree)
|
|
12
10
|
* .position-content → job being discussed + expectation
|
|
13
11
|
*/
|
|
14
12
|
import { cli, Strategy } from '../../registry.js';
|
|
13
|
+
import { requirePage, navigateToChat, findFriendByUid, clickCandidateInList } from './common.js';
|
|
15
14
|
cli({
|
|
16
15
|
site: 'boss',
|
|
17
16
|
name: 'resume',
|
|
18
17
|
description: 'BOSS直聘查看候选人简历(招聘端)',
|
|
19
18
|
domain: 'www.zhipin.com',
|
|
20
19
|
strategy: Strategy.COOKIE,
|
|
20
|
+
navigateBefore: false,
|
|
21
21
|
browser: true,
|
|
22
22
|
args: [
|
|
23
|
-
{ name: 'uid', required: true, help: 'Encrypted UID of the candidate (from chatlist)' },
|
|
23
|
+
{ name: 'uid', required: true, positional: true, help: 'Encrypted UID of the candidate (from chatlist)' },
|
|
24
24
|
],
|
|
25
25
|
columns: [
|
|
26
26
|
'name', 'gender', 'age', 'experience', 'degree', 'active_time',
|
|
@@ -28,100 +28,26 @@ cli({
|
|
|
28
28
|
'job_chatting', 'expect',
|
|
29
29
|
],
|
|
30
30
|
func: async (page, kwargs) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
// Step 1: Navigate to chat page
|
|
35
|
-
await page.goto('https://www.zhipin.com/web/chat/index');
|
|
36
|
-
await page.wait({ time: 3 });
|
|
37
|
-
// Step 2: Get friend list to find candidate's numeric uid
|
|
38
|
-
const friendData = 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/getBossFriendListV2.json?page=1&status=0&jobId=0', 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
|
-
`);
|
|
52
|
-
if (friendData.code !== 0) {
|
|
53
|
-
if (friendData.code === 7 || friendData.code === 37) {
|
|
54
|
-
throw new Error('Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。');
|
|
55
|
-
}
|
|
56
|
-
throw new Error('获取好友列表失败: ' + (friendData.message || friendData.code));
|
|
57
|
-
}
|
|
58
|
-
let friend = null;
|
|
59
|
-
const allFriends = friendData.zpData?.friendList || [];
|
|
60
|
-
friend = allFriends.find((f) => f.encryptUid === uid);
|
|
61
|
-
if (!friend) {
|
|
62
|
-
for (let p = 2; p <= 5; p++) {
|
|
63
|
-
const moreUrl = `https://www.zhipin.com/wapi/zprelation/friend/getBossFriendListV2.json?page=${p}&status=0&jobId=0`;
|
|
64
|
-
const moreData = await page.evaluate(`
|
|
65
|
-
async () => {
|
|
66
|
-
return new Promise((resolve, reject) => {
|
|
67
|
-
const xhr = new XMLHttpRequest();
|
|
68
|
-
xhr.open('GET', '${moreUrl}', true);
|
|
69
|
-
xhr.withCredentials = true;
|
|
70
|
-
xhr.timeout = 15000;
|
|
71
|
-
xhr.setRequestHeader('Accept', 'application/json');
|
|
72
|
-
xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(e); } };
|
|
73
|
-
xhr.onerror = () => reject(new Error('Network Error'));
|
|
74
|
-
xhr.send();
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
`);
|
|
78
|
-
if (moreData.code === 0) {
|
|
79
|
-
const list = moreData.zpData?.friendList || [];
|
|
80
|
-
friend = list.find((f) => f.encryptUid === uid);
|
|
81
|
-
if (friend)
|
|
82
|
-
break;
|
|
83
|
-
if (list.length === 0)
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
31
|
+
requirePage(page);
|
|
32
|
+
await navigateToChat(page, 3);
|
|
33
|
+
const friend = await findFriendByUid(page, kwargs.uid, { maxPages: 5 });
|
|
88
34
|
if (!friend)
|
|
89
35
|
throw new Error('未找到该候选人,请确认 uid 是否正确');
|
|
90
36
|
const numericUid = friend.uid;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
async () => {
|
|
94
|
-
const item = document.querySelector('#_${numericUid}-0') || document.querySelector('[id^="_${numericUid}"]');
|
|
95
|
-
if (item) {
|
|
96
|
-
item.click();
|
|
97
|
-
return { clicked: true };
|
|
98
|
-
}
|
|
99
|
-
const items = document.querySelectorAll('.geek-item');
|
|
100
|
-
for (const el of items) {
|
|
101
|
-
if (el.id && el.id.startsWith('_${numericUid}')) {
|
|
102
|
-
el.click();
|
|
103
|
-
return { clicked: true };
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return { clicked: false };
|
|
107
|
-
}
|
|
108
|
-
`);
|
|
109
|
-
if (!clicked.clicked) {
|
|
37
|
+
const clicked = await clickCandidateInList(page, numericUid);
|
|
38
|
+
if (!clicked) {
|
|
110
39
|
throw new Error('无法在聊天列表中找到该用户,请确认聊天列表中有此人');
|
|
111
40
|
}
|
|
112
|
-
// Step 4: Wait for right panel to load
|
|
113
41
|
await page.wait({ time: 2 });
|
|
114
|
-
//
|
|
42
|
+
// Scrape the right panel
|
|
115
43
|
const resumeInfo = await page.evaluate(`
|
|
116
44
|
(() => {
|
|
117
45
|
const container = document.querySelector('.base-info-single-container') || document.querySelector('.base-info-content');
|
|
118
46
|
if (!container) return { error: 'no container found' };
|
|
119
47
|
|
|
120
|
-
// === Basic Info ===
|
|
121
48
|
const nameEl = container.querySelector('.base-name');
|
|
122
49
|
const name = nameEl ? nameEl.textContent.trim() : '';
|
|
123
50
|
|
|
124
|
-
// Gender
|
|
125
51
|
let gender = '';
|
|
126
52
|
const detailDiv = container.querySelector('.base-info-single-detial');
|
|
127
53
|
if (detailDiv) {
|
|
@@ -133,11 +59,9 @@ cli({
|
|
|
133
59
|
}
|
|
134
60
|
}
|
|
135
61
|
|
|
136
|
-
// Active time
|
|
137
62
|
const activeEl = container.querySelector('.active-time');
|
|
138
63
|
const activeTime = activeEl ? activeEl.textContent.trim() : '';
|
|
139
64
|
|
|
140
|
-
// Age, experience, degree — direct child divs of .base-info-single-detial
|
|
141
65
|
let age = '', experience = '', degree = '';
|
|
142
66
|
if (detailDiv) {
|
|
143
67
|
for (const el of detailDiv.children) {
|
|
@@ -152,50 +76,32 @@ cli({
|
|
|
152
76
|
}
|
|
153
77
|
}
|
|
154
78
|
|
|
155
|
-
|
|
156
|
-
// Structure: two .experience-content divs
|
|
157
|
-
// 1. .time-list → <li> items with icon (work/edu) and time span
|
|
158
|
-
// 2. .detail-list → <li> items with icon (work/edu) and detail text
|
|
159
|
-
// Each <li> has a <use> with xlink:href "#icon-base-info-work" or "#icon-base-info-edu"
|
|
160
|
-
|
|
161
|
-
const workTimes = [];
|
|
162
|
-
const eduTimes = [];
|
|
163
|
-
const workDetails = [];
|
|
164
|
-
const eduDetails = [];
|
|
79
|
+
const workTimes = [], eduTimes = [], workDetails = [], eduDetails = [];
|
|
165
80
|
|
|
166
81
|
const timeList = container.querySelector('.experience-content.time-list');
|
|
167
82
|
if (timeList) {
|
|
168
|
-
const
|
|
169
|
-
for (const li of lis) {
|
|
83
|
+
for (const li of timeList.querySelectorAll('li')) {
|
|
170
84
|
const useEl = li.querySelector('use');
|
|
171
85
|
const href = useEl ? (useEl.getAttribute('xlink:href') || useEl.getAttribute('href') || '') : '';
|
|
172
86
|
const timeSpan = li.querySelector('.time');
|
|
173
87
|
const timeText = timeSpan ? timeSpan.textContent.trim() : li.textContent.trim();
|
|
174
|
-
if (href.includes('base-info-edu'))
|
|
175
|
-
|
|
176
|
-
} else {
|
|
177
|
-
workTimes.push(timeText);
|
|
178
|
-
}
|
|
88
|
+
if (href.includes('base-info-edu')) eduTimes.push(timeText);
|
|
89
|
+
else workTimes.push(timeText);
|
|
179
90
|
}
|
|
180
91
|
}
|
|
181
92
|
|
|
182
93
|
const detailList = container.querySelector('.experience-content.detail-list');
|
|
183
94
|
if (detailList) {
|
|
184
|
-
const
|
|
185
|
-
for (const li of lis) {
|
|
95
|
+
for (const li of detailList.querySelectorAll('li')) {
|
|
186
96
|
const useEl = li.querySelector('use');
|
|
187
97
|
const href = useEl ? (useEl.getAttribute('xlink:href') || useEl.getAttribute('href') || '') : '';
|
|
188
98
|
const valueSpan = li.querySelector('.value');
|
|
189
99
|
const valueText = valueSpan ? valueSpan.textContent.trim() : li.textContent.trim();
|
|
190
|
-
if (href.includes('base-info-edu'))
|
|
191
|
-
|
|
192
|
-
} else {
|
|
193
|
-
workDetails.push(valueText);
|
|
194
|
-
}
|
|
100
|
+
if (href.includes('base-info-edu')) eduDetails.push(valueText);
|
|
101
|
+
else workDetails.push(valueText);
|
|
195
102
|
}
|
|
196
103
|
}
|
|
197
104
|
|
|
198
|
-
// Combine times and details
|
|
199
105
|
const workHistory = [];
|
|
200
106
|
for (let i = 0; i < Math.max(workTimes.length, workDetails.length); i++) {
|
|
201
107
|
const parts = [];
|
|
@@ -212,22 +118,16 @@ cli({
|
|
|
212
118
|
if (parts.length) education.push(parts.join(' '));
|
|
213
119
|
}
|
|
214
120
|
|
|
215
|
-
// === Job Chatting & Expect ===
|
|
216
121
|
const positionContent = container.querySelector('.position-content');
|
|
217
122
|
let jobChatting = '', expect = '';
|
|
218
123
|
if (positionContent) {
|
|
219
124
|
const posNameEl = positionContent.querySelector('.position-name');
|
|
220
125
|
if (posNameEl) jobChatting = posNameEl.textContent.trim();
|
|
221
|
-
|
|
222
126
|
const expectEl = positionContent.querySelector('.position-item.expect .value');
|
|
223
127
|
if (expectEl) expect = expectEl.textContent.trim();
|
|
224
128
|
}
|
|
225
129
|
|
|
226
|
-
return {
|
|
227
|
-
name, gender, age, experience, degree, activeTime,
|
|
228
|
-
workHistory, education,
|
|
229
|
-
jobChatting, expect,
|
|
230
|
-
};
|
|
130
|
+
return { name, gender, age, experience, degree, activeTime, workHistory, education, jobChatting, expect };
|
|
231
131
|
})()
|
|
232
132
|
`);
|
|
233
133
|
if (resumeInfo.error) {
|
package/dist/clis/boss/search.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* BOSS直聘 job search — browser cookie API.
|
|
3
3
|
*/
|
|
4
4
|
import { cli, Strategy } from '../../registry.js';
|
|
5
|
+
import { requirePage, navigateTo, bossFetch, verbose } from './common.js';
|
|
5
6
|
/** City name → BOSS Zhipin city code mapping */
|
|
6
7
|
const CITY_CODES = {
|
|
7
8
|
'全国': '100010000', '北京': '101010100', '上海': '101020100',
|
|
@@ -67,6 +68,7 @@ cli({
|
|
|
67
68
|
description: 'BOSS直聘搜索职位',
|
|
68
69
|
domain: 'www.zhipin.com',
|
|
69
70
|
strategy: Strategy.COOKIE,
|
|
71
|
+
navigateBefore: false,
|
|
70
72
|
browser: true,
|
|
71
73
|
args: [
|
|
72
74
|
{ name: 'query', required: true, positional: true, help: 'Search keyword (e.g. AI agent, 前端)' },
|
|
@@ -80,16 +82,10 @@ cli({
|
|
|
80
82
|
],
|
|
81
83
|
columns: ['name', 'salary', 'company', 'area', 'experience', 'degree', 'skills', 'boss', 'security_id', 'url'],
|
|
82
84
|
func: async (page, kwargs) => {
|
|
83
|
-
|
|
84
|
-
throw new Error('Browser page required');
|
|
85
|
+
requirePage(page);
|
|
85
86
|
const cityCode = resolveCity(kwargs.city);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
// Navigate to the Web search view first to establish proper referrer context
|
|
90
|
-
// This is a lesson learned from boss-cli: referrer is important
|
|
91
|
-
await page.goto(`https://www.zhipin.com/web/geek/job?query=${encodeURIComponent(kwargs.query)}&city=${cityCode}`);
|
|
92
|
-
// Give the page a tiny bit of time to settle to avoid immediate 403s
|
|
87
|
+
verbose('Navigating to set referrer context...');
|
|
88
|
+
await navigateTo(page, `https://www.zhipin.com/web/geek/job?query=${encodeURIComponent(kwargs.query)}&city=${cityCode}`);
|
|
93
89
|
await new Promise(r => setTimeout(r, 1000));
|
|
94
90
|
const expVal = resolveMap(kwargs.experience, EXP_MAP);
|
|
95
91
|
const degreeVal = resolveMap(kwargs.degree, DEGREE_MAP);
|
|
@@ -101,7 +97,6 @@ cli({
|
|
|
101
97
|
const seenIds = new Set();
|
|
102
98
|
while (allJobs.length < limit) {
|
|
103
99
|
if (allJobs.length > 0) {
|
|
104
|
-
// Human-like pause between page fetches (1-3 seconds)
|
|
105
100
|
await new Promise(r => setTimeout(r, 1000 + Math.random() * 2000));
|
|
106
101
|
}
|
|
107
102
|
const qs = new URLSearchParams({
|
|
@@ -120,52 +115,12 @@ cli({
|
|
|
120
115
|
if (industryVal)
|
|
121
116
|
qs.set('industry', industryVal);
|
|
122
117
|
const targetUrl = `https://www.zhipin.com/wapi/zpgeek/search/joblist.json?${qs.toString()}`;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
const evaluateScript = `
|
|
127
|
-
async () => {
|
|
128
|
-
return new Promise((resolve, reject) => {
|
|
129
|
-
const xhr = new window.XMLHttpRequest();
|
|
130
|
-
xhr.open('GET', '${targetUrl}', true);
|
|
131
|
-
xhr.withCredentials = true;
|
|
132
|
-
xhr.timeout = 15000; // 15s timeout
|
|
133
|
-
xhr.setRequestHeader('Accept', 'application/json, text/plain, */*');
|
|
134
|
-
xhr.onload = () => {
|
|
135
|
-
if (xhr.status >= 200 && xhr.status < 300) {
|
|
136
|
-
try {
|
|
137
|
-
resolve(JSON.parse(xhr.responseText));
|
|
138
|
-
} catch (e) {
|
|
139
|
-
reject(new Error('Failed to parse JSON. Raw (200 chars): ' + xhr.responseText.substring(0, 200)));
|
|
140
|
-
}
|
|
141
|
-
} else {
|
|
142
|
-
reject(new Error('XHR HTTP Status: ' + xhr.status));
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
xhr.onerror = () => reject(new Error('XHR Network Error'));
|
|
146
|
-
xhr.ontimeout = () => reject(new Error('XHR Timeout'));
|
|
147
|
-
xhr.send();
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
`;
|
|
151
|
-
let data;
|
|
152
|
-
try {
|
|
153
|
-
data = await page.evaluate(evaluateScript);
|
|
154
|
-
}
|
|
155
|
-
catch (e) {
|
|
156
|
-
throw new Error('API evaluate failed: ' + e.message);
|
|
157
|
-
}
|
|
158
|
-
if (data.code !== 0) {
|
|
159
|
-
if (data.code === 37) {
|
|
160
|
-
throw new Error('Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。');
|
|
161
|
-
}
|
|
162
|
-
throw new Error(`BOSS API error: ${data.message || 'Unknown'} (code=${data.code})\nRaw data: ${JSON.stringify(data)}`);
|
|
163
|
-
}
|
|
118
|
+
verbose(`Fetching page ${currentPage}... (current jobs: ${allJobs.length})`);
|
|
119
|
+
const data = await bossFetch(page, targetUrl);
|
|
164
120
|
const zpData = data.zpData || {};
|
|
165
121
|
const batch = zpData.jobList || [];
|
|
166
|
-
if (batch.length === 0)
|
|
167
|
-
break;
|
|
168
|
-
}
|
|
122
|
+
if (batch.length === 0)
|
|
123
|
+
break;
|
|
169
124
|
let addedInBatch = 0;
|
|
170
125
|
for (const j of batch) {
|
|
171
126
|
if (!j.encryptJobId || seenIds.has(j.encryptJobId))
|
|
@@ -188,14 +143,11 @@ cli({
|
|
|
188
143
|
break;
|
|
189
144
|
}
|
|
190
145
|
if (addedInBatch === 0) {
|
|
191
|
-
|
|
192
|
-
if (process.env.OPENCLI_VERBOSE)
|
|
193
|
-
console.error(`[opencli:boss] API returned duplicate page, stopping pagination at ${allJobs.length} items`);
|
|
146
|
+
verbose(`API returned duplicate page, stopping pagination at ${allJobs.length} items`);
|
|
194
147
|
break;
|
|
195
148
|
}
|
|
196
|
-
if (!zpData.hasMore)
|
|
197
|
-
break;
|
|
198
|
-
}
|
|
149
|
+
if (!zpData.hasMore)
|
|
150
|
+
break;
|
|
199
151
|
currentPage++;
|
|
200
152
|
}
|
|
201
153
|
return allJobs;
|
package/dist/clis/boss/send.js
CHANGED
|
@@ -1,176 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* BOSS直聘 send message — via UI automation on chat page.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* BOSS chat uses MQTT (not HTTP) for messaging, so we must go through the UI
|
|
5
|
+
* rather than making direct API calls.
|
|
6
6
|
*/
|
|
7
7
|
import { cli, Strategy } from '../../registry.js';
|
|
8
|
+
import { requirePage, navigateToChat, findFriendByUid, clickCandidateInList, typeAndSendMessage, } from './common.js';
|
|
8
9
|
cli({
|
|
9
10
|
site: 'boss',
|
|
10
11
|
name: 'send',
|
|
11
12
|
description: 'BOSS直聘发送聊天消息',
|
|
12
13
|
domain: 'www.zhipin.com',
|
|
13
14
|
strategy: Strategy.COOKIE,
|
|
15
|
+
navigateBefore: false,
|
|
14
16
|
browser: true,
|
|
15
17
|
args: [
|
|
16
|
-
{ name: 'uid', required: true, help: 'Encrypted UID of the candidate (from chatlist)' },
|
|
18
|
+
{ name: 'uid', positional: true, required: true, help: 'Encrypted UID of the candidate (from chatlist)' },
|
|
17
19
|
{ name: 'text', required: true, positional: true, help: 'Message text to send' },
|
|
18
20
|
],
|
|
19
21
|
columns: ['status', 'detail'],
|
|
20
22
|
func: async (page, kwargs) => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
// Step 1: Navigate to chat page
|
|
26
|
-
await page.goto('https://www.zhipin.com/web/chat/index');
|
|
27
|
-
await page.wait({ time: 3 });
|
|
28
|
-
// Step 2: Find friend in list to get their numeric uid, then click
|
|
29
|
-
const friendData = await page.evaluate(`
|
|
30
|
-
async () => {
|
|
31
|
-
return new Promise((resolve, reject) => {
|
|
32
|
-
const xhr = new XMLHttpRequest();
|
|
33
|
-
xhr.open('GET', 'https://www.zhipin.com/wapi/zprelation/friend/getBossFriendListV2.json?page=1&status=0&jobId=0', true);
|
|
34
|
-
xhr.withCredentials = true;
|
|
35
|
-
xhr.timeout = 15000;
|
|
36
|
-
xhr.setRequestHeader('Accept', 'application/json');
|
|
37
|
-
xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(e); } };
|
|
38
|
-
xhr.onerror = () => reject(new Error('Network Error'));
|
|
39
|
-
xhr.send();
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
`);
|
|
43
|
-
if (friendData.code !== 0) {
|
|
44
|
-
if (friendData.code === 7 || friendData.code === 37) {
|
|
45
|
-
throw new Error('Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。');
|
|
46
|
-
}
|
|
47
|
-
throw new Error('获取好友列表失败: ' + (friendData.message || friendData.code));
|
|
48
|
-
}
|
|
49
|
-
let target = null;
|
|
50
|
-
const allFriends = friendData.zpData?.friendList || [];
|
|
51
|
-
target = allFriends.find((f) => f.encryptUid === uid);
|
|
52
|
-
if (!target) {
|
|
53
|
-
for (let p = 2; p <= 5; p++) {
|
|
54
|
-
const moreUrl = `https://www.zhipin.com/wapi/zprelation/friend/getBossFriendListV2.json?page=${p}&status=0&jobId=0`;
|
|
55
|
-
const moreData = await page.evaluate(`
|
|
56
|
-
async () => {
|
|
57
|
-
return new Promise((resolve, reject) => {
|
|
58
|
-
const xhr = new XMLHttpRequest();
|
|
59
|
-
xhr.open('GET', '${moreUrl}', true);
|
|
60
|
-
xhr.withCredentials = true;
|
|
61
|
-
xhr.timeout = 15000;
|
|
62
|
-
xhr.setRequestHeader('Accept', 'application/json');
|
|
63
|
-
xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(e); } };
|
|
64
|
-
xhr.onerror = () => reject(new Error('Network Error'));
|
|
65
|
-
xhr.send();
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
`);
|
|
69
|
-
if (moreData.code === 0) {
|
|
70
|
-
const list = moreData.zpData?.friendList || [];
|
|
71
|
-
target = list.find((f) => f.encryptUid === uid);
|
|
72
|
-
if (target)
|
|
73
|
-
break;
|
|
74
|
-
if (list.length === 0)
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
if (!target)
|
|
23
|
+
requirePage(page);
|
|
24
|
+
await navigateToChat(page, 3);
|
|
25
|
+
const friend = await findFriendByUid(page, kwargs.uid, { maxPages: 5 });
|
|
26
|
+
if (!friend)
|
|
80
27
|
throw new Error('未找到该候选人,请确认 uid 是否正确');
|
|
81
|
-
const numericUid =
|
|
82
|
-
const friendName =
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
async () => {
|
|
86
|
-
// The geek-item has id like _748787762-0
|
|
87
|
-
const item = document.querySelector('#_${numericUid}-0') || document.querySelector('[id^="_${numericUid}"]');
|
|
88
|
-
if (item) {
|
|
89
|
-
item.click();
|
|
90
|
-
return { clicked: true, id: item.id };
|
|
91
|
-
}
|
|
92
|
-
// Fallback: try clicking by iterating geek items
|
|
93
|
-
const items = document.querySelectorAll('.geek-item');
|
|
94
|
-
for (const el of items) {
|
|
95
|
-
if (el.id && el.id.startsWith('_${numericUid}')) {
|
|
96
|
-
el.click();
|
|
97
|
-
return { clicked: true, id: el.id };
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return { clicked: false };
|
|
101
|
-
}
|
|
102
|
-
`);
|
|
103
|
-
if (!clicked.clicked) {
|
|
28
|
+
const numericUid = friend.uid;
|
|
29
|
+
const friendName = friend.name || '候选人';
|
|
30
|
+
const clicked = await clickCandidateInList(page, numericUid);
|
|
31
|
+
if (!clicked) {
|
|
104
32
|
throw new Error('无法在聊天列表中找到该用户,请确认聊天列表中有此人');
|
|
105
33
|
}
|
|
106
|
-
// Step 4: Wait for the conversation to load and input area to appear
|
|
107
34
|
await page.wait({ time: 2 });
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// Look for the chat editor - BOSS uses contenteditable div or textarea
|
|
112
|
-
const selectors = [
|
|
113
|
-
'.chat-editor [contenteditable="true"]',
|
|
114
|
-
'.chat-input [contenteditable="true"]',
|
|
115
|
-
'.message-editor [contenteditable="true"]',
|
|
116
|
-
'.chat-conversation [contenteditable="true"]',
|
|
117
|
-
'[contenteditable="true"]',
|
|
118
|
-
'.chat-editor textarea',
|
|
119
|
-
'.chat-input textarea',
|
|
120
|
-
'textarea',
|
|
121
|
-
];
|
|
122
|
-
|
|
123
|
-
for (const sel of selectors) {
|
|
124
|
-
const el = document.querySelector(sel);
|
|
125
|
-
if (el && el.offsetParent !== null) {
|
|
126
|
-
el.focus();
|
|
127
|
-
|
|
128
|
-
if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
|
|
129
|
-
el.value = ${JSON.stringify(text)};
|
|
130
|
-
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
131
|
-
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
132
|
-
} else {
|
|
133
|
-
// contenteditable
|
|
134
|
-
el.textContent = '';
|
|
135
|
-
el.focus();
|
|
136
|
-
document.execCommand('insertText', false, ${JSON.stringify(text)});
|
|
137
|
-
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return { found: true, selector: sel, tag: el.tagName };
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Debug: list all visible elements in chat-conversation
|
|
145
|
-
const conv = document.querySelector('.chat-conversation');
|
|
146
|
-
const allEls = conv ? Array.from(conv.querySelectorAll('*')).filter(e => e.offsetParent !== null).map(e => e.tagName + '.' + (e.className?.substring?.(0, 50) || '')).slice(0, 30) : [];
|
|
147
|
-
|
|
148
|
-
return { found: false, visibleElements: allEls };
|
|
149
|
-
}
|
|
150
|
-
`);
|
|
151
|
-
if (!typed.found) {
|
|
152
|
-
throw new Error('找不到消息输入框。可能的元素: ' + JSON.stringify(typed.visibleElements || []));
|
|
153
|
-
}
|
|
154
|
-
await page.wait({ time: 0.5 });
|
|
155
|
-
// Step 6: Click the send button (Enter key doesn't trigger send on BOSS)
|
|
156
|
-
const sent = await page.evaluate(`
|
|
157
|
-
async () => {
|
|
158
|
-
// The send button is .submit inside .submit-content
|
|
159
|
-
const btn = document.querySelector('.conversation-editor .submit')
|
|
160
|
-
|| document.querySelector('.submit-content .submit')
|
|
161
|
-
|| document.querySelector('.conversation-operate .submit');
|
|
162
|
-
if (btn) {
|
|
163
|
-
btn.click();
|
|
164
|
-
return { clicked: true };
|
|
165
|
-
}
|
|
166
|
-
return { clicked: false };
|
|
167
|
-
}
|
|
168
|
-
`);
|
|
169
|
-
if (!sent.clicked) {
|
|
170
|
-
// Fallback: try Enter key
|
|
171
|
-
await page.pressKey('Enter');
|
|
35
|
+
const sent = await typeAndSendMessage(page, kwargs.text);
|
|
36
|
+
if (!sent) {
|
|
37
|
+
throw new Error('找不到消息输入框');
|
|
172
38
|
}
|
|
173
39
|
await page.wait({ time: 1 });
|
|
174
|
-
return [{ status: '✅ 发送成功', detail: `已向 ${friendName} 发送: ${text}` }];
|
|
40
|
+
return [{ status: '✅ 发送成功', detail: `已向 ${friendName} 发送: ${kwargs.text}` }];
|
|
175
41
|
},
|
|
176
42
|
});
|
package/dist/clis/boss/stats.js
CHANGED
|
@@ -1,92 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* BOSS直聘 stats — job statistics overview.
|
|
3
|
-
*
|
|
4
|
-
* Uses /wapi/zpchat/chatHelper/statistics for total friend count,
|
|
5
|
-
* and /wapi/zpjob/job/chatted/jobList for per-job info.
|
|
6
|
-
* Since BOSS doesn't expose detailed per-job stats via API,
|
|
7
|
-
* we show what's available: job status, chat info, and total stats.
|
|
8
3
|
*/
|
|
9
4
|
import { cli, Strategy } from '../../registry.js';
|
|
5
|
+
import { requirePage, navigateToChat, bossFetch, fetchFriendList, verbose } from './common.js';
|
|
10
6
|
cli({
|
|
11
7
|
site: 'boss',
|
|
12
8
|
name: 'stats',
|
|
13
9
|
description: 'BOSS直聘职位数据统计',
|
|
14
10
|
domain: 'www.zhipin.com',
|
|
15
11
|
strategy: Strategy.COOKIE,
|
|
12
|
+
navigateBefore: false,
|
|
16
13
|
browser: true,
|
|
17
14
|
args: [
|
|
18
15
|
{ name: 'job-id', default: '', help: 'Encrypted job ID (show all if empty)' },
|
|
19
16
|
],
|
|
20
17
|
columns: ['job_name', 'salary', 'city', 'status', 'total_chats', 'encrypt_job_id'],
|
|
21
18
|
func: async (page, kwargs) => {
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
requirePage(page);
|
|
20
|
+
verbose('Fetching job statistics...');
|
|
24
21
|
const filterJobId = kwargs['job-id'] || '';
|
|
25
|
-
|
|
26
|
-
console.error('[opencli:boss] Fetching job statistics...');
|
|
27
|
-
}
|
|
28
|
-
await page.goto('https://www.zhipin.com/web/chat/index');
|
|
29
|
-
await page.wait({ time: 2 });
|
|
22
|
+
await navigateToChat(page);
|
|
30
23
|
// Get job list
|
|
31
|
-
const jobData = await page.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
xhr.open('GET', 'https://www.zhipin.com/wapi/zpjob/job/chatted/jobList', true);
|
|
36
|
-
xhr.withCredentials = true;
|
|
37
|
-
xhr.timeout = 15000;
|
|
38
|
-
xhr.setRequestHeader('Accept', 'application/json');
|
|
39
|
-
xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(new Error('JSON parse failed')); } };
|
|
40
|
-
xhr.onerror = () => reject(new Error('Network Error'));
|
|
41
|
-
xhr.ontimeout = () => reject(new Error('Timeout'));
|
|
42
|
-
xhr.send();
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
`);
|
|
46
|
-
if (jobData.code !== 0) {
|
|
47
|
-
if (jobData.code === 7 || jobData.code === 37) {
|
|
48
|
-
throw new Error('Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。');
|
|
49
|
-
}
|
|
50
|
-
throw new Error(`API error: ${jobData.message} (code=${jobData.code})`);
|
|
51
|
-
}
|
|
52
|
-
// Get total chat stats
|
|
53
|
-
const chatStats = await page.evaluate(`
|
|
54
|
-
async () => {
|
|
55
|
-
return new Promise((resolve, reject) => {
|
|
56
|
-
const xhr = new XMLHttpRequest();
|
|
57
|
-
xhr.open('GET', 'https://www.zhipin.com/wapi/zpchat/chatHelper/statistics', true);
|
|
58
|
-
xhr.withCredentials = true;
|
|
59
|
-
xhr.timeout = 10000;
|
|
60
|
-
xhr.setRequestHeader('Accept', 'application/json');
|
|
61
|
-
xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { resolve({}); } };
|
|
62
|
-
xhr.onerror = () => resolve({});
|
|
63
|
-
xhr.send();
|
|
24
|
+
const jobData = await bossFetch(page, 'https://www.zhipin.com/wapi/zpjob/job/chatted/jobList');
|
|
25
|
+
// Get total chat stats (non-critical, allow failure)
|
|
26
|
+
const chatStats = await bossFetch(page, 'https://www.zhipin.com/wapi/zpchat/chatHelper/statistics', {
|
|
27
|
+
allowNonZero: true,
|
|
64
28
|
});
|
|
65
|
-
}
|
|
66
|
-
`);
|
|
67
29
|
const totalFriends = chatStats.zpData?.totalFriendCount || 0;
|
|
68
|
-
// Get per-job chat counts from friend list
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
xhr.withCredentials = true;
|
|
75
|
-
xhr.timeout = 15000;
|
|
76
|
-
xhr.setRequestHeader('Accept', 'application/json');
|
|
77
|
-
xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { resolve({}); } };
|
|
78
|
-
xhr.onerror = () => resolve({});
|
|
79
|
-
xhr.send();
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
`);
|
|
83
|
-
// Count chats per job
|
|
30
|
+
// Get per-job chat counts from friend list (non-critical)
|
|
31
|
+
let friendList = [];
|
|
32
|
+
try {
|
|
33
|
+
friendList = await fetchFriendList(page);
|
|
34
|
+
}
|
|
35
|
+
catch { /* ignore */ }
|
|
84
36
|
const jobChatCounts = {};
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
jobChatCounts[jobName] = (jobChatCounts[jobName] || 0) + 1;
|
|
89
|
-
}
|
|
37
|
+
for (const f of friendList) {
|
|
38
|
+
const jobName = f.jobName || 'unknown';
|
|
39
|
+
jobChatCounts[jobName] = (jobChatCounts[jobName] || 0) + 1;
|
|
90
40
|
}
|
|
91
41
|
let jobs = jobData.zpData || [];
|
|
92
42
|
if (filterJobId) {
|
|
@@ -100,7 +50,6 @@ cli({
|
|
|
100
50
|
total_chats: String(jobChatCounts[j.jobName] || 0),
|
|
101
51
|
encrypt_job_id: j.encryptJobId || '',
|
|
102
52
|
}));
|
|
103
|
-
// Add summary row
|
|
104
53
|
if (!filterJobId && results.length > 0) {
|
|
105
54
|
results.push({
|
|
106
55
|
job_name: '--- 总计 ---',
|