@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 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
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { main } = require("../lib/call-api");
4
+
5
+ main(process.argv.slice(2));
@@ -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
+ }