@investoday/investoday-api 1.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.
- package/README.md +42 -0
- package/bin/investoday-api.js +5 -0
- package/lib/call-api.js +162 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @investoday/investoday-api
|
|
2
|
+
|
|
3
|
+
Official CLI for accessing InvestToday China market financial data.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @investoday/investoday-api
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## API key
|
|
12
|
+
|
|
13
|
+
Set your API key with the `INVESTODAY_API_KEY` environment variable:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
export INVESTODAY_API_KEY="<your_key>"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Get an API key from:
|
|
20
|
+
|
|
21
|
+
- https://data-api.investoday.net/login
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
investoday-api <endpoint> [key=value ...]
|
|
27
|
+
investoday-api <endpoint> --method POST [key=value ...]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Examples:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
investoday-api stock/basic-info stockCode=600519
|
|
34
|
+
investoday-api search key=贵州茅台 type=11
|
|
35
|
+
investoday-api fund/daily-quotes --method POST fundCode=000001 beginDate=2024-01-01 endDate=2024-12-31
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Notes
|
|
39
|
+
|
|
40
|
+
- Only reads credentials from `INVESTODAY_API_KEY`
|
|
41
|
+
- Only calls `https://data-api.investoday.net/data`
|
|
42
|
+
- Prints the API response `data` field as formatted JSON
|
package/lib/call-api.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
const BASE_URL = "https://data-api.investoday.net/data";
|
|
2
|
+
const REQUEST_TIMEOUT = 30_000;
|
|
3
|
+
|
|
4
|
+
function exitWithError(message) {
|
|
5
|
+
process.stderr.write(`${message}\n`);
|
|
6
|
+
process.exit(1);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function loadApiKey() {
|
|
10
|
+
const envKey = (process.env.INVESTODAY_API_KEY || "").trim();
|
|
11
|
+
if (envKey) {
|
|
12
|
+
return envKey;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
exitWithError("ERROR: Please set the INVESTODAY_API_KEY environment variable first.");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseArgs(argv) {
|
|
19
|
+
if (!argv.length) {
|
|
20
|
+
exitWithError(
|
|
21
|
+
"Usage: investoday-api <endpoint> [key=value ...] [--method GET|POST]\n" +
|
|
22
|
+
"Example: investoday-api stock/basic-info stockCode=600519"
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const apiPath = argv[0].replace(/^\/+/, "");
|
|
27
|
+
let method = "GET";
|
|
28
|
+
const params = {};
|
|
29
|
+
|
|
30
|
+
let index = 1;
|
|
31
|
+
while (index < argv.length) {
|
|
32
|
+
const arg = argv[index];
|
|
33
|
+
if (arg === "--method") {
|
|
34
|
+
index += 1;
|
|
35
|
+
if (index >= argv.length) {
|
|
36
|
+
exitWithError("ERROR: --method requires GET or POST");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
method = argv[index].toUpperCase();
|
|
40
|
+
if (method !== "GET" && method !== "POST") {
|
|
41
|
+
exitWithError(`ERROR: Unsupported HTTP method '${method}', only GET and POST are supported`);
|
|
42
|
+
}
|
|
43
|
+
} else if (!arg.includes("=")) {
|
|
44
|
+
exitWithError(`ERROR: Invalid argument '${arg}', expected key=value`);
|
|
45
|
+
} else {
|
|
46
|
+
const equalIndex = arg.indexOf("=");
|
|
47
|
+
const key = arg.slice(0, equalIndex);
|
|
48
|
+
const value = arg.slice(equalIndex + 1);
|
|
49
|
+
|
|
50
|
+
if (!key) {
|
|
51
|
+
exitWithError(`ERROR: Invalid argument '${arg}', key cannot be empty`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (Object.prototype.hasOwnProperty.call(params, key)) {
|
|
55
|
+
const existing = params[key];
|
|
56
|
+
params[key] = Array.isArray(existing) ? [...existing, value] : [existing, value];
|
|
57
|
+
} else {
|
|
58
|
+
params[key] = value;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
index += 1;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { apiPath, method, params };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function buildUrl(apiPath, params) {
|
|
69
|
+
let url = `${BASE_URL}/${apiPath}`;
|
|
70
|
+
if (!Object.keys(params).length) {
|
|
71
|
+
return url;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const searchParams = new URLSearchParams();
|
|
75
|
+
for (const [key, value] of Object.entries(params)) {
|
|
76
|
+
if (Array.isArray(value)) {
|
|
77
|
+
value.forEach((item) => searchParams.append(key, item));
|
|
78
|
+
} else {
|
|
79
|
+
searchParams.append(key, value);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return `${url}?${searchParams.toString()}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function callApi(apiPath, method, params, apiKey) {
|
|
87
|
+
const headers = { apiKey };
|
|
88
|
+
const requestOptions = {
|
|
89
|
+
method,
|
|
90
|
+
headers,
|
|
91
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT),
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
let url = `${BASE_URL}/${apiPath}`;
|
|
95
|
+
if (method === "POST") {
|
|
96
|
+
headers["Content-Type"] = "application/json";
|
|
97
|
+
requestOptions.body = JSON.stringify(params);
|
|
98
|
+
} else {
|
|
99
|
+
url = buildUrl(apiPath, params);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let response;
|
|
103
|
+
try {
|
|
104
|
+
response = await fetch(url, requestOptions);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (error.name === "TimeoutError" || error.name === "AbortError") {
|
|
107
|
+
exitWithError(`ERROR: Request timed out after ${REQUEST_TIMEOUT / 1000}s: ${url}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let message = String(error.message || error);
|
|
111
|
+
if (apiKey && message.includes(apiKey)) {
|
|
112
|
+
message = message.replaceAll(apiKey, "***");
|
|
113
|
+
}
|
|
114
|
+
exitWithError(`ERROR: Request failed: ${message}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
const body = await response.text().catch(() => "");
|
|
119
|
+
exitWithError(`ERROR: HTTP ${response.status}: ${url}\n${body.slice(0, 500)}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let result;
|
|
123
|
+
try {
|
|
124
|
+
result = await response.json();
|
|
125
|
+
} catch {
|
|
126
|
+
const body = await response.text().catch(() => "");
|
|
127
|
+
exitWithError(`ERROR: Response is not valid JSON\n${body.slice(0, 500)}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (result.code !== 0) {
|
|
131
|
+
exitWithError(`ERROR: API returned error [${result.code}]: ${result.message || "Unknown error"}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (result.data === undefined || result.data === null) {
|
|
135
|
+
exitWithError("ERROR: API response does not contain a data field");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
process.stdout.write(`${JSON.stringify(result.data, null, 2)}\n`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function main(argv = process.argv.slice(2)) {
|
|
142
|
+
const { apiPath, method, params } = parseArgs(argv);
|
|
143
|
+
const apiKey = loadApiKey();
|
|
144
|
+
await callApi(apiPath, method, params, apiKey);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = {
|
|
148
|
+
BASE_URL,
|
|
149
|
+
REQUEST_TIMEOUT,
|
|
150
|
+
buildUrl,
|
|
151
|
+
callApi,
|
|
152
|
+
loadApiKey,
|
|
153
|
+
main,
|
|
154
|
+
parseArgs,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
if (require.main === module) {
|
|
158
|
+
main().catch((error) => {
|
|
159
|
+
const message = error && error.message ? error.message : String(error);
|
|
160
|
+
exitWithError(`ERROR: ${message}`);
|
|
161
|
+
});
|
|
162
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@investoday/investoday-api",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI for accessing InvestToday China market financial data via investoday-api.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/investoday-data/investoday-api-skills",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/investoday-data/investoday-api-skills.git",
|
|
10
|
+
"directory": "package/investoday-api"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/investoday-data/investoday-api-skills/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "commonjs",
|
|
16
|
+
"main": "lib/call-api.js",
|
|
17
|
+
"bin": {
|
|
18
|
+
"investoday-api": "bin/investoday-api.js"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"bin",
|
|
22
|
+
"lib",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"keywords": [
|
|
26
|
+
"investoday",
|
|
27
|
+
"finance",
|
|
28
|
+
"financial-data",
|
|
29
|
+
"china-market",
|
|
30
|
+
"a-share",
|
|
31
|
+
"hk-stock",
|
|
32
|
+
"fund",
|
|
33
|
+
"index",
|
|
34
|
+
"macro-economics",
|
|
35
|
+
"cli"
|
|
36
|
+
],
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
}
|
|
43
|
+
}
|