daxiapi-cli 1.2.0 → 2.0.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.
@@ -0,0 +1,120 @@
1
+ const config = require('../lib/config');
2
+ const api = require('../lib/api');
3
+ const {handleError, createParameterError} = require('../lib/error');
4
+ const {output} = require('../lib/output');
5
+
6
+ module.exports = function (program) {
7
+ const sectorCmd = program.command('sector');
8
+
9
+ sectorCmd
10
+ .description('板块数据')
11
+ .option('--order <field>', '排序字段', 'cs')
12
+ .option('--limit <num>', '数量限制', 5)
13
+ .action(async options => {
14
+ try {
15
+ const token = config.getToken();
16
+ if (!token) {
17
+ const error = new Error('未配置 API Token');
18
+ error.response = {status: 401};
19
+ throw error;
20
+ }
21
+
22
+ const data = await api.getSectorData(token, options.order, options.limit);
23
+ output(data);
24
+ } catch (error) {
25
+ handleError(error);
26
+ process.exit(1);
27
+ }
28
+ });
29
+
30
+ sectorCmd
31
+ .command('bk')
32
+ .description('行业板块数据')
33
+ .action(async () => {
34
+ try {
35
+ const token = config.getToken();
36
+ if (!token) {
37
+ const error = new Error('未配置 API Token');
38
+ error.response = {status: 401};
39
+ throw error;
40
+ }
41
+
42
+ const data = await api.getBkData(token);
43
+ output(data);
44
+ } catch (error) {
45
+ handleError(error);
46
+ process.exit(1);
47
+ }
48
+ });
49
+
50
+ sectorCmd
51
+ .command('stocks')
52
+ .description('板块个股排名')
53
+ .requiredOption('--code <bkCode>', '板块代码')
54
+ .option('--order <field>', '排序字段', 'cs')
55
+ .action(async options => {
56
+ try {
57
+ const token = config.getToken();
58
+ if (!token) {
59
+ const error = new Error('未配置 API Token');
60
+ error.response = {status: 401};
61
+ throw error;
62
+ }
63
+
64
+ const data = await api.getSectorRankStock(token, options.code, options.order);
65
+ output(data);
66
+ } catch (error) {
67
+ handleError(error);
68
+ process.exit(1);
69
+ }
70
+ });
71
+
72
+ sectorCmd
73
+ .command('top')
74
+ .description('热门股票')
75
+ .action(async () => {
76
+ try {
77
+ const token = config.getToken();
78
+ if (!token) {
79
+ const error = new Error('未配置 API Token');
80
+ error.response = {status: 401};
81
+ throw error;
82
+ }
83
+
84
+ const data = await api.getTopStocks(token);
85
+ output(data);
86
+ } catch (error) {
87
+ handleError(error);
88
+ process.exit(1);
89
+ }
90
+ });
91
+
92
+ sectorCmd
93
+ .command('gn')
94
+ .description('热门概念')
95
+ .option('--type <type>', '数据源类型 (dfcf|ths)', 'ths')
96
+ .action(async options => {
97
+ try {
98
+ const token = config.getToken();
99
+ if (!token) {
100
+ const error = new Error('未配置 API Token');
101
+ error.response = {status: 401};
102
+ throw error;
103
+ }
104
+
105
+ if (!['dfcf', 'ths'].includes(options.type)) {
106
+ throw createParameterError(
107
+ '参数无效',
108
+ ["参数 'type' 必须是 dfcf 或 ths"],
109
+ ['daxiapi sector gn --type ths', 'daxiapi sector gn --type dfcf']
110
+ );
111
+ }
112
+
113
+ const data = await api.getGnHot(token, options.type);
114
+ output(data);
115
+ } catch (error) {
116
+ handleError(error);
117
+ process.exit(1);
118
+ }
119
+ });
120
+ };
@@ -0,0 +1,79 @@
1
+ const config = require('../lib/config');
2
+ const api = require('../lib/api');
3
+ const {handleError, createParameterError} = require('../lib/error');
4
+ const {output} = require('../lib/output');
5
+
6
+ module.exports = function(program) {
7
+ const stockCmd = program.command('stock');
8
+
9
+ stockCmd
10
+ .description('股票数据查询')
11
+ .argument('<codes...>', '股票代码(支持多个)')
12
+ .action(async (codes) => {
13
+ try {
14
+ const token = config.getToken();
15
+ if (!token) {
16
+ const error = new Error('未配置 API Token');
17
+ error.response = {status: 401};
18
+ throw error;
19
+ }
20
+
21
+ if (!codes || codes.length === 0) {
22
+ throw createParameterError(
23
+ '参数无效',
24
+ ["参数 'codes' 不能为空"],
25
+ [
26
+ 'daxiapi stock 000001',
27
+ 'daxiapi stock 000001 600031 300750'
28
+ ]
29
+ );
30
+ }
31
+
32
+ const data = await api.getStockData(token, codes);
33
+ output(data);
34
+ } catch (error) {
35
+ handleError(error);
36
+ process.exit(1);
37
+ }
38
+ });
39
+
40
+ stockCmd
41
+ .command('gn <gnId>')
42
+ .description('概念股查询')
43
+ .option('--type <type>', '数据源类型 (dfcf|ths)', 'ths')
44
+ .action(async (gnId, options) => {
45
+ try {
46
+ const token = config.getToken();
47
+ if (!token) {
48
+ const error = new Error('未配置 API Token');
49
+ error.response = {status: 401};
50
+ throw error;
51
+ }
52
+
53
+ if (!gnId) {
54
+ throw createParameterError(
55
+ '参数无效',
56
+ ["参数 'gnId' 不能为空"],
57
+ ['daxiapi stock gn GN1234']
58
+ );
59
+ }
60
+
61
+ if (!['dfcf', 'ths'].includes(options.type)) {
62
+ throw createParameterError(
63
+ '参数无效',
64
+ ["参数 'type' 必须是 dfcf 或 ths"],
65
+ [
66
+ 'daxiapi stock gn GN1234 --type ths',
67
+ 'daxiapi stock gn GN1234 --type dfcf'
68
+ ]
69
+ );
70
+ }
71
+
72
+ const data = await api.getGainianStock(token, gnId, options.type);
73
+ output(data);
74
+ } catch (error) {
75
+ handleError(error);
76
+ process.exit(1);
77
+ }
78
+ });
79
+ };
@@ -0,0 +1,38 @@
1
+ const config = require('../lib/config');
2
+ const api = require('../lib/api');
3
+ const {handleError, createParameterError} = require('../lib/error');
4
+ const {output} = require('../lib/output');
5
+
6
+ module.exports = function(program) {
7
+ program
8
+ .command('zdt')
9
+ .description('涨跌停池')
10
+ .option('--type <type>', '类型 (zt|dt)', 'zt')
11
+ .action(async (options) => {
12
+ try {
13
+ const token = config.getToken();
14
+ if (!token) {
15
+ const error = new Error('未配置 API Token');
16
+ error.response = {status: 401};
17
+ throw error;
18
+ }
19
+
20
+ if (!['zt', 'dt'].includes(options.type)) {
21
+ throw createParameterError(
22
+ '参数无效',
23
+ ["参数 'type' 必须是 zt 或 dt"],
24
+ [
25
+ 'daxiapi zdt --type zt',
26
+ 'daxiapi zdt --type dt'
27
+ ]
28
+ );
29
+ }
30
+
31
+ const data = await api.getZdtPool(token, options.type);
32
+ output(data);
33
+ } catch (error) {
34
+ handleError(error);
35
+ process.exit(1);
36
+ }
37
+ });
38
+ };
package/lib/api.js CHANGED
@@ -1,286 +1,128 @@
1
1
  const axios = require('axios');
2
2
  const config = require('./config');
3
3
 
4
- const BASE_URL = config.getBaseUrl() || 'https://daxiapi.com';
4
+ const BASE_URL = config.get('baseUrl') || 'https://daxiapi.com';
5
5
 
6
- /**
7
- * 获取市场类型前缀
8
- */
9
- function getMarketPrefix(code) {
10
- if (code.startsWith('688') || code.startsWith('689')) {
11
- return 'sh';
12
- }
13
- if (code.startsWith('60')) {
14
- return 'sh';
15
- }
16
- return 'sz';
17
- }
18
-
19
- /**
20
- * 创建请求实例
21
- */
22
6
  function createClient(token) {
23
7
  return axios.create({
24
8
  baseURL: `${BASE_URL}/coze`,
25
9
  headers: {
26
- 'X-API-Token': token,
10
+ 'Authorization': `Bearer ${token}`,
27
11
  'Content-Type': 'application/json'
28
12
  },
29
13
  timeout: 30000
30
14
  });
31
15
  }
32
16
 
33
- /**
34
- * GET 请求
35
- */
36
17
  async function get(client, path) {
37
18
  const {data} = await client.get(path);
38
19
  if (data.errCode !== 0) {
39
- throw new Error(data.errMsg || `API Error: ${data.errCode}`);
20
+ const error = new Error(data.errMsg || `API Error: ${data.errCode}`);
21
+ error.response = {status: data.errCode};
22
+ throw error;
40
23
  }
41
24
  return data.data;
42
25
  }
43
26
 
44
- /**
45
- * POST 请求
46
- */
47
27
  async function post(client, path, body = {}) {
48
28
  const {data} = await client.post(path, body);
49
29
  if (data.errCode !== 0) {
50
- throw new Error(data.errMsg || `API Error: ${data.errCode}`);
30
+ const error = new Error(data.errMsg || `API Error: ${data.errCode}`);
31
+ error.response = {status: data.errCode};
32
+ throw error;
51
33
  }
52
34
  return data.data;
53
35
  }
54
36
 
55
- // ==================== API 方法 ====================
56
-
57
- /**
58
- * 获取指数K线
59
- */
60
- async function getIndexK(token) {
61
- const client = createClient(token);
62
- return get(client, '/get_index_k');
63
- }
64
-
65
- /**
66
- * 获取市场数据
67
- */
68
37
  async function getMarketData(token) {
69
38
  const client = createClient(token);
70
- return get(client, '/get_market_data');
39
+ return get(client, '/get_index_data');
71
40
  }
72
41
 
73
- /**
74
- * 获取市场温度
75
- */
76
- async function getMarketDegree(token) {
42
+ async function getMarketTemp(token) {
77
43
  const client = createClient(token);
78
- return get(client, '/get_market_degree');
44
+ return get(client, '/get_market_temp');
79
45
  }
80
46
 
81
- /**
82
- * 获取市场风格
83
- */
84
47
  async function getMarketStyle(token) {
85
48
  const client = createClient(token);
86
49
  return get(client, '/get_market_style');
87
50
  }
88
51
 
89
- /**
90
- * 获取指数估值
91
- */
92
52
  async function getMarketValueData(token) {
93
53
  const client = createClient(token);
94
54
  return get(client, '/get_market_value_data');
95
55
  }
96
56
 
97
- /**
98
- * 获取收盘新闻
99
- */
100
- async function getMarketNews(token) {
57
+ async function getBkData(token) {
101
58
  const client = createClient(token);
102
- return get(client, '/get_market_end_news');
59
+ return get(client, '/get_bk_data');
103
60
  }
104
61
 
105
- /**
106
- * 获取板块热力图
107
- */
108
- async function getSectorData(token, orderBy = 'cs', lmt = 5) {
62
+ async function getSectorData(token, orderBy = 'cs', limit = 5) {
109
63
  const client = createClient(token);
110
- return post(client, '/get_sector_data', {orderBy, lmt});
64
+ return post(client, '/get_sector_data', {orderBy, lmt: limit});
111
65
  }
112
66
 
113
- /**
114
- * 获取板块内个股排名
115
- */
116
67
  async function getSectorRankStock(token, sectorCode, orderBy = 'cs') {
117
68
  const client = createClient(token);
118
69
  return post(client, '/get_sector_rank_stock', {sectorCode, orderBy});
119
70
  }
120
71
 
121
- /**
122
- * 获取概念内个股
123
- */
124
- async function getGainianStock(token, gnId) {
72
+ async function getTopStocks(token) {
125
73
  const client = createClient(token);
126
- return post(client, '/get_gainian_stock', {gnId});
74
+ return post(client, '/get_top_stocks', {});
75
+ }
76
+
77
+ async function getGnHot(token, type = 'ths') {
78
+ const client = createClient(token);
79
+ return post(client, '/get_gn_hot', {type});
127
80
  }
128
81
 
129
- /**
130
- * 获取个股数据
131
- */
132
82
  async function getStockData(token, codes) {
133
83
  const client = createClient(token);
134
84
  return post(client, '/get_stock_data', {code: codes});
135
85
  }
136
86
 
137
- /**
138
- * 搜索股票/行业
139
- */
140
- async function queryStock(token, keyword, type = 'stock') {
87
+ async function getGainianStock(token, gnId, type = 'ths') {
141
88
  const client = createClient(token);
142
- return post(client, '/query_stock_data', {q: keyword, type});
89
+ return post(client, '/get_gainian_stock', {gnId, type});
143
90
  }
144
91
 
145
- /**
146
- * 获取概念板块数据
147
- */
148
- async function getGnTable(token) {
92
+ async function getKline(token, code) {
149
93
  const client = createClient(token);
150
- return post(client, '/get_gn_table');
94
+ return post(client, '/get_kline', {code});
151
95
  }
152
96
 
153
- // ==================== K线数据(多数据源) ====================
154
-
155
- /**
156
- * 获取K线数据(首选腾讯,备选东方财富)
157
- * @param {string} code - 股票代码
158
- * @param {number} limit - 数据条数,默认300
159
- * @param {string} type - K线类型: day(日线), week(周线), month(月线)
160
- * @returns {Promise<Object>} K线数据
161
- */
162
- async function getKline(code, limit = 300, type = 'day') {
163
- // 首选腾讯
164
- try {
165
- const data = await getKlineFromQQ(code, limit, type);
166
- return {source: 'qq', ...data};
167
- } catch (err) {
168
- console.log('腾讯数据源失败,切换到东方财富...');
169
- }
170
-
171
- // 备选东方财富
172
- try {
173
- const data = await getKlineFromEastmoney(code, limit, type);
174
- return {source: 'eastmoney', ...data};
175
- } catch (err) {
176
- throw new Error('所有数据源均失败,请稍后重试');
177
- }
97
+ async function getZdtPool(token, type = 'zt') {
98
+ const client = createClient(token);
99
+ return post(client, '/get_zdt_pool', {type});
178
100
  }
179
101
 
180
- /**
181
- * 腾讯财经K线数据
182
- * @param {string} code - 股票代码
183
- * @param {number} limit - 数据条数
184
- * @param {string} type - K线类型
185
- */
186
- async function getKlineFromQQ(code, limit = 500, type = 'day') {
187
- const market = getMarketPrefix(code);
188
- const fullCode = `${market}${code}`;
189
-
190
- // 腾讯接口限制最大2000条
191
- if (limit > 2000) {
192
- limit = 2000;
193
- }
194
-
195
- const url = `https://proxy.finance.qq.com/ifzqgtimg/appstock/app/newfqkline/get?_var=&param=${fullCode},${type},,,${limit},qfq&r=0.${Date.now()}`;
196
-
197
- const {data} = await axios.get(url, {timeout: 10000});
198
-
199
- if (!data.data || !data.data[fullCode]) {
200
- throw new Error('腾讯数据源返回异常');
201
- }
202
-
203
- const klineData = data.data[fullCode].qfqday || data.data[fullCode].day;
204
- if (!klineData || klineData.length === 0) {
205
- throw new Error('无K线数据');
206
- }
207
-
208
- const klines = klineData.map(([date, open, close, high, low, volume, , , amount]) => ({
209
- date,
210
- open: parseFloat(open),
211
- close: parseFloat(close),
212
- high: parseFloat(high),
213
- low: parseFloat(low),
214
- volume: parseFloat(volume),
215
- amount: parseFloat(amount)
216
- }));
217
-
218
- return {
219
- code,
220
- name: data.data[fullCode].qt?.[fullCode]?.[1] || code,
221
- klines,
222
- count: klines.length
223
- };
102
+ async function getSecId(token, code) {
103
+ const client = createClient(token);
104
+ return post(client, '/get_sec_id', {code});
224
105
  }
225
106
 
226
- /**
227
- * 东方财富K线数据
228
- * @param {string} code - 股票代码
229
- * @param {number} limit - 数据条数
230
- * @param {string} type - K线类型: day=101, week=102, month=103
231
- */
232
- async function getKlineFromEastmoney(code, limit = 300, type = 'day') {
233
- // K线类型映射
234
- const kltMap = {day: '101', week: '102', month: '103'};
235
- const klt = kltMap[type] || '101';
236
-
237
- // 市场代码
238
- const secid = code.startsWith('6') ? `1.${code}` : `0.${code}`;
239
-
240
- const url = `https://push2his.eastmoney.com/api/qt/stock/kline/get?fields1=f1,f2,f3,f4,f5,f6&fields2=f51,f52,f53,f54,f55,f56,f57,f58,f61&ut=7eea3edcaed734bea9cbfc24409ed989&end=29991010&klt=${klt}&secid=${secid}&fqt=1&lmt=${limit}`;
241
-
242
- const {data} = await axios.get(url, {timeout: 10000});
243
-
244
- if (!data.data || !data.data.klines) {
245
- throw new Error('东方财富数据源返回异常');
246
- }
247
-
248
- const klines = data.data.klines.map(line => {
249
- const [date, open, close, high, low, volume, amount, , turnover] = line.split(',');
250
- return {
251
- date,
252
- open: parseFloat(open),
253
- close: parseFloat(close),
254
- high: parseFloat(high),
255
- low: parseFloat(low),
256
- volume: parseFloat(volume),
257
- amount: parseFloat(amount),
258
- turnover: parseFloat(turnover) || 0
259
- };
260
- });
261
-
262
- return {
263
- code,
264
- name: data.data.name || code,
265
- klines,
266
- count: klines.length
267
- };
107
+ async function queryStockData(token, q, type = 'stock') {
108
+ const client = createClient(token);
109
+ return post(client, '/query_stock_data', {q, type});
268
110
  }
269
111
 
270
112
  module.exports = {
271
- getIndexK,
272
113
  getMarketData,
273
- getMarketDegree,
114
+ getMarketTemp,
274
115
  getMarketStyle,
275
116
  getMarketValueData,
276
- getMarketNews,
117
+ getBkData,
277
118
  getSectorData,
278
119
  getSectorRankStock,
279
- getGainianStock,
120
+ getTopStocks,
121
+ getGnHot,
280
122
  getStockData,
281
- queryStock,
282
- getGnTable,
123
+ getGainianStock,
283
124
  getKline,
284
- getKlineFromQQ,
285
- getKlineFromEastmoney
125
+ getZdtPool,
126
+ getSecId,
127
+ queryStockData
286
128
  };
package/lib/config.js CHANGED
@@ -17,30 +17,18 @@ const config = new Conf({
17
17
  schema
18
18
  });
19
19
 
20
- /**
21
- * 获取Token(优先环境变量)
22
- */
23
20
  function getToken() {
24
21
  return process.env.DAXIAPI_TOKEN || config.get('token');
25
22
  }
26
23
 
27
- /**
28
- * 获取配置项
29
- */
30
24
  function get(key) {
31
25
  return config.get(key);
32
26
  }
33
27
 
34
- /**
35
- * 设置配置项
36
- */
37
28
  function set(key, value) {
38
29
  config.set(key, value);
39
30
  }
40
31
 
41
- /**
42
- * 获取所有配置
43
- */
44
32
  function getAll() {
45
33
  return {
46
34
  token: getToken() ? '******' + getToken().slice(-4) : undefined,
@@ -48,9 +36,6 @@ function getAll() {
48
36
  };
49
37
  }
50
38
 
51
- /**
52
- * 删除配置项
53
- */
54
39
  function del(key) {
55
40
  config.delete(key);
56
41
  }
package/lib/error.js ADDED
@@ -0,0 +1,80 @@
1
+ const chalk = require('chalk');
2
+
3
+ const errorMessages = {
4
+ 401: {
5
+ title: 'API Token 无效或已过期',
6
+ solutions: [
7
+ '检查 Token 是否正确配置:\n daxiapi config set token YOUR_TOKEN',
8
+ '或设置环境变量:\n export DAXIAPI_TOKEN=YOUR_TOKEN',
9
+ 'Token 可在 daxiapi.com 用户中心获取'
10
+ ]
11
+ },
12
+ 403: {
13
+ title: '无访问权限',
14
+ solutions: [
15
+ '检查您的账户是否有访问该接口的权限',
16
+ '联系管理员开通相关权限',
17
+ '访问 daxiapi.com 了解权限说明'
18
+ ]
19
+ },
20
+ 429: {
21
+ title: '请求频率超限',
22
+ solutions: [
23
+ '请稍后重试',
24
+ '检查您的请求频率是否过高',
25
+ '升级账户获取更高配额'
26
+ ]
27
+ },
28
+ 500: {
29
+ title: '服务器内部错误',
30
+ solutions: [
31
+ '请稍后重试',
32
+ '如果问题持续,请联系技术支持',
33
+ '访问 daxiapi.com 查看服务状态'
34
+ ]
35
+ }
36
+ };
37
+
38
+ function handleError(error) {
39
+ console.log(chalk.red.bold('\n❌ 错误:'), error.message || error.title);
40
+
41
+ const statusCode = error.response?.status || error.statusCode;
42
+ const errorInfo = errorMessages[statusCode];
43
+
44
+ if (errorInfo) {
45
+ console.log(chalk.bold('\n解决方法:'));
46
+ errorInfo.solutions.forEach((solution, index) => {
47
+ console.log(` ${index + 1}. ${solution}`);
48
+ });
49
+ } else if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND') {
50
+ console.log(chalk.bold('\n解决方法:'));
51
+ console.log(' 1. 检查网络连接是否正常');
52
+ console.log(' 2. 检查 API 地址是否正确:\n daxiapi config set baseUrl https://daxiapi.com');
53
+ console.log(' 3. 如果使用代理,请检查代理配置');
54
+ } else if (error.details) {
55
+ console.log(chalk.bold('\n详细信息:'));
56
+ error.details.forEach(detail => {
57
+ console.log(` - ${detail}`);
58
+ });
59
+ if (error.examples) {
60
+ console.log(chalk.bold('\n使用示例:'));
61
+ error.examples.forEach(example => {
62
+ console.log(` ${example}`);
63
+ });
64
+ }
65
+ }
66
+
67
+ console.log('');
68
+ }
69
+
70
+ function createParameterError(message, details, examples) {
71
+ const error = new Error(message);
72
+ error.details = details;
73
+ error.examples = examples;
74
+ return error;
75
+ }
76
+
77
+ module.exports = {
78
+ handleError,
79
+ createParameterError
80
+ };