@jackwener/opencli 1.1.0 → 1.1.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/.agents/skills/cross-project-adapter-migration/SKILL.md +2 -2
- package/.github/pull_request_template.md +7 -0
- package/.github/workflows/doc-check.yml +36 -0
- package/.github/workflows/docs.yml +7 -42
- package/CHANGELOG.md +23 -0
- package/CLI-EXPLORER.md +9 -8
- package/README.md +25 -10
- package/README.zh-CN.md +26 -11
- package/SKILL.md +95 -31
- package/dist/browser/cdp.js +6 -1
- package/dist/browser/page.d.ts +4 -1
- package/dist/browser/page.js +7 -1
- package/dist/build-manifest.js +23 -16
- package/dist/cli-manifest.json +431 -276
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +189 -162
- package/dist/clis/apple-podcasts/commands.test.d.ts +2 -0
- package/dist/clis/apple-podcasts/commands.test.js +76 -0
- package/dist/clis/apple-podcasts/search.js +2 -2
- package/dist/clis/apple-podcasts/top.js +9 -2
- package/dist/clis/arxiv/search.js +1 -1
- package/dist/clis/bilibili/dynamic.js +1 -1
- package/dist/clis/bilibili/favorite.js +1 -1
- package/dist/clis/bilibili/feed.js +1 -1
- package/dist/clis/bilibili/following.js +1 -1
- package/dist/clis/bilibili/history.js +1 -1
- package/dist/clis/bilibili/me.js +1 -1
- package/dist/clis/bilibili/ranking.js +1 -1
- package/dist/clis/bilibili/search.js +3 -3
- package/dist/clis/bilibili/subtitle.js +1 -1
- package/dist/clis/bilibili/user-videos.js +1 -1
- package/dist/{bilibili.d.ts → clis/bilibili/utils.d.ts} +1 -1
- package/dist/clis/bloomberg/businessweek.js +17 -0
- package/dist/clis/bloomberg/economics.js +17 -0
- package/dist/clis/bloomberg/feeds.d.ts +1 -0
- package/dist/clis/bloomberg/feeds.js +15 -0
- package/dist/clis/bloomberg/industries.d.ts +1 -0
- package/dist/clis/bloomberg/industries.js +17 -0
- package/dist/clis/bloomberg/main.d.ts +1 -0
- package/dist/clis/bloomberg/main.js +17 -0
- package/dist/clis/bloomberg/markets.d.ts +1 -0
- package/dist/clis/bloomberg/markets.js +17 -0
- package/dist/clis/bloomberg/news.d.ts +1 -0
- package/dist/clis/bloomberg/news.js +105 -0
- package/dist/clis/bloomberg/opinions.d.ts +1 -0
- package/dist/clis/bloomberg/opinions.js +17 -0
- package/dist/clis/bloomberg/politics.d.ts +1 -0
- package/dist/clis/bloomberg/politics.js +17 -0
- package/dist/clis/bloomberg/tech.d.ts +1 -0
- package/dist/clis/bloomberg/tech.js +17 -0
- package/dist/clis/bloomberg/utils.d.ts +34 -0
- package/dist/clis/bloomberg/utils.js +364 -0
- package/dist/clis/bloomberg/utils.test.d.ts +1 -0
- package/dist/clis/bloomberg/utils.test.js +129 -0
- package/dist/clis/boss/batchgreet.js +2 -2
- package/dist/clis/boss/chatlist.js +2 -2
- package/dist/clis/boss/detail.js +2 -2
- package/dist/clis/boss/greet.js +4 -4
- package/dist/clis/boss/search.js +1 -1
- package/dist/clis/boss/send.js +1 -1
- package/dist/clis/boss/stats.js +2 -2
- package/dist/clis/chaoxing/assignments.js +1 -1
- package/dist/clis/chaoxing/exams.js +1 -1
- package/dist/{chaoxing.d.ts → clis/chaoxing/utils.d.ts} +1 -1
- package/dist/{chaoxing.js → clis/chaoxing/utils.js} +0 -2
- package/dist/clis/chaoxing/utils.test.d.ts +1 -0
- package/dist/{chaoxing.test.js → clis/chaoxing/utils.test.js} +1 -1
- package/dist/clis/chatgpt/read.js +1 -1
- package/dist/clis/chatwise/export.js +1 -1
- package/dist/clis/chatwise/model.js +2 -2
- package/dist/clis/chatwise/screenshot.js +1 -1
- package/dist/clis/codex/export.js +1 -1
- package/dist/clis/codex/model.js +2 -2
- package/dist/clis/codex/screenshot.js +1 -1
- package/dist/clis/coupang/add-to-cart.js +3 -4
- package/dist/clis/coupang/search.js +2 -4
- package/dist/clis/coupang/utils.test.d.ts +1 -0
- package/dist/{coupang.test.js → clis/coupang/utils.test.js} +1 -1
- package/dist/clis/ctrip/search.js +1 -1
- package/dist/clis/cursor/export.js +1 -1
- package/dist/clis/cursor/model.js +2 -2
- package/dist/clis/cursor/screenshot.js +1 -1
- package/dist/clis/jike/comment.js +2 -3
- package/dist/clis/jike/create.js +1 -2
- package/dist/clis/jike/feed.js +0 -1
- package/dist/clis/jike/like.js +1 -2
- package/dist/clis/jike/notifications.js +0 -1
- package/dist/clis/jike/post.yaml +1 -0
- package/dist/clis/jike/repost.js +1 -2
- package/dist/clis/jike/search.js +2 -3
- package/dist/clis/jike/topic.yaml +1 -0
- package/dist/clis/jike/user.yaml +1 -0
- package/dist/clis/jimeng/history.yaml +0 -1
- package/dist/clis/linkedin/search.js +7 -7
- package/dist/clis/linux-do/category.yaml +1 -0
- package/dist/clis/linux-do/search.yaml +4 -3
- package/dist/clis/linux-do/topic.yaml +1 -0
- package/dist/clis/notion/export.js +1 -1
- package/dist/clis/reddit/comment.js +3 -4
- package/dist/clis/reddit/read.js +4 -5
- package/dist/clis/reddit/save.js +2 -3
- package/dist/clis/reddit/saved.js +0 -1
- package/dist/clis/reddit/search.yaml +1 -0
- package/dist/clis/reddit/subscribe.js +0 -1
- package/dist/clis/reddit/upvote.js +2 -3
- package/dist/clis/reddit/upvoted.js +0 -1
- package/dist/clis/reddit/user-comments.yaml +1 -0
- package/dist/clis/reddit/user-posts.yaml +1 -0
- package/dist/clis/reddit/user.yaml +1 -0
- package/dist/clis/reuters/search.js +1 -1
- package/dist/clis/smzdm/search.js +2 -3
- package/dist/clis/stackoverflow/search.yaml +1 -0
- package/dist/clis/steam/top-sellers.yaml +29 -0
- package/dist/clis/twitter/accept.js +2 -2
- package/dist/clis/twitter/article.js +2 -2
- package/dist/clis/twitter/block.d.ts +1 -0
- package/dist/clis/twitter/block.js +88 -0
- package/dist/clis/twitter/delete.js +1 -1
- package/dist/clis/twitter/hide-reply.d.ts +1 -0
- package/dist/clis/twitter/hide-reply.js +66 -0
- package/dist/clis/twitter/like.js +1 -1
- package/dist/clis/twitter/post.js +1 -1
- package/dist/clis/twitter/reply-dm.js +1 -1
- package/dist/clis/twitter/reply.js +2 -2
- package/dist/clis/twitter/search.js +1 -1
- package/dist/clis/twitter/thread.js +2 -2
- package/dist/clis/twitter/trending.d.ts +1 -0
- package/dist/clis/twitter/trending.js +91 -0
- package/dist/clis/twitter/unblock.d.ts +1 -0
- package/dist/clis/twitter/unblock.js +71 -0
- package/dist/clis/v2ex/topic.yaml +1 -0
- package/dist/clis/weibo/hot.js +0 -1
- package/dist/clis/weread/book.js +1 -1
- package/dist/clis/weread/highlights.js +1 -1
- package/dist/clis/weread/notes.js +1 -1
- package/dist/clis/weread/search.js +1 -1
- package/dist/clis/wikipedia/search.js +1 -1
- package/dist/clis/xiaohongshu/creator-note-detail.d.ts +15 -0
- package/dist/clis/xiaohongshu/creator-note-detail.js +69 -5
- package/dist/clis/xiaohongshu/creator-note-detail.test.js +80 -33
- package/dist/clis/xiaohongshu/creator-notes.js +35 -5
- package/dist/clis/xiaohongshu/creator-notes.test.js +35 -6
- package/dist/clis/xiaohongshu/creator-profile.js +0 -1
- package/dist/clis/xiaohongshu/creator-stats.js +0 -1
- package/dist/clis/xiaohongshu/download.js +2 -3
- package/dist/clis/xiaohongshu/feed.yaml +0 -1
- package/dist/clis/xiaohongshu/notifications.yaml +0 -1
- package/dist/clis/xiaohongshu/search.js +2 -2
- package/dist/clis/xiaohongshu/user.js +1 -2
- package/dist/clis/yahoo-finance/quote.js +0 -1
- package/dist/clis/youtube/search.js +1 -1
- package/dist/clis/youtube/transcript.js +1 -1
- package/dist/clis/youtube/video.js +1 -1
- package/dist/clis/zhihu/download.js +1 -2
- package/dist/clis/zhihu/question.js +1 -1
- package/dist/clis/zhihu/search.yaml +4 -3
- package/dist/commanderAdapter.d.ts +21 -0
- package/dist/commanderAdapter.js +111 -0
- package/dist/{engine.d.ts → discovery.d.ts} +0 -6
- package/dist/{engine.js → discovery.js} +1 -98
- package/dist/download/index.d.ts +2 -6
- package/dist/download/index.js +19 -46
- package/dist/engine.test.d.ts +1 -1
- package/dist/engine.test.js +8 -7
- package/dist/execution.d.ts +22 -0
- package/dist/execution.js +129 -0
- package/dist/explore.js +121 -107
- package/dist/external-clis.yaml +48 -0
- package/dist/external.d.ts +7 -2
- package/dist/external.js +11 -14
- package/dist/main.js +1 -1
- package/dist/pipeline/steps/browser.js +8 -2
- package/dist/registry.d.ts +2 -0
- package/dist/registry.js +2 -0
- package/dist/runtime.d.ts +5 -0
- package/dist/runtime.js +8 -0
- package/dist/serialization.d.ts +34 -0
- package/dist/serialization.js +63 -0
- package/dist/types.d.ts +4 -1
- package/docs/.vitepress/config.mts +14 -3
- package/docs/adapters/browser/arxiv.md +27 -0
- package/docs/adapters/browser/barchart.md +32 -0
- package/docs/adapters/browser/bloomberg.md +70 -0
- package/docs/adapters/browser/chaoxing.md +39 -0
- package/docs/adapters/browser/grok.md +35 -0
- package/docs/adapters/browser/hf.md +42 -0
- package/docs/adapters/browser/jike.md +45 -0
- package/docs/adapters/browser/jimeng.md +39 -0
- package/docs/adapters/browser/linux-do.md +45 -0
- package/docs/adapters/browser/sinafinance.md +35 -0
- package/docs/adapters/browser/stackoverflow.md +35 -0
- package/docs/adapters/browser/steam.md +26 -0
- package/docs/adapters/browser/twitter.md +3 -0
- package/docs/adapters/browser/weread.md +48 -0
- package/docs/adapters/browser/wikipedia.md +30 -0
- package/docs/adapters/browser/xiaohongshu.md +5 -1
- package/docs/adapters/desktop/chatgpt.md +3 -3
- package/docs/adapters/index.md +13 -0
- package/docs/advanced/download.md +4 -4
- package/docs/developer/architecture.md +17 -4
- package/package.json +1 -1
- package/scripts/check-doc-coverage.sh +69 -0
- package/scripts/copy-yaml.cjs +7 -0
- package/src/browser/cdp.ts +6 -1
- package/src/browser/page.ts +7 -1
- package/src/build-manifest.ts +25 -19
- package/src/cli.ts +218 -139
- package/src/clis/apple-podcasts/commands.test.ts +95 -0
- package/src/clis/apple-podcasts/search.ts +2 -2
- package/src/clis/apple-podcasts/top.ts +12 -2
- package/src/clis/arxiv/search.ts +1 -1
- package/src/clis/bilibili/dynamic.ts +1 -1
- package/src/clis/bilibili/favorite.ts +1 -1
- package/src/clis/bilibili/feed.ts +1 -1
- package/src/clis/bilibili/following.ts +1 -1
- package/src/clis/bilibili/history.ts +1 -1
- package/src/clis/bilibili/me.ts +1 -1
- package/src/clis/bilibili/ranking.ts +1 -1
- package/src/clis/bilibili/search.ts +3 -3
- package/src/clis/bilibili/subtitle.ts +1 -1
- package/src/clis/bilibili/user-videos.ts +1 -1
- package/src/{bilibili.ts → clis/bilibili/utils.ts} +1 -1
- package/src/clis/bloomberg/businessweek.ts +18 -0
- package/src/clis/bloomberg/economics.ts +18 -0
- package/src/clis/bloomberg/feeds.ts +16 -0
- package/src/clis/bloomberg/industries.ts +18 -0
- package/src/clis/bloomberg/main.ts +18 -0
- package/src/clis/bloomberg/markets.ts +18 -0
- package/src/clis/bloomberg/news.ts +136 -0
- package/src/clis/bloomberg/opinions.ts +18 -0
- package/src/clis/bloomberg/politics.ts +18 -0
- package/src/clis/bloomberg/tech.ts +18 -0
- package/src/clis/bloomberg/utils.test.ts +135 -0
- package/src/clis/bloomberg/utils.ts +429 -0
- package/src/clis/boss/batchgreet.ts +2 -2
- package/src/clis/boss/chatlist.ts +2 -2
- package/src/clis/boss/detail.ts +2 -2
- package/src/clis/boss/greet.ts +4 -4
- package/src/clis/boss/search.ts +1 -1
- package/src/clis/boss/send.ts +1 -1
- package/src/clis/boss/stats.ts +2 -2
- package/src/clis/chaoxing/assignments.ts +1 -1
- package/src/clis/chaoxing/exams.ts +1 -1
- package/src/{chaoxing.test.ts → clis/chaoxing/utils.test.ts} +1 -1
- package/src/{chaoxing.ts → clis/chaoxing/utils.ts} +1 -3
- package/src/clis/chatgpt/README.zh-CN.md +3 -3
- package/src/clis/chatgpt/read.ts +1 -1
- package/src/clis/chatwise/export.ts +1 -1
- package/src/clis/chatwise/model.ts +2 -2
- package/src/clis/chatwise/screenshot.ts +1 -1
- package/src/clis/codex/export.ts +1 -1
- package/src/clis/codex/model.ts +2 -2
- package/src/clis/codex/screenshot.ts +1 -1
- package/src/clis/coupang/add-to-cart.ts +3 -4
- package/src/clis/coupang/search.ts +2 -4
- package/src/{coupang.test.ts → clis/coupang/utils.test.ts} +1 -1
- package/src/clis/ctrip/search.ts +1 -1
- package/src/clis/cursor/export.ts +1 -1
- package/src/clis/cursor/model.ts +2 -2
- package/src/clis/cursor/screenshot.ts +1 -1
- package/src/clis/jike/comment.ts +2 -3
- package/src/clis/jike/create.ts +1 -2
- package/src/clis/jike/feed.ts +0 -1
- package/src/clis/jike/like.ts +1 -2
- package/src/clis/jike/notifications.ts +0 -1
- package/src/clis/jike/post.yaml +1 -0
- package/src/clis/jike/repost.ts +1 -2
- package/src/clis/jike/search.ts +2 -3
- package/src/clis/jike/topic.yaml +1 -0
- package/src/clis/jike/user.yaml +1 -0
- package/src/clis/jimeng/history.yaml +0 -1
- package/src/clis/linkedin/search.ts +7 -7
- package/src/clis/linux-do/category.yaml +1 -0
- package/src/clis/linux-do/search.yaml +4 -3
- package/src/clis/linux-do/topic.yaml +1 -0
- package/src/clis/notion/export.ts +1 -1
- package/src/clis/reddit/comment.ts +3 -4
- package/src/clis/reddit/read.ts +4 -5
- package/src/clis/reddit/save.ts +2 -3
- package/src/clis/reddit/saved.ts +0 -1
- package/src/clis/reddit/search.yaml +1 -0
- package/src/clis/reddit/subscribe.ts +0 -1
- package/src/clis/reddit/upvote.ts +2 -3
- package/src/clis/reddit/upvoted.ts +0 -1
- package/src/clis/reddit/user-comments.yaml +1 -0
- package/src/clis/reddit/user-posts.yaml +1 -0
- package/src/clis/reddit/user.yaml +1 -0
- package/src/clis/reuters/search.ts +1 -1
- package/src/clis/smzdm/search.ts +2 -3
- package/src/clis/stackoverflow/search.yaml +1 -0
- package/src/clis/steam/top-sellers.yaml +29 -0
- package/src/clis/twitter/accept.ts +2 -2
- package/src/clis/twitter/article.ts +2 -2
- package/src/clis/twitter/block.ts +92 -0
- package/src/clis/twitter/delete.ts +1 -1
- package/src/clis/twitter/hide-reply.ts +70 -0
- package/src/clis/twitter/like.ts +1 -1
- package/src/clis/twitter/post.ts +1 -1
- package/src/clis/twitter/reply-dm.ts +1 -1
- package/src/clis/twitter/reply.ts +2 -2
- package/src/clis/twitter/search.ts +1 -1
- package/src/clis/twitter/thread.ts +2 -2
- package/src/clis/twitter/trending.ts +113 -0
- package/src/clis/twitter/unblock.ts +75 -0
- package/src/clis/v2ex/topic.yaml +1 -0
- package/src/clis/weibo/hot.ts +0 -1
- package/src/clis/weread/book.ts +1 -1
- package/src/clis/weread/highlights.ts +1 -1
- package/src/clis/weread/notes.ts +1 -1
- package/src/clis/weread/search.ts +1 -1
- package/src/clis/wikipedia/search.ts +1 -1
- package/src/clis/xiaohongshu/creator-note-detail.test.ts +82 -33
- package/src/clis/xiaohongshu/creator-note-detail.ts +89 -5
- package/src/clis/xiaohongshu/creator-notes.test.ts +39 -6
- package/src/clis/xiaohongshu/creator-notes.ts +44 -5
- package/src/clis/xiaohongshu/creator-profile.ts +0 -1
- package/src/clis/xiaohongshu/creator-stats.ts +0 -1
- package/src/clis/xiaohongshu/download.ts +2 -3
- package/src/clis/xiaohongshu/feed.yaml +0 -1
- package/src/clis/xiaohongshu/notifications.yaml +0 -1
- package/src/clis/xiaohongshu/search.ts +2 -2
- package/src/clis/xiaohongshu/user.ts +1 -2
- package/src/clis/yahoo-finance/quote.ts +0 -1
- package/src/clis/youtube/search.ts +1 -1
- package/src/clis/youtube/transcript.ts +1 -1
- package/src/clis/youtube/video.ts +1 -1
- package/src/clis/zhihu/download.ts +1 -2
- package/src/clis/zhihu/question.ts +1 -1
- package/src/clis/zhihu/search.yaml +4 -3
- package/src/commanderAdapter.ts +113 -0
- package/src/{engine.ts → discovery.ts} +1 -108
- package/src/download/index.ts +21 -54
- package/src/engine.test.ts +8 -7
- package/src/execution.ts +138 -0
- package/src/explore.ts +135 -109
- package/src/external-clis.yaml +9 -0
- package/src/external.ts +15 -12
- package/src/main.ts +1 -1
- package/src/pipeline/steps/browser.ts +7 -2
- package/src/registry.ts +5 -0
- package/src/runtime.ts +9 -0
- package/src/serialization.ts +79 -0
- package/src/types.ts +1 -1
- package/tests/e2e/browser-public.test.ts +25 -0
- package/tests/e2e/public-commands.test.ts +55 -1
- package/dist/clis/twitter/trending.yaml +0 -46
- package/docs/public/CNAME +0 -1
- package/src/clis/twitter/trending.yaml +0 -46
- /package/dist/{bilibili.js → clis/bilibili/utils.js} +0 -0
- /package/dist/{chaoxing.test.d.ts → clis/bloomberg/businessweek.d.ts} +0 -0
- /package/dist/{coupang.test.d.ts → clis/bloomberg/economics.d.ts} +0 -0
- /package/dist/{coupang.d.ts → clis/coupang/utils.d.ts} +0 -0
- /package/dist/{coupang.js → clis/coupang/utils.js} +0 -0
- /package/src/{coupang.ts → clis/coupang/utils.ts} +0 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { CliError } from '../../errors.js';
|
|
2
|
+
export const BLOOMBERG_FEEDS = {
|
|
3
|
+
main: 'https://feeds.bloomberg.com/news.rss',
|
|
4
|
+
markets: 'https://feeds.bloomberg.com/markets/news.rss',
|
|
5
|
+
economics: 'https://feeds.bloomberg.com/economics/news.rss',
|
|
6
|
+
industries: 'https://feeds.bloomberg.com/industries/news.rss',
|
|
7
|
+
tech: 'https://feeds.bloomberg.com/technology/news.rss',
|
|
8
|
+
politics: 'https://feeds.bloomberg.com/politics/news.rss',
|
|
9
|
+
businessweek: 'https://feeds.bloomberg.com/businessweek/news.rss',
|
|
10
|
+
opinions: 'https://feeds.bloomberg.com/bview/news.rss',
|
|
11
|
+
};
|
|
12
|
+
const DEFAULT_USER_AGENT = 'Mozilla/5.0 (compatible; opencli)';
|
|
13
|
+
export async function fetchBloombergFeed(name, limit = 1) {
|
|
14
|
+
const feedUrl = BLOOMBERG_FEEDS[name];
|
|
15
|
+
if (!feedUrl) {
|
|
16
|
+
throw new CliError('ARGUMENT', `Unknown Bloomberg feed: ${name}`);
|
|
17
|
+
}
|
|
18
|
+
const resp = await fetch(feedUrl, {
|
|
19
|
+
headers: { 'User-Agent': DEFAULT_USER_AGENT },
|
|
20
|
+
});
|
|
21
|
+
if (!resp.ok) {
|
|
22
|
+
throw new CliError('FETCH_ERROR', `Bloomberg RSS HTTP ${resp.status}`, 'Bloomberg may be temporarily unavailable; try again later.');
|
|
23
|
+
}
|
|
24
|
+
const xml = await resp.text();
|
|
25
|
+
const items = parseBloombergRss(xml);
|
|
26
|
+
if (!items.length) {
|
|
27
|
+
throw new CliError('NOT_FOUND', 'Bloomberg RSS feed returned no items', 'Bloomberg may have changed the feed format.');
|
|
28
|
+
}
|
|
29
|
+
const count = Math.max(1, Math.min(Number(limit) || 1, 20));
|
|
30
|
+
return items.slice(0, count);
|
|
31
|
+
}
|
|
32
|
+
export function parseBloombergRss(xml) {
|
|
33
|
+
const items = [];
|
|
34
|
+
const itemRegex = /<item\b[^>]*>([\s\S]*?)<\/item>/gi;
|
|
35
|
+
let match;
|
|
36
|
+
while ((match = itemRegex.exec(xml))) {
|
|
37
|
+
const block = match[1];
|
|
38
|
+
const title = extractTagText(block, 'title');
|
|
39
|
+
const summary = extractTagText(block, 'description');
|
|
40
|
+
const link = extractTagText(block, 'link') || extractTagText(block, 'guid');
|
|
41
|
+
const mediaLinks = extractMediaLinksFromRssItem(block);
|
|
42
|
+
if (!title || !link)
|
|
43
|
+
continue;
|
|
44
|
+
items.push({
|
|
45
|
+
title,
|
|
46
|
+
summary,
|
|
47
|
+
link,
|
|
48
|
+
mediaLinks,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return items;
|
|
52
|
+
}
|
|
53
|
+
export function normalizeBloombergLink(input) {
|
|
54
|
+
const raw = String(input || '').trim();
|
|
55
|
+
if (!raw) {
|
|
56
|
+
throw new CliError('ARGUMENT', 'A Bloomberg link is required');
|
|
57
|
+
}
|
|
58
|
+
if (raw.startsWith('/'))
|
|
59
|
+
return `https://www.bloomberg.com${raw}`;
|
|
60
|
+
return raw;
|
|
61
|
+
}
|
|
62
|
+
export function validateBloombergLink(input) {
|
|
63
|
+
const normalized = normalizeBloombergLink(input);
|
|
64
|
+
let url;
|
|
65
|
+
try {
|
|
66
|
+
url = new URL(normalized);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
throw new CliError('ARGUMENT', `Invalid Bloomberg link: ${input}`, 'Pass a full https://www.bloomberg.com/... URL or a relative Bloomberg path.');
|
|
70
|
+
}
|
|
71
|
+
if (!/(?:\.|^)bloomberg\.com$/i.test(url.hostname)) {
|
|
72
|
+
throw new CliError('ARGUMENT', `Expected a bloomberg.com link, got: ${url.hostname}`, 'Pass a Bloomberg article URL from bloomberg.com.');
|
|
73
|
+
}
|
|
74
|
+
return url.toString();
|
|
75
|
+
}
|
|
76
|
+
export function renderStoryBody(body) {
|
|
77
|
+
const blocks = Array.isArray(body?.content) ? body.content : [];
|
|
78
|
+
const parts = blocks
|
|
79
|
+
.map((block) => renderBlock(block, 0))
|
|
80
|
+
.map((part) => normalizeBlockText(part))
|
|
81
|
+
.filter(Boolean);
|
|
82
|
+
return parts.join('\n\n').replace(/\n{3,}/g, '\n\n').trim();
|
|
83
|
+
}
|
|
84
|
+
export function extractStoryMediaLinks(story) {
|
|
85
|
+
const urls = new Set();
|
|
86
|
+
collectMediaUrls(story?.ledeImageUrl, urls);
|
|
87
|
+
collectMediaUrls(story?.socialImageUrl, urls);
|
|
88
|
+
collectMediaUrls(story?.lede, urls);
|
|
89
|
+
collectMediaUrls(story?.imageAttachments, urls);
|
|
90
|
+
collectMediaUrls(story?.videoAttachments, urls);
|
|
91
|
+
const mediaBlocks = Array.isArray(story?.body?.content)
|
|
92
|
+
? story.body.content.filter((block) => block?.type === 'media')
|
|
93
|
+
: [];
|
|
94
|
+
collectMediaUrls(mediaBlocks, urls);
|
|
95
|
+
return [...urls];
|
|
96
|
+
}
|
|
97
|
+
function renderBlock(block, depth) {
|
|
98
|
+
if (!block || typeof block !== 'object')
|
|
99
|
+
return '';
|
|
100
|
+
switch (block.type) {
|
|
101
|
+
case 'paragraph':
|
|
102
|
+
return renderInlineNodes(block.content || []);
|
|
103
|
+
case 'heading': {
|
|
104
|
+
const text = renderInlineNodes(block.content || []);
|
|
105
|
+
if (!text)
|
|
106
|
+
return '';
|
|
107
|
+
const level = Number(block.data?.level ?? block.data?.weight ?? 2);
|
|
108
|
+
const prefix = level <= 1 ? '# ' : level === 2 ? '## ' : '### ';
|
|
109
|
+
return `${prefix}${text}`;
|
|
110
|
+
}
|
|
111
|
+
case 'blockquote': {
|
|
112
|
+
const text = renderInlineNodes(block.content || []);
|
|
113
|
+
if (!text)
|
|
114
|
+
return '';
|
|
115
|
+
return text.split('\n').map((line) => line ? `> ${line}` : '>').join('\n');
|
|
116
|
+
}
|
|
117
|
+
case 'list':
|
|
118
|
+
return renderListBlock(block, depth);
|
|
119
|
+
case 'tabularData':
|
|
120
|
+
return renderTabularDataBlock(block);
|
|
121
|
+
case 'media':
|
|
122
|
+
return renderMediaBlock(block);
|
|
123
|
+
case 'inline-newsletter':
|
|
124
|
+
case 'newsletter':
|
|
125
|
+
case 'ad':
|
|
126
|
+
return '';
|
|
127
|
+
default: {
|
|
128
|
+
if (Array.isArray(block.content) && block.content.length > 0) {
|
|
129
|
+
const inlineText = renderInlineNodes(block.content);
|
|
130
|
+
if (inlineText)
|
|
131
|
+
return inlineText;
|
|
132
|
+
const nested = block.content.map((child) => renderBlock(child, depth + 1)).filter(Boolean);
|
|
133
|
+
if (nested.length)
|
|
134
|
+
return nested.join('\n');
|
|
135
|
+
}
|
|
136
|
+
return extractGenericText(block);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function renderInlineNodes(nodes) {
|
|
141
|
+
return nodes.map((node) => renderInlineNode(node)).join('');
|
|
142
|
+
}
|
|
143
|
+
function renderInlineNode(node) {
|
|
144
|
+
if (node == null)
|
|
145
|
+
return '';
|
|
146
|
+
if (typeof node === 'string')
|
|
147
|
+
return decodeXmlEntities(node);
|
|
148
|
+
switch (node.type) {
|
|
149
|
+
case 'text':
|
|
150
|
+
return decodeXmlEntities(node.value || '');
|
|
151
|
+
case 'linebreak':
|
|
152
|
+
return '\n';
|
|
153
|
+
case 'link':
|
|
154
|
+
case 'entity':
|
|
155
|
+
case 'strong':
|
|
156
|
+
case 'emphasis':
|
|
157
|
+
case 'italic':
|
|
158
|
+
case 'underline':
|
|
159
|
+
case 'span':
|
|
160
|
+
if (Array.isArray(node.content) && node.content.length > 0) {
|
|
161
|
+
return renderInlineNodes(node.content);
|
|
162
|
+
}
|
|
163
|
+
return decodeXmlEntities(node.value || '');
|
|
164
|
+
default:
|
|
165
|
+
if (Array.isArray(node.content) && node.content.length > 0) {
|
|
166
|
+
return renderInlineNodes(node.content);
|
|
167
|
+
}
|
|
168
|
+
if (typeof node.value === 'string')
|
|
169
|
+
return decodeXmlEntities(node.value);
|
|
170
|
+
return '';
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function renderListBlock(block, depth) {
|
|
174
|
+
const items = Array.isArray(block.content) ? block.content : [];
|
|
175
|
+
if (!items.length)
|
|
176
|
+
return '';
|
|
177
|
+
const listStyle = String(block.subType || block.data?.style || block.data?.listType || '');
|
|
178
|
+
const ordered = /\bordered\b|\bnumber(?:ed)?\b/i.test(listStyle);
|
|
179
|
+
let index = 1;
|
|
180
|
+
return items
|
|
181
|
+
.map((item) => {
|
|
182
|
+
const prefix = ordered ? `${index++}. ` : '- ';
|
|
183
|
+
return renderListItem(item, prefix, depth);
|
|
184
|
+
})
|
|
185
|
+
.filter(Boolean)
|
|
186
|
+
.join('\n');
|
|
187
|
+
}
|
|
188
|
+
function renderListItem(item, prefix, depth) {
|
|
189
|
+
const indent = ' '.repeat(depth);
|
|
190
|
+
const body = normalizeBlockText(renderListItemBody(item, depth + 1));
|
|
191
|
+
if (!body)
|
|
192
|
+
return '';
|
|
193
|
+
const lines = body.split('\n');
|
|
194
|
+
const head = `${indent}${prefix}${lines[0]}`;
|
|
195
|
+
if (lines.length === 1)
|
|
196
|
+
return head;
|
|
197
|
+
const continuationIndent = `${indent}${' '.repeat(prefix.length)}`;
|
|
198
|
+
const tail = lines.slice(1).map((line) => `${continuationIndent}${line}`).join('\n');
|
|
199
|
+
return `${head}\n${tail}`;
|
|
200
|
+
}
|
|
201
|
+
function renderListItemBody(item, depth) {
|
|
202
|
+
if (!item || typeof item !== 'object')
|
|
203
|
+
return '';
|
|
204
|
+
if (item.type === 'list-item' && Array.isArray(item.content)) {
|
|
205
|
+
const parts = item.content
|
|
206
|
+
.map((child) => child?.type === 'paragraph'
|
|
207
|
+
? renderInlineNodes(child.content || [])
|
|
208
|
+
: renderBlock(child, depth))
|
|
209
|
+
.map((part) => normalizeBlockText(part))
|
|
210
|
+
.filter(Boolean);
|
|
211
|
+
return parts.join('\n');
|
|
212
|
+
}
|
|
213
|
+
return renderBlock(item, depth);
|
|
214
|
+
}
|
|
215
|
+
function renderTabularDataBlock(block) {
|
|
216
|
+
const rows = block?.data?.rows ?? block?.data?.table?.rows ?? block?.content;
|
|
217
|
+
if (!Array.isArray(rows) || !rows.length) {
|
|
218
|
+
return extractGenericText(block.data || block.content || block);
|
|
219
|
+
}
|
|
220
|
+
const lines = rows
|
|
221
|
+
.map((row) => extractGenericText(row))
|
|
222
|
+
.map((line) => normalizeBlockText(line))
|
|
223
|
+
.filter(Boolean);
|
|
224
|
+
return lines.join('\n');
|
|
225
|
+
}
|
|
226
|
+
function renderMediaBlock(block) {
|
|
227
|
+
const candidates = [
|
|
228
|
+
block?.data?.chart?.caption,
|
|
229
|
+
block?.data?.attachment?.caption,
|
|
230
|
+
block?.data?.attachment?.title,
|
|
231
|
+
block?.data?.attachment?.subtitle,
|
|
232
|
+
block?.data?.video?.caption,
|
|
233
|
+
];
|
|
234
|
+
const caption = candidates
|
|
235
|
+
.map((value) => normalizeBlockText(stripHtml(String(value || ''))))
|
|
236
|
+
.find(Boolean);
|
|
237
|
+
return caption || '';
|
|
238
|
+
}
|
|
239
|
+
function extractGenericText(value) {
|
|
240
|
+
const parts = [];
|
|
241
|
+
collectText(value, parts);
|
|
242
|
+
return parts.join(' ').replace(/\s+/g, ' ').trim();
|
|
243
|
+
}
|
|
244
|
+
function collectText(value, out) {
|
|
245
|
+
if (value == null)
|
|
246
|
+
return;
|
|
247
|
+
if (typeof value === 'string') {
|
|
248
|
+
const text = normalizeBlockText(stripHtml(decodeXmlEntities(value)));
|
|
249
|
+
if (text)
|
|
250
|
+
out.push(text);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (Array.isArray(value)) {
|
|
254
|
+
for (const item of value)
|
|
255
|
+
collectText(item, out);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (typeof value === 'object') {
|
|
259
|
+
if (typeof value.value === 'string') {
|
|
260
|
+
const text = normalizeBlockText(stripHtml(decodeXmlEntities(value.value)));
|
|
261
|
+
if (text)
|
|
262
|
+
out.push(text);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (Array.isArray(value.content)) {
|
|
266
|
+
collectText(value.content, out);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
for (const entry of Object.values(value))
|
|
270
|
+
collectText(entry, out);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function extractTagText(block, tag) {
|
|
274
|
+
const safeTag = escapeRegExp(tag);
|
|
275
|
+
const match = block.match(new RegExp(`<${safeTag}(?:\\s[^>]*)?>([\\s\\S]*?)<\\/${safeTag}>`, 'i'));
|
|
276
|
+
if (!match)
|
|
277
|
+
return '';
|
|
278
|
+
return normalizeBlockText(stripHtml(decodeXmlEntities(stripCdata(match[1]))));
|
|
279
|
+
}
|
|
280
|
+
function extractMediaLinksFromRssItem(block) {
|
|
281
|
+
const links = new Set();
|
|
282
|
+
const mediaRegex = /<(?:media:content|media:thumbnail|enclosure)\b[^>]*\burl="([^"]+)"[^>]*>/gi;
|
|
283
|
+
let match;
|
|
284
|
+
while ((match = mediaRegex.exec(block))) {
|
|
285
|
+
const url = decodeXmlEntities(match[1] || '').trim();
|
|
286
|
+
if (url)
|
|
287
|
+
links.add(url);
|
|
288
|
+
}
|
|
289
|
+
return [...links];
|
|
290
|
+
}
|
|
291
|
+
function collectMediaUrls(value, out, seen = new WeakSet()) {
|
|
292
|
+
if (value == null)
|
|
293
|
+
return;
|
|
294
|
+
if (typeof value === 'string') {
|
|
295
|
+
const normalized = normalizeMediaUrl(value);
|
|
296
|
+
if (normalized)
|
|
297
|
+
out.add(normalized);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (Array.isArray(value)) {
|
|
301
|
+
for (const item of value)
|
|
302
|
+
collectMediaUrls(item, out, seen);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (typeof value === 'object') {
|
|
306
|
+
if (seen.has(value))
|
|
307
|
+
return;
|
|
308
|
+
seen.add(value);
|
|
309
|
+
for (const key of ['url', 'src', 'fallback', 'poster']) {
|
|
310
|
+
const candidate = value[key];
|
|
311
|
+
if (typeof candidate === 'string') {
|
|
312
|
+
const normalized = normalizeMediaUrl(candidate);
|
|
313
|
+
if (normalized)
|
|
314
|
+
out.add(normalized);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
for (const entry of Object.values(value)) {
|
|
318
|
+
collectMediaUrls(entry, out, seen);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function normalizeMediaUrl(value) {
|
|
323
|
+
const url = decodeXmlEntities(String(value || '')).trim();
|
|
324
|
+
if (!/^https?:\/\//i.test(url))
|
|
325
|
+
return null;
|
|
326
|
+
if (!looksLikeMediaUrl(url))
|
|
327
|
+
return null;
|
|
328
|
+
return url;
|
|
329
|
+
}
|
|
330
|
+
function looksLikeMediaUrl(url) {
|
|
331
|
+
return /(?:assets\.bwbx\.io|resource\.bloomberg\.com|media\.bloomberg\.com)/i.test(url)
|
|
332
|
+
|| /\.(?:jpg|jpeg|png|webp|gif|svg|mp4|m3u8)(?:[?#].*)?$/i.test(url);
|
|
333
|
+
}
|
|
334
|
+
function stripCdata(value) {
|
|
335
|
+
const match = value.match(/^<!\[CDATA\[([\s\S]*?)\]\]>$/);
|
|
336
|
+
return match ? match[1] : value;
|
|
337
|
+
}
|
|
338
|
+
function stripHtml(value) {
|
|
339
|
+
return String(value || '').replace(/<[^>]+>/g, ' ');
|
|
340
|
+
}
|
|
341
|
+
function decodeXmlEntities(value) {
|
|
342
|
+
return String(value || '')
|
|
343
|
+
.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, '$1')
|
|
344
|
+
.replace(/&#(\d+);/g, (_m, code) => String.fromCodePoint(Number(code)))
|
|
345
|
+
.replace(/&#x([0-9a-f]+);/gi, (_m, code) => String.fromCodePoint(parseInt(code, 16)))
|
|
346
|
+
.replace(/&/g, '&')
|
|
347
|
+
.replace(/</g, '<')
|
|
348
|
+
.replace(/>/g, '>')
|
|
349
|
+
.replace(/"/g, '"')
|
|
350
|
+
.replace(/'/g, "'")
|
|
351
|
+
.replace(/'/g, "'")
|
|
352
|
+
.replace(/ /g, ' ');
|
|
353
|
+
}
|
|
354
|
+
function normalizeBlockText(value) {
|
|
355
|
+
return String(value || '')
|
|
356
|
+
.replace(/\r/g, '')
|
|
357
|
+
.replace(/[ \t]+\n/g, '\n')
|
|
358
|
+
.replace(/\n[ \t]+/g, '\n')
|
|
359
|
+
.replace(/[ \t]{2,}/g, ' ')
|
|
360
|
+
.trim();
|
|
361
|
+
}
|
|
362
|
+
function escapeRegExp(value) {
|
|
363
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
364
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { extractStoryMediaLinks, parseBloombergRss, renderStoryBody } from './utils.js';
|
|
3
|
+
describe('Bloomberg utils', () => {
|
|
4
|
+
it('parses Bloomberg RSS items with summary, link, and deduped media links', () => {
|
|
5
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
6
|
+
<rss xmlns:media="http://search.yahoo.com/mrss/">
|
|
7
|
+
<channel>
|
|
8
|
+
<item>
|
|
9
|
+
<title><![CDATA[Headline One]]></title>
|
|
10
|
+
<description><![CDATA[Summary <b>One</b> & more]]></description>
|
|
11
|
+
<link>https://www.bloomberg.com/news/articles/2026-03-19/example-one</link>
|
|
12
|
+
<media:content url="https://assets.bwbx.io/example-one.jpg" type="image/jpeg">
|
|
13
|
+
<media:thumbnail url="https://assets.bwbx.io/example-one.jpg" />
|
|
14
|
+
</media:content>
|
|
15
|
+
</item>
|
|
16
|
+
<item>
|
|
17
|
+
<title>Headline Two</title>
|
|
18
|
+
<description>Summary Two</description>
|
|
19
|
+
<guid isPermaLink="true">https://www.bloomberg.com/news/articles/2026-03-19/example-two</guid>
|
|
20
|
+
<enclosure url="https://assets.bwbx.io/example-two.png" type="image/png" />
|
|
21
|
+
</item>
|
|
22
|
+
</channel>
|
|
23
|
+
</rss>`;
|
|
24
|
+
const items = parseBloombergRss(xml);
|
|
25
|
+
expect(items).toHaveLength(2);
|
|
26
|
+
expect(items[0]).toEqual({
|
|
27
|
+
title: 'Headline One',
|
|
28
|
+
summary: 'Summary One & more',
|
|
29
|
+
link: 'https://www.bloomberg.com/news/articles/2026-03-19/example-one',
|
|
30
|
+
mediaLinks: ['https://assets.bwbx.io/example-one.jpg'],
|
|
31
|
+
});
|
|
32
|
+
expect(items[1]).toEqual({
|
|
33
|
+
title: 'Headline Two',
|
|
34
|
+
summary: 'Summary Two',
|
|
35
|
+
link: 'https://www.bloomberg.com/news/articles/2026-03-19/example-two',
|
|
36
|
+
mediaLinks: ['https://assets.bwbx.io/example-two.png'],
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
it('renders Bloomberg story rich-text body into readable text', () => {
|
|
40
|
+
const body = {
|
|
41
|
+
type: 'document',
|
|
42
|
+
content: [
|
|
43
|
+
{ type: 'inline-newsletter', data: { position: 'top' }, content: [] },
|
|
44
|
+
{
|
|
45
|
+
type: 'paragraph',
|
|
46
|
+
data: {},
|
|
47
|
+
content: [
|
|
48
|
+
{ type: 'text', value: 'Lead paragraph with ' },
|
|
49
|
+
{ type: 'entity', content: [{ type: 'text', value: 'linked text' }] },
|
|
50
|
+
{ type: 'text', value: '.' },
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
type: 'heading',
|
|
55
|
+
data: { level: 2 },
|
|
56
|
+
content: [{ type: 'text', value: 'Key Points' }],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
type: 'list',
|
|
60
|
+
data: { style: 'unordered' },
|
|
61
|
+
content: [
|
|
62
|
+
{
|
|
63
|
+
type: 'list-item',
|
|
64
|
+
content: [
|
|
65
|
+
{ type: 'paragraph', content: [{ type: 'text', value: 'Point one' }] },
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
type: 'list-item',
|
|
70
|
+
content: [
|
|
71
|
+
{ type: 'paragraph', content: [{ type: 'text', value: 'Point two' }] },
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
type: 'blockquote',
|
|
78
|
+
content: [{ type: 'text', value: 'Quoted line' }],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
type: 'media',
|
|
82
|
+
data: {
|
|
83
|
+
attachment: {
|
|
84
|
+
caption: '<p>Chart caption</p>',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{ type: 'ad', data: { num: 1 }, content: [] },
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
expect(renderStoryBody(body)).toBe([
|
|
92
|
+
'Lead paragraph with linked text.',
|
|
93
|
+
'## Key Points',
|
|
94
|
+
'- Point one\n- Point two',
|
|
95
|
+
'> Quoted line',
|
|
96
|
+
'Chart caption',
|
|
97
|
+
].join('\n\n'));
|
|
98
|
+
});
|
|
99
|
+
it('collects deduped story media links from lede, attachments, and body media', () => {
|
|
100
|
+
const story = {
|
|
101
|
+
ledeImageUrl: 'https://assets.bwbx.io/lede.webp',
|
|
102
|
+
lede: { url: 'https://assets.bwbx.io/lede.webp' },
|
|
103
|
+
socialImageUrl: 'https://assets.bwbx.io/social.png',
|
|
104
|
+
imageAttachments: {
|
|
105
|
+
one: { url: 'https://assets.bwbx.io/figure.jpg' },
|
|
106
|
+
},
|
|
107
|
+
body: {
|
|
108
|
+
content: [
|
|
109
|
+
{
|
|
110
|
+
type: 'media',
|
|
111
|
+
data: {
|
|
112
|
+
chart: {
|
|
113
|
+
src: 'https://resource.bloomberg.com/images/chart.png',
|
|
114
|
+
fallback: 'https://assets.bwbx.io/chart-fallback.png',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
expect(extractStoryMediaLinks(story)).toEqual([
|
|
122
|
+
'https://assets.bwbx.io/lede.webp',
|
|
123
|
+
'https://assets.bwbx.io/social.png',
|
|
124
|
+
'https://assets.bwbx.io/figure.jpg',
|
|
125
|
+
'https://resource.bloomberg.com/images/chart.png',
|
|
126
|
+
'https://assets.bwbx.io/chart-fallback.png',
|
|
127
|
+
]);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -13,7 +13,7 @@ cli({
|
|
|
13
13
|
strategy: Strategy.COOKIE,
|
|
14
14
|
browser: true,
|
|
15
15
|
args: [
|
|
16
|
-
{ name: '
|
|
16
|
+
{ name: 'job-id', default: '', help: 'Filter by encrypted job ID (greet all jobs if empty)' },
|
|
17
17
|
{ name: 'limit', type: 'int', default: 5, help: 'Max candidates to greet' },
|
|
18
18
|
{ name: 'text', default: '', help: 'Custom greeting message (uses default if empty)' },
|
|
19
19
|
],
|
|
@@ -21,7 +21,7 @@ cli({
|
|
|
21
21
|
func: async (page, kwargs) => {
|
|
22
22
|
if (!page)
|
|
23
23
|
throw new Error('Browser page required');
|
|
24
|
-
const filterJobId = kwargs
|
|
24
|
+
const filterJobId = kwargs['job-id'] || '';
|
|
25
25
|
const limit = kwargs.limit || 5;
|
|
26
26
|
const text = kwargs.text || '你好,请问您对这个职位感兴趣吗?';
|
|
27
27
|
if (process.env.OPENCLI_VERBOSE) {
|
|
@@ -9,7 +9,7 @@ cli({
|
|
|
9
9
|
args: [
|
|
10
10
|
{ name: 'page', type: 'int', default: 1, help: 'Page number' },
|
|
11
11
|
{ name: 'limit', type: 'int', default: 20, help: 'Number of results' },
|
|
12
|
-
{ name: '
|
|
12
|
+
{ name: 'job-id', default: '0', help: 'Filter by job ID (0=all)' },
|
|
13
13
|
],
|
|
14
14
|
columns: ['name', 'job', 'last_msg', 'last_time', 'uid', 'security_id'],
|
|
15
15
|
func: async (page, kwargs) => {
|
|
@@ -17,7 +17,7 @@ cli({
|
|
|
17
17
|
throw new Error('Browser page required');
|
|
18
18
|
await page.goto('https://www.zhipin.com/web/chat/index');
|
|
19
19
|
await page.wait({ time: 2 });
|
|
20
|
-
const jobId = kwargs
|
|
20
|
+
const jobId = kwargs['job-id'] || '0';
|
|
21
21
|
const pageNum = kwargs.page || 1;
|
|
22
22
|
const limit = kwargs.limit || 20;
|
|
23
23
|
const targetUrl = `https://www.zhipin.com/wapi/zprelation/friend/getBossFriendListV2.json?page=${pageNum}&status=0&jobId=${jobId}`;
|
package/dist/clis/boss/detail.js
CHANGED
|
@@ -13,7 +13,7 @@ cli({
|
|
|
13
13
|
strategy: Strategy.COOKIE,
|
|
14
14
|
browser: true,
|
|
15
15
|
args: [
|
|
16
|
-
{ name: '
|
|
16
|
+
{ name: 'security-id', required: true, help: 'Security ID from search results (securityId field)' },
|
|
17
17
|
],
|
|
18
18
|
columns: [
|
|
19
19
|
'name', 'salary', 'experience', 'degree', 'city', 'district',
|
|
@@ -25,7 +25,7 @@ cli({
|
|
|
25
25
|
func: async (page, kwargs) => {
|
|
26
26
|
if (!page)
|
|
27
27
|
throw new Error('Browser page required');
|
|
28
|
-
const securityId = kwargs
|
|
28
|
+
const securityId = kwargs['security-id'];
|
|
29
29
|
// Navigate to zhipin.com first to establish cookie context (referrer + cookies)
|
|
30
30
|
await page.goto('https://www.zhipin.com/web/geek/job');
|
|
31
31
|
await page.wait({ time: 1 });
|
package/dist/clis/boss/greet.js
CHANGED
|
@@ -18,8 +18,8 @@ cli({
|
|
|
18
18
|
browser: true,
|
|
19
19
|
args: [
|
|
20
20
|
{ name: 'uid', required: true, help: 'Encrypted UID of the candidate (from recommend)' },
|
|
21
|
-
{ name: '
|
|
22
|
-
{ name: '
|
|
21
|
+
{ name: 'security-id', required: true, help: 'Security ID of the candidate' },
|
|
22
|
+
{ name: 'job-id', required: true, help: 'Encrypted job ID' },
|
|
23
23
|
{ name: 'text', default: '', help: 'Custom greeting message (uses default template if empty)' },
|
|
24
24
|
],
|
|
25
25
|
columns: ['status', 'detail'],
|
|
@@ -27,8 +27,8 @@ cli({
|
|
|
27
27
|
if (!page)
|
|
28
28
|
throw new Error('Browser page required');
|
|
29
29
|
const uid = kwargs.uid;
|
|
30
|
-
const securityId = kwargs
|
|
31
|
-
const jobId = kwargs
|
|
30
|
+
const securityId = kwargs['security-id'];
|
|
31
|
+
const jobId = kwargs['job-id'];
|
|
32
32
|
const text = kwargs.text;
|
|
33
33
|
if (process.env.OPENCLI_VERBOSE) {
|
|
34
34
|
console.error(`[opencli:boss] Greeting candidate ${uid}...`);
|
package/dist/clis/boss/search.js
CHANGED
|
@@ -69,7 +69,7 @@ cli({
|
|
|
69
69
|
strategy: Strategy.COOKIE,
|
|
70
70
|
browser: true,
|
|
71
71
|
args: [
|
|
72
|
-
{ name: 'query', required: true, help: 'Search keyword (e.g. AI agent, 前端)' },
|
|
72
|
+
{ name: 'query', required: true, positional: true, help: 'Search keyword (e.g. AI agent, 前端)' },
|
|
73
73
|
{ name: 'city', default: '北京', help: 'City name or code (e.g. 杭州, 上海, 101010100)' },
|
|
74
74
|
{ name: 'experience', default: '', help: 'Experience: 应届/1年以内/1-3年/3-5年/5-10年/10年以上' },
|
|
75
75
|
{ name: 'degree', default: '', help: 'Degree: 大专/本科/硕士/博士' },
|
package/dist/clis/boss/send.js
CHANGED
|
@@ -14,7 +14,7 @@ cli({
|
|
|
14
14
|
browser: true,
|
|
15
15
|
args: [
|
|
16
16
|
{ name: 'uid', required: true, help: 'Encrypted UID of the candidate (from chatlist)' },
|
|
17
|
-
{ name: 'text', required: true, help: 'Message text to send' },
|
|
17
|
+
{ name: 'text', required: true, positional: true, help: 'Message text to send' },
|
|
18
18
|
],
|
|
19
19
|
columns: ['status', 'detail'],
|
|
20
20
|
func: async (page, kwargs) => {
|
package/dist/clis/boss/stats.js
CHANGED
|
@@ -15,13 +15,13 @@ cli({
|
|
|
15
15
|
strategy: Strategy.COOKIE,
|
|
16
16
|
browser: true,
|
|
17
17
|
args: [
|
|
18
|
-
{ name: '
|
|
18
|
+
{ name: 'job-id', default: '', help: 'Encrypted job ID (show all if empty)' },
|
|
19
19
|
],
|
|
20
20
|
columns: ['job_name', 'salary', 'city', 'status', 'total_chats', 'encrypt_job_id'],
|
|
21
21
|
func: async (page, kwargs) => {
|
|
22
22
|
if (!page)
|
|
23
23
|
throw new Error('Browser page required');
|
|
24
|
-
const filterJobId = kwargs
|
|
24
|
+
const filterJobId = kwargs['job-id'] || '';
|
|
25
25
|
if (process.env.OPENCLI_VERBOSE) {
|
|
26
26
|
console.error('[opencli:boss] Fetching job statistics...');
|
|
27
27
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
|
-
import { getCourses, initSession, enterCourse, getTabIframeUrl, parseAssignmentsFromDom, sleep, } from '
|
|
2
|
+
import { getCourses, initSession, enterCourse, getTabIframeUrl, parseAssignmentsFromDom, sleep, } from './utils.js';
|
|
3
3
|
cli({
|
|
4
4
|
site: 'chaoxing',
|
|
5
5
|
name: 'assignments',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
|
-
import { getCourses, initSession, enterCourse, getTabIframeUrl, parseExamsFromDom, sleep, } from '
|
|
2
|
+
import { getCourses, initSession, enterCourse, getTabIframeUrl, parseExamsFromDom, sleep, } from './utils.js';
|
|
3
3
|
cli({
|
|
4
4
|
site: 'chaoxing',
|
|
5
5
|
name: 'exams',
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Chaoxing has no flat "list all assignments" API; data is behind session-gated
|
|
6
6
|
* course pages loaded as iframes.
|
|
7
7
|
*/
|
|
8
|
-
import type { IPage } from '
|
|
8
|
+
import type { IPage } from '../../types.js';
|
|
9
9
|
/** Sleep for given milliseconds (anti-scraping delay). */
|
|
10
10
|
export declare function sleep(ms: number): Promise<void>;
|
|
11
11
|
/** Execute a credentialed fetch in the browser context, returning JSON or text. */
|
|
@@ -85,7 +85,6 @@ export async function getCourses(page) {
|
|
|
85
85
|
/** Navigate to the interaction page to establish a Chaoxing session. */
|
|
86
86
|
export async function initSession(page) {
|
|
87
87
|
await page.goto('https://mooc2-ans.chaoxing.com/mooc2-ans/visit/interaction');
|
|
88
|
-
await page.wait(3);
|
|
89
88
|
}
|
|
90
89
|
/**
|
|
91
90
|
* Enter a course via stucoursemiddle redirect (establishes course session + enc).
|
|
@@ -95,7 +94,6 @@ export async function enterCourse(page, course) {
|
|
|
95
94
|
const url = `https://mooc1.chaoxing.com/visit/stucoursemiddle` +
|
|
96
95
|
`?courseid=${course.courseId}&clazzid=${course.classId}&cpi=${course.cpi}&ismooc2=1&v=2`;
|
|
97
96
|
await page.goto(url);
|
|
98
|
-
await page.wait(3);
|
|
99
97
|
}
|
|
100
98
|
/**
|
|
101
99
|
* On the course page, click a tab (作业 / 考试) and return the iframe src
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { formatTimestamp, workStatusLabel } from './
|
|
2
|
+
import { formatTimestamp, workStatusLabel } from './utils.js';
|
|
3
3
|
function localDatePrefixFromMillis(ts) {
|
|
4
4
|
const d = new Date(ts);
|
|
5
5
|
const yyyy = d.getFullYear();
|