@jackwener/opencli 1.7.3 → 1.7.5
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/README.md +81 -59
- package/README.zh-CN.md +93 -67
- package/cli-manifest.json +5015 -2975
- package/clis/antigravity/serve.js +71 -25
- package/clis/baidu-scholar/search.js +87 -0
- package/clis/baidu-scholar/search.test.js +23 -0
- package/clis/bilibili/favorite.js +18 -13
- package/clis/binance/depth.js +3 -4
- package/clis/boss/utils.js +2 -3
- package/clis/chatgpt-app/ax.js +6 -3
- package/clis/deepseek/ask.js +74 -0
- package/clis/deepseek/history.js +25 -0
- package/clis/deepseek/new.js +20 -0
- package/clis/deepseek/read.js +22 -0
- package/clis/deepseek/status.js +24 -0
- package/clis/deepseek/utils.js +208 -0
- package/clis/douban/search.js +1 -0
- package/clis/douban/search.test.js +11 -0
- package/clis/douban/subject.js +20 -93
- package/clis/douban/subject.test.js +11 -0
- package/clis/douban/utils.js +250 -8
- package/clis/douban/utils.test.js +179 -4
- package/clis/doubao/utils.js +319 -130
- package/clis/doubao/utils.test.js +241 -2
- package/clis/eastmoney/_secid.js +78 -0
- package/clis/eastmoney/announcement.js +52 -0
- package/clis/eastmoney/convertible.js +73 -0
- package/clis/eastmoney/etf.js +65 -0
- package/clis/eastmoney/holders.js +78 -0
- package/clis/eastmoney/hot-rank.js +50 -0
- package/clis/eastmoney/hot-rank.test.js +59 -0
- package/clis/eastmoney/index-board.js +96 -0
- package/clis/eastmoney/kline.js +87 -0
- package/clis/eastmoney/kuaixun.js +54 -0
- package/clis/eastmoney/longhu.js +67 -0
- package/clis/eastmoney/money-flow.js +78 -0
- package/clis/eastmoney/northbound.js +57 -0
- package/clis/eastmoney/quote.js +107 -0
- package/clis/eastmoney/rank.js +94 -0
- package/clis/eastmoney/sectors.js +76 -0
- package/clis/google-scholar/search.js +58 -0
- package/clis/google-scholar/search.test.js +23 -0
- package/clis/gov-law/commands.test.js +39 -0
- package/clis/gov-law/recent.js +22 -0
- package/clis/gov-law/search.js +41 -0
- package/clis/gov-law/shared.js +51 -0
- package/clis/gov-policy/commands.test.js +27 -0
- package/clis/gov-policy/recent.js +47 -0
- package/clis/gov-policy/search.js +48 -0
- package/clis/grok/image.test.ts +107 -0
- package/clis/grok/image.ts +356 -0
- package/clis/nowcoder/companies.js +23 -0
- package/clis/nowcoder/creators.js +27 -0
- package/clis/nowcoder/detail.js +61 -0
- package/clis/nowcoder/experience.js +36 -0
- package/clis/nowcoder/hot.js +24 -0
- package/clis/nowcoder/jobs.js +21 -0
- package/clis/nowcoder/notifications.js +29 -0
- package/clis/nowcoder/papers.js +40 -0
- package/clis/nowcoder/practice.js +37 -0
- package/clis/nowcoder/recommend.js +30 -0
- package/clis/nowcoder/referral.js +39 -0
- package/clis/nowcoder/salary.js +40 -0
- package/clis/nowcoder/search.js +49 -0
- package/clis/nowcoder/suggest.js +33 -0
- package/clis/nowcoder/topics.js +27 -0
- package/clis/nowcoder/trending.js +25 -0
- package/clis/tdx/hot-rank.js +47 -0
- package/clis/tdx/hot-rank.test.js +59 -0
- package/clis/ths/hot-rank.js +49 -0
- package/clis/ths/hot-rank.test.js +64 -0
- package/clis/twitter/bookmarks.js +2 -1
- package/clis/twitter/list-add.js +337 -0
- package/clis/twitter/list-add.test.js +15 -0
- package/clis/twitter/list-remove.js +297 -0
- package/clis/twitter/list-remove.test.js +14 -0
- package/clis/twitter/list-tweets.js +185 -0
- package/clis/twitter/list-tweets.test.js +108 -0
- package/clis/twitter/lists.js +134 -47
- package/clis/twitter/lists.test.js +105 -38
- package/clis/uiverse/_shared.js +368 -0
- package/clis/uiverse/_shared.test.js +55 -0
- package/clis/uiverse/code.js +47 -0
- package/clis/uiverse/preview.js +71 -0
- package/clis/wanfang/search.js +66 -0
- package/clis/wanfang/search.test.js +23 -0
- package/clis/web/read.js +1 -1
- package/clis/weixin/download.js +3 -2
- package/clis/xiaohongshu/comments.js +2 -2
- package/clis/xiaohongshu/comments.test.js +46 -25
- package/clis/xiaohongshu/download.js +6 -7
- package/clis/xiaohongshu/download.test.js +17 -5
- package/clis/xiaohongshu/note-helpers.js +46 -12
- package/clis/xiaohongshu/note.js +3 -5
- package/clis/xiaohongshu/note.test.js +52 -25
- package/clis/xiaohongshu/publish.js +149 -28
- package/clis/xiaohongshu/publish.test.js +319 -6
- package/clis/xiaoyuzhou/auth.js +303 -0
- package/clis/xiaoyuzhou/auth.test.js +124 -0
- package/clis/xiaoyuzhou/download.js +53 -0
- package/clis/xiaoyuzhou/download.test.js +135 -0
- package/clis/xiaoyuzhou/episode.js +9 -4
- package/clis/xiaoyuzhou/podcast-episodes.js +15 -11
- package/clis/xiaoyuzhou/podcast.js +9 -4
- package/clis/xiaoyuzhou/transcript.js +76 -0
- package/clis/xiaoyuzhou/transcript.test.js +195 -0
- package/clis/xiaoyuzhou/utils.js +0 -40
- package/clis/xiaoyuzhou/utils.test.js +15 -75
- package/clis/youtube/feed.js +120 -0
- package/clis/youtube/history.js +118 -0
- package/clis/youtube/like.js +62 -0
- package/clis/youtube/playlist.js +97 -0
- package/clis/youtube/subscribe.js +71 -0
- package/clis/youtube/subscriptions.js +57 -0
- package/clis/youtube/unlike.js +62 -0
- package/clis/youtube/unsubscribe.js +71 -0
- package/clis/youtube/utils.js +122 -0
- package/clis/youtube/utils.test.js +32 -1
- package/clis/youtube/watch-later.js +76 -0
- package/clis/zsxq/dynamics.js +1 -1
- package/clis/zsxq/utils.js +6 -3
- package/clis/zsxq/utils.test.js +31 -0
- package/dist/src/browser/base-page.d.ts +1 -1
- package/dist/src/browser/base-page.js +25 -5
- package/dist/src/browser/bridge.d.ts +3 -0
- package/dist/src/browser/bridge.js +52 -15
- package/dist/src/browser/cdp.js +2 -1
- package/dist/src/browser/daemon-client.d.ts +7 -4
- package/dist/src/browser/daemon-client.js +6 -1
- package/dist/src/browser/daemon-client.test.js +40 -1
- package/dist/src/browser/dom-snapshot.js +20 -3
- package/dist/src/browser/page.d.ts +18 -5
- package/dist/src/browser/page.js +96 -15
- package/dist/src/browser/page.test.js +158 -1
- package/dist/src/browser/target-errors.d.ts +23 -0
- package/dist/src/browser/target-errors.js +29 -0
- package/dist/src/browser/target-errors.test.js +61 -0
- package/dist/src/browser/target-resolver.d.ts +57 -0
- package/dist/src/browser/target-resolver.js +298 -0
- package/dist/src/browser/target-resolver.test.js +43 -0
- package/dist/src/browser.test.js +38 -1
- package/dist/src/cli.js +272 -187
- package/dist/src/cli.test.js +167 -90
- package/dist/src/commanderAdapter.d.ts +0 -1
- package/dist/src/commanderAdapter.js +2 -16
- package/dist/src/commanderAdapter.test.js +1 -1
- package/dist/src/commands/daemon.d.ts +4 -2
- package/dist/src/commands/daemon.js +22 -2
- package/dist/src/commands/daemon.test.js +65 -2
- package/dist/src/completion-shared.js +2 -5
- package/dist/src/daemon.js +10 -0
- package/dist/src/doctor.d.ts +1 -0
- package/dist/src/doctor.js +32 -9
- package/dist/src/doctor.test.js +28 -12
- package/dist/src/download/article-download.d.ts +1 -0
- package/dist/src/download/article-download.js +3 -0
- package/dist/src/download/article-download.test.js +39 -0
- package/dist/src/external-clis.yaml +2 -2
- package/dist/src/logger.d.ts +2 -2
- package/dist/src/logger.js +3 -3
- package/dist/src/output.js +1 -5
- package/dist/src/output.test.js +0 -21
- package/dist/src/pipeline/steps/transform.js +1 -1
- package/dist/src/pipeline/template.d.ts +1 -0
- package/dist/src/pipeline/template.js +11 -3
- package/dist/src/pipeline/template.test.js +3 -0
- package/dist/src/pipeline/transform.test.js +14 -0
- package/dist/src/plugin.d.ts +8 -9
- package/dist/src/plugin.js +24 -28
- package/dist/src/plugin.test.js +16 -60
- package/dist/src/registry.d.ts +1 -0
- package/dist/src/registry.js +3 -2
- package/dist/src/registry.test.js +22 -0
- package/dist/src/types.d.ts +15 -6
- package/package.json +1 -1
- package/clis/twitter/lists-parser.js +0 -77
- package/clis/twitter/lists.d.ts +0 -5
- package/dist/src/cascade.d.ts +0 -46
- package/dist/src/cascade.js +0 -135
- package/dist/src/explore.d.ts +0 -99
- package/dist/src/explore.js +0 -402
- package/dist/src/generate-verified.d.ts +0 -105
- package/dist/src/generate-verified.js +0 -696
- package/dist/src/generate-verified.test.js +0 -925
- package/dist/src/generate.d.ts +0 -46
- package/dist/src/generate.js +0 -117
- package/dist/src/record.d.ts +0 -96
- package/dist/src/record.js +0 -657
- package/dist/src/record.test.js +0 -293
- package/dist/src/skill-generate.d.ts +0 -30
- package/dist/src/skill-generate.js +0 -75
- package/dist/src/skill-generate.test.js +0 -173
- package/dist/src/synthesize.d.ts +0 -97
- package/dist/src/synthesize.js +0 -208
- /package/dist/src/{generate-verified.test.d.ts → browser/target-errors.test.d.ts} +0 -0
- /package/dist/src/{record.test.d.ts → browser/target-resolver.test.d.ts} +0 -0
- /package/dist/src/{skill-generate.test.d.ts → download/article-download.test.d.ts} +0 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { getRegistry } from '@jackwener/opencli/registry';
|
|
3
|
+
import './hot-rank.js';
|
|
4
|
+
|
|
5
|
+
describe('eastmoney hot-rank command', () => {
|
|
6
|
+
it('registers the command with correct metadata', () => {
|
|
7
|
+
const command = getRegistry().get('eastmoney/hot-rank');
|
|
8
|
+
expect(command).toBeDefined();
|
|
9
|
+
expect(command).toMatchObject({
|
|
10
|
+
site: 'eastmoney',
|
|
11
|
+
name: 'hot-rank',
|
|
12
|
+
description: expect.stringContaining('东方财富'),
|
|
13
|
+
domain: 'guba.eastmoney.com',
|
|
14
|
+
navigateBefore: true,
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('returns hot stock data from the page', async () => {
|
|
19
|
+
const command = getRegistry().get('eastmoney/hot-rank');
|
|
20
|
+
const mockData = [
|
|
21
|
+
{ rank: 1, symbol: '600519', name: '贵州茅台', price: '1680.00', changePercent: '+2.35%', heat: '28.5万', url: 'https://guba.eastmoney.com/list,600519.html' },
|
|
22
|
+
{ rank: 2, symbol: '000001', name: '平安银行', price: '12.50', changePercent: '-0.80%', heat: '15.2万', url: 'https://guba.eastmoney.com/list,000001.html' },
|
|
23
|
+
];
|
|
24
|
+
const page = {
|
|
25
|
+
goto: vi.fn().mockResolvedValue(undefined),
|
|
26
|
+
wait: vi.fn().mockResolvedValue(undefined),
|
|
27
|
+
evaluate: vi.fn().mockResolvedValue(mockData),
|
|
28
|
+
};
|
|
29
|
+
const result = await command.func(page, { limit: 20 });
|
|
30
|
+
expect(result).toHaveLength(2);
|
|
31
|
+
expect(result[0]).toEqual(mockData[0]);
|
|
32
|
+
expect(page.goto).toHaveBeenCalledWith('https://guba.eastmoney.com/rank/');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('respects the limit parameter', async () => {
|
|
36
|
+
const command = getRegistry().get('eastmoney/hot-rank');
|
|
37
|
+
const mockData = Array.from({ length: 30 }, (_, i) => ({
|
|
38
|
+
rank: i + 1, symbol: `${i}`, name: `stock${i}`, price: '0', changePercent: '0%', heat: '0', url: '',
|
|
39
|
+
}));
|
|
40
|
+
const page = {
|
|
41
|
+
goto: vi.fn().mockResolvedValue(undefined),
|
|
42
|
+
wait: vi.fn().mockResolvedValue(undefined),
|
|
43
|
+
evaluate: vi.fn().mockResolvedValue(mockData),
|
|
44
|
+
};
|
|
45
|
+
const result = await command.func(page, { limit: 10 });
|
|
46
|
+
expect(result).toHaveLength(10);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('returns empty array when evaluate returns non-array', async () => {
|
|
50
|
+
const command = getRegistry().get('eastmoney/hot-rank');
|
|
51
|
+
const page = {
|
|
52
|
+
goto: vi.fn().mockResolvedValue(undefined),
|
|
53
|
+
wait: vi.fn().mockResolvedValue(undefined),
|
|
54
|
+
evaluate: vi.fn().mockResolvedValue(null),
|
|
55
|
+
};
|
|
56
|
+
const result = await command.func(page, { limit: 20 });
|
|
57
|
+
expect(result).toEqual([]);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// eastmoney index-board — live quotes for key Chinese market indices.
|
|
2
|
+
//
|
|
3
|
+
// Data source: push2.eastmoney.com (Tier 1 public JSON, no auth).
|
|
4
|
+
// opencli eastmoney index-board
|
|
5
|
+
// opencli eastmoney index-board --group all
|
|
6
|
+
|
|
7
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
8
|
+
import { CliError } from '@jackwener/opencli/errors';
|
|
9
|
+
|
|
10
|
+
const INDEX_GROUPS = {
|
|
11
|
+
main: [
|
|
12
|
+
['1.000001', '上证指数'],
|
|
13
|
+
['0.399001', '深证成指'],
|
|
14
|
+
['0.399006', '创业板指'],
|
|
15
|
+
['1.000688', '科创50'],
|
|
16
|
+
['1.000300', '沪深300'],
|
|
17
|
+
['1.000905', '中证500'],
|
|
18
|
+
],
|
|
19
|
+
hk: [
|
|
20
|
+
['100.HSI', '恒生指数'],
|
|
21
|
+
['100.HSCEI', '恒生国企'],
|
|
22
|
+
['100.HSTECH', '恒生科技'],
|
|
23
|
+
],
|
|
24
|
+
us: [
|
|
25
|
+
['100.DJIA', '道琼斯'],
|
|
26
|
+
['100.SPX', '标普500'],
|
|
27
|
+
['100.NDX', '纳斯达克100'],
|
|
28
|
+
['100.IXIC', '纳斯达克综指'],
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const FIELDS = 'f2,f3,f4,f12,f13,f14,f15,f16,f17,f18';
|
|
33
|
+
|
|
34
|
+
cli({
|
|
35
|
+
site: 'eastmoney',
|
|
36
|
+
name: 'index-board',
|
|
37
|
+
description: '主要市场指数行情(A股 / 港股 / 美股)',
|
|
38
|
+
domain: 'push2.eastmoney.com',
|
|
39
|
+
strategy: Strategy.PUBLIC,
|
|
40
|
+
browser: false,
|
|
41
|
+
args: [
|
|
42
|
+
{
|
|
43
|
+
name: 'group',
|
|
44
|
+
type: 'string',
|
|
45
|
+
default: 'main',
|
|
46
|
+
help: '指数分组:main (A股主要), hk (港股), us (美股), all',
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
columns: ['code', 'name', 'price', 'changePercent', 'change', 'open', 'high', 'low', 'prevClose'],
|
|
50
|
+
func: async (_page, args) => {
|
|
51
|
+
const group = String(args.group ?? 'main').toLowerCase();
|
|
52
|
+
/** @type {[string,string][]} */
|
|
53
|
+
let entries;
|
|
54
|
+
if (group === 'all') {
|
|
55
|
+
entries = [...INDEX_GROUPS.main, ...INDEX_GROUPS.hk, ...INDEX_GROUPS.us];
|
|
56
|
+
} else if (INDEX_GROUPS[group]) {
|
|
57
|
+
entries = INDEX_GROUPS[group];
|
|
58
|
+
} else {
|
|
59
|
+
throw new CliError('INVALID_ARGUMENT', `Unknown group "${group}". Valid: main, hk, us, all`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const secids = entries.map(([secid]) => secid).join(',');
|
|
63
|
+
const url = new URL('https://push2.eastmoney.com/api/qt/ulist.np/get');
|
|
64
|
+
url.searchParams.set('secids', secids);
|
|
65
|
+
url.searchParams.set('fltt', '2');
|
|
66
|
+
url.searchParams.set('fields', FIELDS);
|
|
67
|
+
url.searchParams.set('ut', 'bd1d9ddb04089700cf9c27f6f7426281');
|
|
68
|
+
|
|
69
|
+
const resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0', Accept: 'application/json' } });
|
|
70
|
+
if (!resp.ok) throw new CliError('HTTP_ERROR', `eastmoney index-board failed: HTTP ${resp.status}`);
|
|
71
|
+
const data = await resp.json();
|
|
72
|
+
const diff = Array.isArray(data?.data?.diff) ? data.data.diff : [];
|
|
73
|
+
if (diff.length === 0) throw new CliError('NO_DATA', 'eastmoney returned no index data');
|
|
74
|
+
|
|
75
|
+
// Preserve the order defined in INDEX_GROUPS regardless of API ordering
|
|
76
|
+
const byCode = new Map(diff.map((it) => [String(it.f12), it]));
|
|
77
|
+
return entries
|
|
78
|
+
.map(([secid, fallbackName]) => {
|
|
79
|
+
const code = secid.split('.')[1];
|
|
80
|
+
const it = byCode.get(code);
|
|
81
|
+
if (!it) return null;
|
|
82
|
+
return {
|
|
83
|
+
code,
|
|
84
|
+
name: it.f14 || fallbackName,
|
|
85
|
+
price: it.f2,
|
|
86
|
+
changePercent: it.f3,
|
|
87
|
+
change: it.f4,
|
|
88
|
+
open: it.f17,
|
|
89
|
+
high: it.f15,
|
|
90
|
+
low: it.f16,
|
|
91
|
+
prevClose: it.f18,
|
|
92
|
+
};
|
|
93
|
+
})
|
|
94
|
+
.filter(Boolean);
|
|
95
|
+
},
|
|
96
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// eastmoney kline — historical OHLCV for one stock, any timeframe.
|
|
2
|
+
//
|
|
3
|
+
// Data source: push2his.eastmoney.com (Tier 1 public JSON).
|
|
4
|
+
// opencli eastmoney kline 600519 --period day --limit 30
|
|
5
|
+
// opencli eastmoney kline sh600519 --period week --adjust forward
|
|
6
|
+
|
|
7
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
8
|
+
import { CliError } from '@jackwener/opencli/errors';
|
|
9
|
+
import { resolveSecid } from './_secid.js';
|
|
10
|
+
|
|
11
|
+
const PERIOD_MAP = {
|
|
12
|
+
'1m': 1, '5m': 5, '15m': 15, '30m': 30, '60m': 60,
|
|
13
|
+
minute: 1, hour: 60,
|
|
14
|
+
day: 101, daily: 101,
|
|
15
|
+
week: 102, weekly: 102,
|
|
16
|
+
month: 103, monthly: 103,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const ADJUST_MAP = {
|
|
20
|
+
none: 0, no: 0, off: 0,
|
|
21
|
+
forward: 1, front: 1, 'pre': 1,
|
|
22
|
+
backward: 2, back: 2, 'post': 2,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
cli({
|
|
26
|
+
site: 'eastmoney',
|
|
27
|
+
name: 'kline',
|
|
28
|
+
description: 'K线历史数据(分/日/周/月/前复权/后复权)',
|
|
29
|
+
domain: 'push2his.eastmoney.com',
|
|
30
|
+
strategy: Strategy.PUBLIC,
|
|
31
|
+
browser: false,
|
|
32
|
+
args: [
|
|
33
|
+
{ name: 'symbol', required: true, positional: true, help: '股票代码(A/HK/US 均可)' },
|
|
34
|
+
{ name: 'period', type: 'string', default: 'day', help: '周期:1m/5m/15m/30m/60m/day/week/month' },
|
|
35
|
+
{ name: 'adjust', type: 'string', default: 'forward', help: '复权:none / forward / backward' },
|
|
36
|
+
{ name: 'limit', type: 'int', default: 30, help: '返回最近 N 根(末尾)' },
|
|
37
|
+
],
|
|
38
|
+
columns: ['date', 'open', 'close', 'high', 'low', 'volume', 'turnover', 'amplitude', 'changePercent', 'change', 'turnoverRate'],
|
|
39
|
+
func: async (_page, args) => {
|
|
40
|
+
const secid = resolveSecid(args.symbol);
|
|
41
|
+
const periodKey = String(args.period ?? 'day').toLowerCase();
|
|
42
|
+
const klt = PERIOD_MAP[periodKey];
|
|
43
|
+
if (klt == null) {
|
|
44
|
+
throw new CliError('INVALID_ARGUMENT', `Unknown period "${periodKey}". Valid: ${Object.keys(PERIOD_MAP).join(', ')}`);
|
|
45
|
+
}
|
|
46
|
+
const adjustKey = String(args.adjust ?? 'forward').toLowerCase();
|
|
47
|
+
const fqt = ADJUST_MAP[adjustKey];
|
|
48
|
+
if (fqt == null) {
|
|
49
|
+
throw new CliError('INVALID_ARGUMENT', `Unknown adjust "${adjustKey}". Valid: none / forward / backward`);
|
|
50
|
+
}
|
|
51
|
+
const limit = Math.max(1, Math.min(Number(args.limit) || 30, 1000));
|
|
52
|
+
|
|
53
|
+
const url = new URL('https://push2his.eastmoney.com/api/qt/stock/kline/get');
|
|
54
|
+
url.searchParams.set('secid', secid);
|
|
55
|
+
url.searchParams.set('klt', String(klt));
|
|
56
|
+
url.searchParams.set('fqt', String(fqt));
|
|
57
|
+
url.searchParams.set('beg', '0');
|
|
58
|
+
url.searchParams.set('end', '20500101');
|
|
59
|
+
url.searchParams.set('fields1', 'f1,f2,f3,f4,f5,f6');
|
|
60
|
+
url.searchParams.set('fields2', 'f51,f52,f53,f54,f55,f56,f57,f58,f59,f60,f61');
|
|
61
|
+
url.searchParams.set('ut', 'b2884a393a59ad64002292a3e90d46a5');
|
|
62
|
+
|
|
63
|
+
const resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
|
|
64
|
+
if (!resp.ok) throw new CliError('HTTP_ERROR', `eastmoney kline failed: HTTP ${resp.status}`);
|
|
65
|
+
const data = await resp.json();
|
|
66
|
+
/** @type {string[]} */
|
|
67
|
+
const raw = Array.isArray(data?.data?.klines) ? data.data.klines : [];
|
|
68
|
+
if (raw.length === 0) throw new CliError('NO_DATA', `No kline data for ${args.symbol}`);
|
|
69
|
+
|
|
70
|
+
return raw.slice(-limit).map((line) => {
|
|
71
|
+
const [date, open, close, high, low, volume, turnover, amplitude, changePercent, change, turnoverRate] = line.split(',');
|
|
72
|
+
return {
|
|
73
|
+
date,
|
|
74
|
+
open: Number(open),
|
|
75
|
+
close: Number(close),
|
|
76
|
+
high: Number(high),
|
|
77
|
+
low: Number(low),
|
|
78
|
+
volume: Number(volume),
|
|
79
|
+
turnover: Number(turnover),
|
|
80
|
+
amplitude: Number(amplitude),
|
|
81
|
+
changePercent: Number(changePercent),
|
|
82
|
+
change: Number(change),
|
|
83
|
+
turnoverRate: Number(turnoverRate),
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// eastmoney kuaixun — 7x24 real-time market news feed.
|
|
2
|
+
//
|
|
3
|
+
// opencli eastmoney kuaixun
|
|
4
|
+
// opencli eastmoney kuaixun --column 102 --limit 30
|
|
5
|
+
|
|
6
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
7
|
+
import { CliError } from '@jackwener/opencli/errors';
|
|
8
|
+
|
|
9
|
+
// Known columns on eastmoney 7x24:
|
|
10
|
+
// 102 = 重要 (default)
|
|
11
|
+
// 101 = 全部
|
|
12
|
+
// 104 = 公司
|
|
13
|
+
// 105 = 市场
|
|
14
|
+
// 106 = 机构
|
|
15
|
+
// 107 = 宏观
|
|
16
|
+
|
|
17
|
+
cli({
|
|
18
|
+
site: 'eastmoney',
|
|
19
|
+
name: 'kuaixun',
|
|
20
|
+
description: '东方财富 7x24 财经快讯',
|
|
21
|
+
domain: 'np-listapi.eastmoney.com',
|
|
22
|
+
strategy: Strategy.PUBLIC,
|
|
23
|
+
browser: false,
|
|
24
|
+
args: [
|
|
25
|
+
{ name: 'column', type: 'string', default: '102', help: '频道:102 (重要) / 101 (全部) / 104 / 105 / 106 / 107' },
|
|
26
|
+
{ name: 'limit', type: 'int', default: 20, help: '返回数量 (max 100)' },
|
|
27
|
+
],
|
|
28
|
+
columns: ['time', 'title', 'summary', 'stocks'],
|
|
29
|
+
func: async (_page, args) => {
|
|
30
|
+
const column = String(args.column ?? '102').trim();
|
|
31
|
+
const limit = Math.max(1, Math.min(Number(args.limit) || 20, 100));
|
|
32
|
+
|
|
33
|
+
const url = new URL('https://np-listapi.eastmoney.com/comm/web/getFastNewsList');
|
|
34
|
+
url.searchParams.set('client', 'web');
|
|
35
|
+
url.searchParams.set('biz', 'web_724');
|
|
36
|
+
url.searchParams.set('fastColumn', column);
|
|
37
|
+
url.searchParams.set('sortEnd', '');
|
|
38
|
+
url.searchParams.set('pageSize', String(limit));
|
|
39
|
+
url.searchParams.set('req_trace', '1');
|
|
40
|
+
|
|
41
|
+
const resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0', Accept: 'application/json' } });
|
|
42
|
+
if (!resp.ok) throw new CliError('HTTP_ERROR', `kuaixun failed: HTTP ${resp.status}`);
|
|
43
|
+
const data = await resp.json();
|
|
44
|
+
const list = Array.isArray(data?.data?.fastNewsList) ? data.data.fastNewsList : [];
|
|
45
|
+
if (list.length === 0) throw new CliError('NO_DATA', 'eastmoney returned no kuaixun data');
|
|
46
|
+
|
|
47
|
+
return list.slice(0, limit).map((it) => ({
|
|
48
|
+
time: it.showTime,
|
|
49
|
+
title: it.title,
|
|
50
|
+
summary: (it.summary || '').replace(/\s+/g, ' ').slice(0, 400),
|
|
51
|
+
stocks: Array.isArray(it.stockList) ? it.stockList.join(', ') : '',
|
|
52
|
+
}));
|
|
53
|
+
},
|
|
54
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// eastmoney longhu — dragon & tiger list (龙虎榜).
|
|
2
|
+
//
|
|
3
|
+
// opencli eastmoney longhu
|
|
4
|
+
// opencli eastmoney longhu --date 2025-12-10 --limit 20
|
|
5
|
+
|
|
6
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
7
|
+
import { CliError } from '@jackwener/opencli/errors';
|
|
8
|
+
|
|
9
|
+
function defaultTradeDate() {
|
|
10
|
+
// Default window = 30 days back; results sorted DESC so latest comes first.
|
|
11
|
+
// This avoids missing data on weekends/holidays when "yesterday" had no trading.
|
|
12
|
+
const d = new Date(Date.now() + 8 * 3600 * 1000);
|
|
13
|
+
d.setUTCDate(d.getUTCDate() - 30);
|
|
14
|
+
return d.toISOString().slice(0, 10);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
cli({
|
|
18
|
+
site: 'eastmoney',
|
|
19
|
+
name: 'longhu',
|
|
20
|
+
description: '龙虎榜明细(A股交易所公开披露榜单)',
|
|
21
|
+
domain: 'datacenter-web.eastmoney.com',
|
|
22
|
+
strategy: Strategy.PUBLIC,
|
|
23
|
+
browser: false,
|
|
24
|
+
args: [
|
|
25
|
+
{ name: 'date', type: 'string', default: '', help: '开始交易日 YYYY-MM-DD (默认昨天)' },
|
|
26
|
+
{ name: 'limit', type: 'int', default: 20, help: '返回数量 (max 100)' },
|
|
27
|
+
],
|
|
28
|
+
columns: ['tradeDate', 'code', 'name', 'closePrice', 'changeRate', 'boardAmt', 'buyAmt', 'sellAmt', 'netAmt', 'turnover', 'dealRatio', 'market', 'reason'],
|
|
29
|
+
func: async (_page, args) => {
|
|
30
|
+
const sinceDate = String(args.date || '').trim() || defaultTradeDate();
|
|
31
|
+
const limit = Math.max(1, Math.min(Number(args.limit) || 20, 100));
|
|
32
|
+
|
|
33
|
+
const url = new URL('https://datacenter-web.eastmoney.com/api/data/v1/get');
|
|
34
|
+
url.searchParams.set('sortColumns', 'TRADE_DATE,SECURITY_CODE');
|
|
35
|
+
url.searchParams.set('sortTypes', '-1,1');
|
|
36
|
+
url.searchParams.set('pageSize', String(limit));
|
|
37
|
+
url.searchParams.set('pageNumber', '1');
|
|
38
|
+
url.searchParams.set('reportName', 'RPT_DAILYBILLBOARD_DETAILS');
|
|
39
|
+
url.searchParams.set('columns', 'ALL');
|
|
40
|
+
url.searchParams.set('source', 'WEB');
|
|
41
|
+
url.searchParams.set('client', 'WEB');
|
|
42
|
+
url.searchParams.set('filter', `(TRADE_DATE>='${sinceDate}')`);
|
|
43
|
+
|
|
44
|
+
const resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
|
|
45
|
+
if (!resp.ok) throw new CliError('HTTP_ERROR', `longhu failed: HTTP ${resp.status}`);
|
|
46
|
+
const data = await resp.json();
|
|
47
|
+
/** @type {any[]} */
|
|
48
|
+
const rows = Array.isArray(data?.result?.data) ? data.result.data : [];
|
|
49
|
+
if (rows.length === 0) throw new CliError('NO_DATA', `No longhu data since ${sinceDate}`);
|
|
50
|
+
|
|
51
|
+
return rows.slice(0, limit).map((it) => ({
|
|
52
|
+
tradeDate: String(it.TRADE_DATE || '').slice(0, 10),
|
|
53
|
+
code: it.SECURITY_CODE,
|
|
54
|
+
name: it.SECURITY_NAME_ABBR,
|
|
55
|
+
closePrice: it.CLOSE_PRICE,
|
|
56
|
+
changeRate: it.CHANGE_RATE,
|
|
57
|
+
boardAmt: it.BILLBOARD_DEAL_AMT,
|
|
58
|
+
buyAmt: it.BILLBOARD_BUY_AMT,
|
|
59
|
+
sellAmt: it.BILLBOARD_SELL_AMT,
|
|
60
|
+
netAmt: it.BILLBOARD_NET_AMT,
|
|
61
|
+
turnover: it.ACCUM_AMOUNT,
|
|
62
|
+
dealRatio: it.DEAL_AMOUNT_RATIO,
|
|
63
|
+
market: it.TRADE_MARKET,
|
|
64
|
+
reason: it.EXPLANATION,
|
|
65
|
+
}));
|
|
66
|
+
},
|
|
67
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// eastmoney money-flow — main-force net inflow ranking (沪深A今日/5日/10日).
|
|
2
|
+
//
|
|
3
|
+
// opencli eastmoney money-flow
|
|
4
|
+
// opencli eastmoney money-flow --range 5d --limit 30
|
|
5
|
+
|
|
6
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
7
|
+
import { CliError } from '@jackwener/opencli/errors';
|
|
8
|
+
|
|
9
|
+
const A_MARKET = 'm:0+t:6,m:0+t:80,m:1+t:2,m:1+t:23,m:0+t:81+s:2048';
|
|
10
|
+
|
|
11
|
+
// (main, super, big, medium, small) net inflow amount fields
|
|
12
|
+
const RANGES = {
|
|
13
|
+
today: { fid: 'f62', fields: { net: 'f62', netPct: 'f184', super: 'f66', big: 'f72', medium: 'f78', small: 'f84' } },
|
|
14
|
+
'5d': { fid: 'f164', fields: { net: 'f164', netPct: 'f165', super: 'f166', big: 'f169', medium: 'f172', small: 'f175' } },
|
|
15
|
+
'10d': { fid: 'f174', fields: { net: 'f174', netPct: 'f175', super: 'f176', big: 'f179', medium: 'f182', small: 'f185' } },
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
cli({
|
|
19
|
+
site: 'eastmoney',
|
|
20
|
+
name: 'money-flow',
|
|
21
|
+
description: '主力资金净流入排行(今日/5日/10日)',
|
|
22
|
+
domain: 'push2.eastmoney.com',
|
|
23
|
+
strategy: Strategy.PUBLIC,
|
|
24
|
+
browser: false,
|
|
25
|
+
args: [
|
|
26
|
+
{ name: 'range', type: 'string', default: 'today', help: '周期:today / 5d / 10d' },
|
|
27
|
+
{ name: 'order', type: 'string', default: 'desc', help: '排序:desc (净流入排行) / asc (净流出)' },
|
|
28
|
+
{ name: 'limit', type: 'int', default: 20, help: '返回数量 (max 100)' },
|
|
29
|
+
],
|
|
30
|
+
columns: ['rank', 'code', 'name', 'price', 'changePercent', 'mainNet', 'mainNetRatio', 'superNet', 'bigNet', 'mediumNet', 'smallNet'],
|
|
31
|
+
func: async (_page, args) => {
|
|
32
|
+
const rangeKey = String(args.range ?? 'today').toLowerCase();
|
|
33
|
+
const range = RANGES[rangeKey];
|
|
34
|
+
if (!range) {
|
|
35
|
+
throw new CliError('INVALID_ARGUMENT', `Unknown range "${rangeKey}". Valid: ${Object.keys(RANGES).join(', ')}`);
|
|
36
|
+
}
|
|
37
|
+
const po = String(args.order ?? 'desc').toLowerCase() === 'asc' ? '0' : '1';
|
|
38
|
+
const limit = Math.max(1, Math.min(Number(args.limit) || 20, 100));
|
|
39
|
+
|
|
40
|
+
const fieldList = [
|
|
41
|
+
'f12', 'f14', 'f2', 'f3',
|
|
42
|
+
range.fields.net, range.fields.netPct,
|
|
43
|
+
range.fields.super, range.fields.big, range.fields.medium, range.fields.small,
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const url = new URL('https://push2.eastmoney.com/api/qt/clist/get');
|
|
47
|
+
url.searchParams.set('pn', '1');
|
|
48
|
+
url.searchParams.set('pz', String(limit));
|
|
49
|
+
url.searchParams.set('po', po);
|
|
50
|
+
url.searchParams.set('np', '1');
|
|
51
|
+
url.searchParams.set('fltt', '2');
|
|
52
|
+
url.searchParams.set('invt', '2');
|
|
53
|
+
url.searchParams.set('fid', range.fid);
|
|
54
|
+
url.searchParams.set('fs', A_MARKET);
|
|
55
|
+
url.searchParams.set('fields', fieldList.join(','));
|
|
56
|
+
url.searchParams.set('ut', 'b2884a393a59ad64002292a3e90d46a5');
|
|
57
|
+
|
|
58
|
+
const resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
|
|
59
|
+
if (!resp.ok) throw new CliError('HTTP_ERROR', `money-flow failed: HTTP ${resp.status}`);
|
|
60
|
+
const data = await resp.json();
|
|
61
|
+
const diff = Array.isArray(data?.data?.diff) ? data.data.diff : [];
|
|
62
|
+
if (diff.length === 0) throw new CliError('NO_DATA', 'eastmoney returned no money-flow data');
|
|
63
|
+
|
|
64
|
+
return diff.slice(0, limit).map((it, i) => ({
|
|
65
|
+
rank: i + 1,
|
|
66
|
+
code: it.f12,
|
|
67
|
+
name: it.f14,
|
|
68
|
+
price: it.f2,
|
|
69
|
+
changePercent: it.f3,
|
|
70
|
+
mainNet: it[range.fields.net],
|
|
71
|
+
mainNetRatio: it[range.fields.netPct],
|
|
72
|
+
superNet: it[range.fields.super],
|
|
73
|
+
bigNet: it[range.fields.big],
|
|
74
|
+
mediumNet: it[range.fields.medium],
|
|
75
|
+
smallNet: it[range.fields.small],
|
|
76
|
+
}));
|
|
77
|
+
},
|
|
78
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// eastmoney northbound — live realtime cross-border capital flow (北向/南向).
|
|
2
|
+
//
|
|
3
|
+
// Returns the latest non-empty minute snapshot of cumulative net flow in 万元.
|
|
4
|
+
// opencli eastmoney northbound
|
|
5
|
+
// opencli eastmoney northbound --direction south
|
|
6
|
+
|
|
7
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
8
|
+
import { CliError } from '@jackwener/opencli/errors';
|
|
9
|
+
|
|
10
|
+
cli({
|
|
11
|
+
site: 'eastmoney',
|
|
12
|
+
name: 'northbound',
|
|
13
|
+
description: '沪深港通北向/南向资金当日分时净流入(万元)',
|
|
14
|
+
domain: 'push2.eastmoney.com',
|
|
15
|
+
strategy: Strategy.PUBLIC,
|
|
16
|
+
browser: false,
|
|
17
|
+
args: [
|
|
18
|
+
{ name: 'direction', type: 'string', default: 'north', help: '方向:north (北向,即外资买A) / south (南向,即内地买港)' },
|
|
19
|
+
{ name: 'limit', type: 'int', default: 10, help: '返回最近 N 分钟' },
|
|
20
|
+
],
|
|
21
|
+
columns: ['time', 'cumulativeNetYi', 'minuteNetYi', 'totalNetYi'],
|
|
22
|
+
func: async (_page, args) => {
|
|
23
|
+
const dir = String(args.direction ?? 'north').toLowerCase();
|
|
24
|
+
if (!['north', 'south', 'n', 's'].includes(dir)) {
|
|
25
|
+
throw new CliError('INVALID_ARGUMENT', `Unknown direction "${dir}". Valid: north / south`);
|
|
26
|
+
}
|
|
27
|
+
const limit = Math.max(1, Math.min(Number(args.limit) || 10, 240));
|
|
28
|
+
|
|
29
|
+
const url = new URL('https://push2.eastmoney.com/api/qt/kamtbs.rtmin/get');
|
|
30
|
+
url.searchParams.set('fields1', 'f1,f2,f3,f4');
|
|
31
|
+
url.searchParams.set('fields2', 'f51,f52,f54,f56');
|
|
32
|
+
url.searchParams.set('ut', 'b2884a393a59ad64002292a3e90d46a5');
|
|
33
|
+
|
|
34
|
+
const resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
|
|
35
|
+
if (!resp.ok) throw new CliError('HTTP_ERROR', `northbound failed: HTTP ${resp.status}`);
|
|
36
|
+
const data = await resp.json();
|
|
37
|
+
const key = (dir === 'south' || dir === 's') ? 's2n' : 'n2s';
|
|
38
|
+
/** @type {string[]} */
|
|
39
|
+
const rows = Array.isArray(data?.data?.[key]) ? data.data[key] : [];
|
|
40
|
+
if (rows.length === 0) throw new CliError('NO_DATA', `No ${key} data returned`);
|
|
41
|
+
|
|
42
|
+
// CSV fields per entry: "HH:MM,cumulative_net(万), minute_net(万), total_net(万)"
|
|
43
|
+
// Drop rows with '-' (after market close or before open). Convert 万元 → 亿元 for readability.
|
|
44
|
+
const valid = rows
|
|
45
|
+
.map((r) => r.split(','))
|
|
46
|
+
.filter((c) => c.length >= 4 && c[1] !== '-');
|
|
47
|
+
if (valid.length === 0) {
|
|
48
|
+
throw new CliError('NO_DATA', `${key} has no valid minute data yet (markets may not be open)`);
|
|
49
|
+
}
|
|
50
|
+
return valid.slice(-limit).map(([time, cum, min, total]) => ({
|
|
51
|
+
time,
|
|
52
|
+
cumulativeNetYi: +(Number(cum) / 10000).toFixed(4),
|
|
53
|
+
minuteNetYi: +(Number(min) / 10000).toFixed(4),
|
|
54
|
+
totalNetYi: +(Number(total) / 10000).toFixed(4),
|
|
55
|
+
}));
|
|
56
|
+
},
|
|
57
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// eastmoney quote — live quote for one or more stocks (A/HK/US).
|
|
2
|
+
//
|
|
3
|
+
// Data source: push2.eastmoney.com (Tier 1 public JSON, no auth).
|
|
4
|
+
// Supported inputs (comma / space separated):
|
|
5
|
+
// 600000, sh600000, 000001, sz000001, 00700.HK, hk00700, AAPL, us.AAPL
|
|
6
|
+
//
|
|
7
|
+
// opencli eastmoney quote 600000 --fields all
|
|
8
|
+
// opencli eastmoney quote "sh600000,sz000001,00700.HK"
|
|
9
|
+
|
|
10
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
11
|
+
import { CliError } from '@jackwener/opencli/errors';
|
|
12
|
+
import { resolveSecid, splitSymbols } from './_secid.js';
|
|
13
|
+
|
|
14
|
+
const FIELDS = [
|
|
15
|
+
'f12', // code
|
|
16
|
+
'f13', // market
|
|
17
|
+
'f14', // name
|
|
18
|
+
'f2', // price
|
|
19
|
+
'f3', // changePercent
|
|
20
|
+
'f4', // change
|
|
21
|
+
'f5', // volume (手)
|
|
22
|
+
'f6', // turnover (CNY)
|
|
23
|
+
'f7', // amplitude %
|
|
24
|
+
'f8', // turnoverRate %
|
|
25
|
+
'f9', // peDynamic
|
|
26
|
+
'f10', // volumeRatio
|
|
27
|
+
'f15', // high
|
|
28
|
+
'f16', // low
|
|
29
|
+
'f17', // open
|
|
30
|
+
'f18', // prevClose
|
|
31
|
+
'f20', // marketCap
|
|
32
|
+
'f21', // floatMarketCap
|
|
33
|
+
'f23', // priceBook
|
|
34
|
+
].join(',');
|
|
35
|
+
|
|
36
|
+
function marketLabel(f13) {
|
|
37
|
+
if (f13 === 1) return 'SH';
|
|
38
|
+
if (f13 === 0) return 'SZ/BJ';
|
|
39
|
+
if (f13 === 116) return 'HK';
|
|
40
|
+
if (f13 === 105 || f13 === 106 || f13 === 107) return 'US';
|
|
41
|
+
return String(f13 ?? '');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
cli({
|
|
45
|
+
site: 'eastmoney',
|
|
46
|
+
name: 'quote',
|
|
47
|
+
description: '个股实时行情(A股 / 港股 / 美股)— 来自 push2.eastmoney.com',
|
|
48
|
+
domain: 'push2.eastmoney.com',
|
|
49
|
+
strategy: Strategy.PUBLIC,
|
|
50
|
+
browser: false,
|
|
51
|
+
args: [
|
|
52
|
+
{ name: 'symbols', required: true, positional: true, help: '股票代码(可用逗号/空格分隔多个)' },
|
|
53
|
+
],
|
|
54
|
+
columns: [
|
|
55
|
+
'code', 'name', 'market', 'price', 'changePercent', 'change',
|
|
56
|
+
'open', 'high', 'low', 'prevClose', 'volume', 'turnover',
|
|
57
|
+
'turnoverRate', 'amplitude', 'peDynamic', 'priceBook',
|
|
58
|
+
'marketCap', 'floatMarketCap',
|
|
59
|
+
],
|
|
60
|
+
func: async (_page, args) => {
|
|
61
|
+
const raw = splitSymbols(args.symbols);
|
|
62
|
+
if (raw.length === 0) {
|
|
63
|
+
throw new CliError('INVALID_ARGUMENT', 'At least one symbol is required');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** @type {string[]} */
|
|
67
|
+
const secids = [];
|
|
68
|
+
for (const s of raw) {
|
|
69
|
+
try { secids.push(resolveSecid(s)); }
|
|
70
|
+
catch (err) { throw new CliError('INVALID_ARGUMENT', `Unrecognized symbol "${s}"`); }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Multi-stock in one call via ulist.np
|
|
74
|
+
const url = new URL('https://push2.eastmoney.com/api/qt/ulist.np/get');
|
|
75
|
+
url.searchParams.set('secids', secids.join(','));
|
|
76
|
+
url.searchParams.set('fltt', '2');
|
|
77
|
+
url.searchParams.set('fields', FIELDS);
|
|
78
|
+
url.searchParams.set('ut', 'bd1d9ddb04089700cf9c27f6f7426281');
|
|
79
|
+
|
|
80
|
+
const resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0', Accept: 'application/json' } });
|
|
81
|
+
if (!resp.ok) throw new CliError('HTTP_ERROR', `eastmoney quote failed: HTTP ${resp.status}`);
|
|
82
|
+
const data = await resp.json();
|
|
83
|
+
const diff = Array.isArray(data?.data?.diff) ? data.data.diff : [];
|
|
84
|
+
if (diff.length === 0) throw new CliError('NO_DATA', 'eastmoney returned no quotes', `Check symbols: ${raw.join(', ')}`);
|
|
85
|
+
|
|
86
|
+
return diff.map((it) => ({
|
|
87
|
+
code: it.f12,
|
|
88
|
+
name: it.f14,
|
|
89
|
+
market: marketLabel(it.f13),
|
|
90
|
+
price: it.f2,
|
|
91
|
+
changePercent: it.f3,
|
|
92
|
+
change: it.f4,
|
|
93
|
+
open: it.f17,
|
|
94
|
+
high: it.f15,
|
|
95
|
+
low: it.f16,
|
|
96
|
+
prevClose: it.f18,
|
|
97
|
+
volume: it.f5,
|
|
98
|
+
turnover: it.f6,
|
|
99
|
+
turnoverRate: it.f8,
|
|
100
|
+
amplitude: it.f7,
|
|
101
|
+
peDynamic: it.f9,
|
|
102
|
+
priceBook: it.f23,
|
|
103
|
+
marketCap: it.f20,
|
|
104
|
+
floatMarketCap: it.f21,
|
|
105
|
+
}));
|
|
106
|
+
},
|
|
107
|
+
});
|