@mathieuc/tradingview 3.4.1 → 3.4.2
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/.eslintrc.js +1 -0
- package/README.md +7 -1
- package/examples/AllPrivateIndicators.js +5 -4
- package/examples/BuiltInIndicator.js +3 -4
- package/examples/CustomChartType.js +4 -6
- package/examples/CustomTimeframe.js +3 -4
- package/examples/Errors.js +3 -3
- package/examples/FakeReplayMode.js +4 -5
- package/examples/FromToData.js +4 -5
- package/examples/GetDrawings.js +3 -4
- package/examples/GraphicIndicator.js +4 -5
- package/examples/MultipleSyncFetch.js +3 -4
- package/examples/PinePermManage.js +7 -7
- package/examples/ReplayMode.js +4 -5
- package/examples/Search.js +4 -5
- package/examples/SimpleChart.js +3 -3
- package/examples/UserLogin.js +4 -4
- package/package.json +6 -3
- package/src/chart/session.js +1 -1
- package/src/classes/PinePermManager.js +89 -74
- package/src/miscRequests.js +163 -187
- package/vite.config.js +6 -0
- package/src/FormData.js +0 -36
- package/src/request.js +0 -37
- package/test.js +0 -70
- package/tests/01. Search.js +0 -17
- package/tests/02. QuoteSession.js +0 -51
- package/tests/03. SimpleChart.js +0 -59
- package/tests/04. Indicators.js +0 -96
- package/tests/05. BuiltInIndicator.js +0 -39
- package/tests/06. FakeReplayMode.js +0 -57
- package/tests/07. ReplayMode.js +0 -49
- package/tests/08. CustomChartTypes.js +0 -113
- package/tests/09. AllErrors.js +0 -184
- package/tests/10. Authenticated.js +0 -101
package/src/miscRequests.js
CHANGED
|
@@ -1,31 +1,18 @@
|
|
|
1
1
|
const os = require('os');
|
|
2
|
-
const
|
|
3
|
-
const request = require('./request');
|
|
4
|
-
const FormData = require('./FormData');
|
|
2
|
+
const axios = require('axios');
|
|
5
3
|
|
|
6
|
-
const PinePermManager = require('./classes/PinePermManager');
|
|
7
4
|
const PineIndicator = require('./classes/PineIndicator');
|
|
8
5
|
|
|
6
|
+
const validateStatus = (status) => status < 500;
|
|
7
|
+
|
|
9
8
|
const indicators = ['Recommend.Other', 'Recommend.All', 'Recommend.MA'];
|
|
10
9
|
const builtInIndicList = [];
|
|
11
10
|
|
|
12
11
|
async function fetchScanData(tickers = [], type = '', columns = []) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
headers: {
|
|
18
|
-
'Content-Type': 'application/json',
|
|
19
|
-
},
|
|
20
|
-
}, true, JSON.stringify({ symbols: { tickers }, columns }));
|
|
21
|
-
|
|
22
|
-
if (!data.startsWith('{')) throw new Error('Wrong screener or symbol');
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
data = JSON.parse(data);
|
|
26
|
-
} catch (e) {
|
|
27
|
-
throw new Error('Can\'t parse server response');
|
|
28
|
-
}
|
|
12
|
+
const { data } = await axios.post(`https://scanner.tradingview.com/${type}/scan`, {
|
|
13
|
+
symbols: { tickers },
|
|
14
|
+
columns,
|
|
15
|
+
}, { validateStatus });
|
|
29
16
|
|
|
30
17
|
return data;
|
|
31
18
|
}
|
|
@@ -53,40 +40,40 @@ async function fetchScanData(tickers = [], type = '', columns = []) {
|
|
|
53
40
|
* }} Periods
|
|
54
41
|
*/
|
|
55
42
|
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
43
|
+
// /**
|
|
44
|
+
// * @typedef {string | 'forex' | 'crypto'
|
|
45
|
+
// * | 'america' | 'australia' | 'canada' | 'egypt'
|
|
46
|
+
// * | 'germany' | 'india' | 'israel' | 'italy'
|
|
47
|
+
// * | 'luxembourg' | 'poland' | 'sweden' | 'turkey'
|
|
48
|
+
// * | 'uk' | 'vietnam'} Screener
|
|
49
|
+
// * You can use `getScreener(exchange)` function for non-forex and non-crypto markets.
|
|
50
|
+
// */
|
|
64
51
|
|
|
65
52
|
module.exports = {
|
|
66
|
-
/**
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
*/
|
|
72
|
-
getScreener(exchange) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
},
|
|
53
|
+
// /**
|
|
54
|
+
// * Get a screener from an exchange
|
|
55
|
+
// * @function getScreener
|
|
56
|
+
// * @param {string} exchange Example: BINANCE, EURONEXT, NASDAQ
|
|
57
|
+
// * @returns {Screener}
|
|
58
|
+
// */
|
|
59
|
+
// getScreener(exchange) {
|
|
60
|
+
// const e = exchange.toUpperCase();
|
|
61
|
+
// if (['NASDAQ', 'NYSE', 'NYSE ARCA', 'OTC'].includes(e)) return 'america';
|
|
62
|
+
// if (['ASX'].includes(e)) return 'australia';
|
|
63
|
+
// if (['TSX', 'TSXV', 'CSE', 'NEO'].includes(e)) return 'canada';
|
|
64
|
+
// if (['EGX'].includes(e)) return 'egypt';
|
|
65
|
+
// if (['FWB', 'SWB', 'XETR'].includes(e)) return 'germany';
|
|
66
|
+
// if (['BSE', 'NSE'].includes(e)) return 'india';
|
|
67
|
+
// if (['TASE'].includes(e)) return 'israel';
|
|
68
|
+
// if (['MIL', 'MILSEDEX'].includes(e)) return 'italy';
|
|
69
|
+
// if (['LUXSE'].includes(e)) return 'luxembourg';
|
|
70
|
+
// if (['NEWCONNECT'].includes(e)) return 'poland';
|
|
71
|
+
// if (['NGM'].includes(e)) return 'sweden';
|
|
72
|
+
// if (['BIST'].includes(e)) return 'turkey';
|
|
73
|
+
// if (['LSE', 'LSIN'].includes(e)) return 'uk';
|
|
74
|
+
// if (['HNX'].includes(e)) return 'vietnam';
|
|
75
|
+
// return 'global';
|
|
76
|
+
// },
|
|
90
77
|
|
|
91
78
|
/**
|
|
92
79
|
* Get technical analysis
|
|
@@ -138,20 +125,25 @@ module.exports = {
|
|
|
138
125
|
* @returns {Promise<SearchMarketResult[]>} Search results
|
|
139
126
|
*/
|
|
140
127
|
async searchMarket(search, filter = '') {
|
|
141
|
-
const { data } = await
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
128
|
+
const { data } = await axios.get(
|
|
129
|
+
`https://symbol-search.tradingview.com/symbol_search/?text=${search.replace(/ /g, '%20')}&type=${filter}`,
|
|
130
|
+
{
|
|
131
|
+
validateStatus,
|
|
132
|
+
headers: {
|
|
133
|
+
origin: 'https://www.tradingview.com',
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
);
|
|
146
137
|
|
|
147
138
|
return data.map((s) => {
|
|
148
139
|
const exchange = s.exchange.split(' ')[0];
|
|
149
140
|
const id = `${exchange}:${s.symbol}`;
|
|
150
141
|
|
|
151
|
-
const screener = (['forex', 'crypto'].includes(s.type)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
);
|
|
142
|
+
// const screener = (['forex', 'crypto'].includes(s.type)
|
|
143
|
+
// ? s.type
|
|
144
|
+
// : this.getScreener(exchange)
|
|
145
|
+
// );
|
|
146
|
+
const screener = 'global';
|
|
155
147
|
|
|
156
148
|
return {
|
|
157
149
|
id,
|
|
@@ -189,17 +181,18 @@ module.exports = {
|
|
|
189
181
|
async searchIndicator(search = '') {
|
|
190
182
|
if (!builtInIndicList.length) {
|
|
191
183
|
await Promise.all(['standard', 'candlestick', 'fundamental'].map(async (type) => {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
184
|
+
const { data } = await axios.get(
|
|
185
|
+
`https://pine-facade.tradingview.com/pine-facade/list/?filter=${type}`,
|
|
186
|
+
{ validateStatus },
|
|
187
|
+
);
|
|
188
|
+
builtInIndicList.push(...data);
|
|
196
189
|
}));
|
|
197
190
|
}
|
|
198
191
|
|
|
199
|
-
const { data } = await
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
192
|
+
const { data } = await axios.get(
|
|
193
|
+
`https://www.tradingview.com/pubscripts-suggest-json/?search=${search.replace(/ /g, '%20')}`,
|
|
194
|
+
{ validateStatus },
|
|
195
|
+
);
|
|
203
196
|
|
|
204
197
|
function norm(str = '') {
|
|
205
198
|
return str.toUpperCase().replace(/[^A-Z]/g, '');
|
|
@@ -255,16 +248,10 @@ module.exports = {
|
|
|
255
248
|
async getIndicator(id, version = 'last') {
|
|
256
249
|
const indicID = id.replace(/ |%/g, '%25');
|
|
257
250
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
try {
|
|
264
|
-
data = JSON.parse(data);
|
|
265
|
-
} catch (e) {
|
|
266
|
-
throw new Error(`Inexistent or unsupported indicator: '${id}'`);
|
|
267
|
-
}
|
|
251
|
+
const { data } = await axios.get(
|
|
252
|
+
`https://pine-facade.tradingview.com/pine-facade/translate/${indicID}/${version}`,
|
|
253
|
+
{ validateStatus },
|
|
254
|
+
);
|
|
268
255
|
|
|
269
256
|
if (!data.success || !data.result.metaInfo || !data.result.metaInfo.inputs) {
|
|
270
257
|
throw new Error(`Inexistent or unsupported indicator: "${data.reason}"`);
|
|
@@ -342,6 +329,7 @@ module.exports = {
|
|
|
342
329
|
* @prop {number} notifications.following Notification from following accounts
|
|
343
330
|
* @prop {string} session User session
|
|
344
331
|
* @prop {string} sessionHash User session hash
|
|
332
|
+
* @prop {string} signature User session signature
|
|
345
333
|
* @prop {string} privateChannel User private channel
|
|
346
334
|
* @prop {string} authToken User auth token
|
|
347
335
|
* @prop {Date} joinDate Account creation date
|
|
@@ -357,21 +345,20 @@ module.exports = {
|
|
|
357
345
|
* @returns {Promise<User>} Token
|
|
358
346
|
*/
|
|
359
347
|
async loginUser(username, password, remember = true, UA = 'TWAPI/3.0') {
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
referer: 'https://www.tradingview.com',
|
|
371
|
-
'Content-Type': `multipart/form-data; boundary=${formData.boundary}`,
|
|
372
|
-
'User-agent': `${UA} (${os.version()}; ${os.platform()}; ${os.arch()})`,
|
|
348
|
+
const { data, headers } = await axios.post(
|
|
349
|
+
'https://www.tradingview.com/accounts/signin/',
|
|
350
|
+
`username=${username}&password=${password}${remember ? '&remember=on' : ''}`,
|
|
351
|
+
{
|
|
352
|
+
validateStatus,
|
|
353
|
+
headers: {
|
|
354
|
+
referer: 'https://www.tradingview.com',
|
|
355
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
356
|
+
'User-agent': `${UA} (${os.version()}; ${os.platform()}; ${os.arch()})`,
|
|
357
|
+
},
|
|
373
358
|
},
|
|
374
|
-
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
const cookies = headers['set-cookie'];
|
|
375
362
|
|
|
376
363
|
if (data.error) throw new Error(data.error);
|
|
377
364
|
|
|
@@ -408,43 +395,36 @@ module.exports = {
|
|
|
408
395
|
* @returns {Promise<User>} Token
|
|
409
396
|
*/
|
|
410
397
|
async getUser(session, signature = '', location = 'https://www.tradingview.com/') {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
res.on('data', (d) => { rs += d; });
|
|
417
|
-
res.on('end', async () => {
|
|
418
|
-
if (res.headers.location && location !== res.headers.location) {
|
|
419
|
-
cb(await module.exports.getUser(session, signature, res.headers.location));
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
if (rs.includes('auth_token')) {
|
|
423
|
-
cb({
|
|
424
|
-
id: /"id":([0-9]{1,10}),/.exec(rs)[1],
|
|
425
|
-
username: /"username":"(.*?)"/.exec(rs)[1],
|
|
426
|
-
firstName: /"first_name":"(.*?)"/.exec(rs)[1],
|
|
427
|
-
lastName: /"last_name":"(.*?)"/.exec(rs)[1],
|
|
428
|
-
reputation: parseFloat(/"reputation":(.*?),/.exec(rs)[1] || 0),
|
|
429
|
-
following: parseFloat(/,"following":([0-9]*?),/.exec(rs)[1] || 0),
|
|
430
|
-
followers: parseFloat(/,"followers":([0-9]*?),/.exec(rs)[1] || 0),
|
|
431
|
-
notifications: {
|
|
432
|
-
following: parseFloat(/"notification_count":\{"following":([0-9]*),/.exec(rs)[1] || 0),
|
|
433
|
-
user: parseFloat(/"notification_count":\{"following":[0-9]*,"user":([0-9]*)/.exec(rs)[1] || 0),
|
|
434
|
-
},
|
|
435
|
-
session,
|
|
436
|
-
signature,
|
|
437
|
-
sessionHash: /"session_hash":"(.*?)"/.exec(rs)[1],
|
|
438
|
-
privateChannel: /"private_channel":"(.*?)"/.exec(rs)[1],
|
|
439
|
-
authToken: /"auth_token":"(.*?)"/.exec(rs)[1],
|
|
440
|
-
joinDate: new Date(/"date_joined":"(.*?)"/.exec(rs)[1] || 0),
|
|
441
|
-
});
|
|
442
|
-
} else err(new Error('Wrong or expired sessionid/signature'));
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
res.on('error', err);
|
|
446
|
-
}).end();
|
|
398
|
+
const { data } = await axios.get(location, {
|
|
399
|
+
validateStatus,
|
|
400
|
+
headers: {
|
|
401
|
+
cookie: `sessionid=${session}${signature ? `;sessionid_sign=${signature};` : ''}`,
|
|
402
|
+
},
|
|
447
403
|
});
|
|
404
|
+
|
|
405
|
+
if (data.includes('auth_token')) {
|
|
406
|
+
return {
|
|
407
|
+
id: /"id":([0-9]{1,10}),/.exec(data)?.[1],
|
|
408
|
+
username: /"username":"(.*?)"/.exec(data)?.[1],
|
|
409
|
+
firstName: /"first_name":"(.*?)"/.exec(data)?.[1],
|
|
410
|
+
lastName: /"last_name":"(.*?)"/.exec(data)?.[1],
|
|
411
|
+
reputation: parseFloat(/"reputation":(.*?),/.exec(data)?.[1] || 0),
|
|
412
|
+
following: parseFloat(/,"following":([0-9]*?),/.exec(data)?.[1] || 0),
|
|
413
|
+
followers: parseFloat(/,"followers":([0-9]*?),/.exec(data)?.[1] || 0),
|
|
414
|
+
notifications: {
|
|
415
|
+
following: parseFloat(/"notification_count":\{"following":([0-9]*),/.exec(data)?.[1] || 0),
|
|
416
|
+
user: parseFloat(/"notification_count":\{"following":[0-9]*,"user":([0-9]*)/.exec(data)?.[1] || 0),
|
|
417
|
+
},
|
|
418
|
+
session,
|
|
419
|
+
signature,
|
|
420
|
+
sessionHash: /"session_hash":"(.*?)"/.exec(data)?.[1],
|
|
421
|
+
privateChannel: /"private_channel":"(.*?)"/.exec(data)?.[1],
|
|
422
|
+
authToken: /"auth_token":"(.*?)"/.exec(data)?.[1],
|
|
423
|
+
joinDate: new Date(/"date_joined":"(.*?)"/.exec(data)?.[1] || 0),
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
throw new Error('Wrong or expired sessionid/signature');
|
|
448
428
|
},
|
|
449
429
|
|
|
450
430
|
/**
|
|
@@ -455,44 +435,29 @@ module.exports = {
|
|
|
455
435
|
* @returns {Promise<SearchIndicatorResult[]>} Search results
|
|
456
436
|
*/
|
|
457
437
|
async getPrivateIndicators(session, signature = '') {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
res.on('data', (d) => { rs += d; });
|
|
464
|
-
res.on('end', async () => {
|
|
465
|
-
try {
|
|
466
|
-
rs = JSON.parse(rs);
|
|
467
|
-
} catch (error) {
|
|
468
|
-
err(new Error('Can\'t parse private indicator list'));
|
|
469
|
-
return;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
cb(rs.map((ind) => ({
|
|
473
|
-
id: ind.scriptIdPart,
|
|
474
|
-
version: ind.version,
|
|
475
|
-
name: ind.scriptName,
|
|
476
|
-
author: {
|
|
477
|
-
id: -1,
|
|
478
|
-
username: '@ME@',
|
|
479
|
-
},
|
|
480
|
-
image: ind.imageUrl,
|
|
481
|
-
access: 'private',
|
|
482
|
-
source: ind.scriptSource,
|
|
483
|
-
type: (ind.extra && ind.extra.kind) ? ind.extra.kind : 'study',
|
|
484
|
-
get() {
|
|
485
|
-
return module.exports.getIndicator(ind.scriptIdPart, ind.version);
|
|
486
|
-
},
|
|
487
|
-
getManager() {
|
|
488
|
-
return new PinePermManager(ind.scriptIdPart);
|
|
489
|
-
},
|
|
490
|
-
})));
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
res.on('error', err);
|
|
494
|
-
}).end();
|
|
438
|
+
const { data } = await axios.get('https://pine-facade.tradingview.com/pine-facade/list?filter=saved', {
|
|
439
|
+
validateStatus,
|
|
440
|
+
headers: {
|
|
441
|
+
cookie: `sessionid=${session}${signature ? `;sessionid_sign=${signature};` : ''}`,
|
|
442
|
+
},
|
|
495
443
|
});
|
|
444
|
+
|
|
445
|
+
return data.map((ind) => ({
|
|
446
|
+
id: ind.scriptIdPart,
|
|
447
|
+
version: ind.version,
|
|
448
|
+
name: ind.scriptName,
|
|
449
|
+
author: {
|
|
450
|
+
id: -1,
|
|
451
|
+
username: '@ME@',
|
|
452
|
+
},
|
|
453
|
+
image: ind.imageUrl,
|
|
454
|
+
access: 'private',
|
|
455
|
+
source: ind.scriptSource,
|
|
456
|
+
type: (ind.extra && ind.extra.kind) ? ind.extra.kind : 'study',
|
|
457
|
+
get() {
|
|
458
|
+
return module.exports.getIndicator(ind.scriptIdPart, ind.version);
|
|
459
|
+
},
|
|
460
|
+
}));
|
|
496
461
|
},
|
|
497
462
|
|
|
498
463
|
/**
|
|
@@ -500,6 +465,7 @@ module.exports = {
|
|
|
500
465
|
* @typedef {Object} UserCredentials
|
|
501
466
|
* @prop {number} id User ID
|
|
502
467
|
* @prop {string} session User session ('sessionid' cookie)
|
|
468
|
+
* @prop {string} [signature] User session signature ('sessionid_sign' cookie)
|
|
503
469
|
*/
|
|
504
470
|
|
|
505
471
|
/**
|
|
@@ -510,16 +476,23 @@ module.exports = {
|
|
|
510
476
|
* @returns {Promise<string>} Token
|
|
511
477
|
*/
|
|
512
478
|
async getChartToken(layout, credentials = {}) {
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
479
|
+
const { id, session, signature } = (
|
|
480
|
+
credentials.id && credentials.session
|
|
481
|
+
? credentials
|
|
482
|
+
: { id: -1, session: null, signature: null }
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
const { data } = await axios.get(
|
|
486
|
+
`https://www.tradingview.com/chart-token/?image_url=${layout}&user_id=${id}`,
|
|
487
|
+
{
|
|
488
|
+
validateStatus,
|
|
489
|
+
headers: {
|
|
490
|
+
cookie: session
|
|
491
|
+
? `sessionid=${session}${signature ? `;sessionid_sign=${signature};` : ''}`
|
|
492
|
+
: '',
|
|
493
|
+
},
|
|
494
|
+
},
|
|
495
|
+
);
|
|
523
496
|
|
|
524
497
|
if (!data.token) throw new Error('Wrong layout or credentials');
|
|
525
498
|
|
|
@@ -557,18 +530,21 @@ module.exports = {
|
|
|
557
530
|
* @param {number} [chartID] Chart ID
|
|
558
531
|
* @returns {Promise<Drawing[]>} Drawings
|
|
559
532
|
*/
|
|
560
|
-
async getDrawings(layout, symbol = '', credentials = {}, chartID =
|
|
533
|
+
async getDrawings(layout, symbol = '', credentials = {}, chartID = '_shared') {
|
|
561
534
|
const chartToken = await module.exports.getChartToken(layout, credentials);
|
|
562
|
-
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
535
|
+
|
|
536
|
+
const { data } = await axios.get(
|
|
537
|
+
`https://charts-storage.tradingview.com/charts-storage/get/layout/${
|
|
538
|
+
layout
|
|
539
|
+
}/sources?chart_id=${
|
|
540
|
+
chartID
|
|
541
|
+
}&jwt=${
|
|
542
|
+
chartToken
|
|
543
|
+
}${
|
|
544
|
+
(symbol ? `&symbol=${symbol}` : '')
|
|
545
|
+
}`,
|
|
546
|
+
{ validateStatus },
|
|
547
|
+
);
|
|
572
548
|
|
|
573
549
|
if (!data.payload) throw new Error('Wrong layout, user credentials, or chart id.');
|
|
574
550
|
|
package/vite.config.js
ADDED
package/src/FormData.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/** @class */
|
|
2
|
-
class FormData {
|
|
3
|
-
#string = '';
|
|
4
|
-
|
|
5
|
-
#boundary = '';
|
|
6
|
-
|
|
7
|
-
get boundary() {
|
|
8
|
-
return this.#boundary;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
constructor() {
|
|
12
|
-
const random = (Math.random() * 10 ** 20).toString(36);
|
|
13
|
-
this.#boundary = `${random}`;
|
|
14
|
-
this.#string = `--${this.boundary}`;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Adds a property to the FormData object
|
|
19
|
-
* @param {string} key Property key
|
|
20
|
-
* @param {string} value Property value
|
|
21
|
-
*/
|
|
22
|
-
append(key, value) {
|
|
23
|
-
this.#string += `\r\nContent-Disposition: form-data; name="${key}"`;
|
|
24
|
-
this.#string += `\r\n\r\n${value}`;
|
|
25
|
-
this.#string += `\r\n--${this.boundary}`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* @returns {string}
|
|
30
|
-
*/
|
|
31
|
-
toString() {
|
|
32
|
-
return `${this.#string}--`;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
module.exports = FormData;
|
package/src/request.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
const https = require('https');
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @param {https.RequestOptions} options HTTPS Request options
|
|
5
|
-
* @param {boolean} [raw] Get raw or JSON data
|
|
6
|
-
* @param {string} [content] Request body content
|
|
7
|
-
* @returns {Promise<{ data: (string | object | array), cookies: string[] }>} Result
|
|
8
|
-
*/
|
|
9
|
-
function request(options = {}, raw = false, content = '') {
|
|
10
|
-
return new Promise((cb, err) => {
|
|
11
|
-
const req = https.request(options, (res) => {
|
|
12
|
-
let data = '';
|
|
13
|
-
res.on('data', (c) => { data += c; });
|
|
14
|
-
res.on('end', () => {
|
|
15
|
-
if (raw) {
|
|
16
|
-
cb({ data, cookies: res.headers['set-cookie'] });
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
data = JSON.parse(data);
|
|
22
|
-
} catch (error) {
|
|
23
|
-
console.log(data);
|
|
24
|
-
err(new Error('Can\'t parse server response'));
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
cb({ data, cookies: res.headers['set-cookie'] });
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
req.on('error', err);
|
|
33
|
-
req.end(content);
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
module.exports = request;
|
package/test.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-continue */
|
|
2
|
-
/* eslint-disable no-await-in-loop */
|
|
3
|
-
/* eslint-disable import/no-dynamic-require */
|
|
4
|
-
/* eslint-disable no-restricted-syntax */
|
|
5
|
-
/* eslint-disable global-require */
|
|
6
|
-
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
require('@mathieuc/console')(
|
|
9
|
-
'§', // Character you want to use (defaut: '§')
|
|
10
|
-
true, // Active timestamp (defaut: false)
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
const TESTS = {};
|
|
14
|
-
|
|
15
|
-
function err(name) {
|
|
16
|
-
return (...msg) => {
|
|
17
|
-
TESTS[name].errors += 1;
|
|
18
|
-
console.error('§30§101 > §0', ...msg);
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function warn(name) {
|
|
23
|
-
return (...msg) => {
|
|
24
|
-
TESTS[name].warnings += 1;
|
|
25
|
-
console.warn('§30§103 > §0', ...msg);
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function log() {
|
|
30
|
-
return (...msg) => console.log('§30§107 > §0', ...msg);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function success() {
|
|
34
|
-
return (...msg) => console.info('§30§102 > §0', ...msg);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
(async () => {
|
|
38
|
-
console.info('§90§30§104 ==== Starting tests ==== §0');
|
|
39
|
-
|
|
40
|
-
for (const file of fs.readdirSync('./tests').filter((f) => f.endsWith('.js'))) {
|
|
41
|
-
if (process.argv[2] && !file.startsWith(process.argv[2])) continue;
|
|
42
|
-
const test = require(`./tests/${file}`);
|
|
43
|
-
const name = file.replace(/\.js/g, '');
|
|
44
|
-
TESTS[name] = { errors: 0, warnings: 0 };
|
|
45
|
-
console.log(`§90§30§107 ${name}`);
|
|
46
|
-
await new Promise((cb) => test(log(), success(), warn(name), err(name), cb));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
console.info('§90§30§104 ==== ALL TESTS DONE ==== §0\n');
|
|
50
|
-
|
|
51
|
-
let errored = false;
|
|
52
|
-
|
|
53
|
-
Object.keys(TESTS).forEach((t) => {
|
|
54
|
-
let color = '2';
|
|
55
|
-
if (TESTS[t].warnings) color = '3';
|
|
56
|
-
if (TESTS[t].errors) {
|
|
57
|
-
color = '1';
|
|
58
|
-
errored = true;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
console.info(`§90§30§10${color} ${t} §0 §91E§0: ${TESTS[t].errors}§0 §93W§0: ${TESTS[t].warnings}`);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
process.exit(errored ? 1 : 0);
|
|
65
|
-
})();
|
|
66
|
-
|
|
67
|
-
setTimeout(() => {
|
|
68
|
-
console.log('§30§101 TIMEOUT §0');
|
|
69
|
-
throw new Error('Timeout');
|
|
70
|
-
}, 60000);
|
package/tests/01. Search.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
const TradingView = require('../main');
|
|
2
|
-
|
|
3
|
-
module.exports = async (log, success, warn, err, cb) => {
|
|
4
|
-
log('Searching market: \'BINANCE:\'');
|
|
5
|
-
const markets = await TradingView.searchMarket('BINANCE:');
|
|
6
|
-
success('Found', markets.length, 'markets');
|
|
7
|
-
|
|
8
|
-
log('Searching indicator: \'RSI\'');
|
|
9
|
-
const indicators = await TradingView.searchIndicator('RSI');
|
|
10
|
-
success('Found', indicators.length, 'indicators');
|
|
11
|
-
|
|
12
|
-
if (markets.length < 10 || indicators.length < 10) {
|
|
13
|
-
err('Not enough results');
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
cb();
|
|
17
|
-
};
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
const TradingView = require('../main');
|
|
2
|
-
|
|
3
|
-
module.exports = (log, success, warn, err, cb) => {
|
|
4
|
-
const client = new TradingView.Client();
|
|
5
|
-
|
|
6
|
-
client.onError((...error) => {
|
|
7
|
-
err('Client error', ...error);
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
const quoteSession = new client.Session.Quote({
|
|
11
|
-
fields: 'all',
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
const BTC = new quoteSession.Market('BTCEUR');
|
|
15
|
-
|
|
16
|
-
BTC.onLoaded(() => {
|
|
17
|
-
success('BTCEUR LOADED');
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
const keys = [
|
|
21
|
-
'volume', 'update_mode', 'type', 'timezone',
|
|
22
|
-
'short_name', 'rtc_time', 'rtc', 'rchp', 'ch',
|
|
23
|
-
'rch', 'provider_id', 'pro_name', 'pricescale',
|
|
24
|
-
'prev_close_price', 'original_name', 'lp',
|
|
25
|
-
'open_price', 'minmove2', 'minmov', 'lp_time',
|
|
26
|
-
'low_price', 'is_tradable', 'high_price',
|
|
27
|
-
'fractional', 'exchange', 'description',
|
|
28
|
-
'current_session', 'currency_code', 'chp',
|
|
29
|
-
'currency-logoid', 'base-currency-logoid',
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
BTC.onData(async (data) => {
|
|
33
|
-
const rsKeys = Object.keys(data);
|
|
34
|
-
success('BTCEUR DATA');
|
|
35
|
-
if (rsKeys.length <= 2) return;
|
|
36
|
-
|
|
37
|
-
keys.forEach((k) => {
|
|
38
|
-
if (!rsKeys.includes(k)) {
|
|
39
|
-
err(`Missing '${k}' key in`, rsKeys);
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
quoteSession.delete();
|
|
44
|
-
await client.end();
|
|
45
|
-
cb();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
BTC.onError((...error) => {
|
|
49
|
-
err('BTCEUR ERROR:', error);
|
|
50
|
-
});
|
|
51
|
-
};
|