@mathieuc/tradingview 3.4.0 → 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 +3 -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/client.js +5 -1
- package/src/miscRequests.js +175 -190
- 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 -146
- package/tests/10. Authenticated.js +0 -95
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,26 +345,28 @@ 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
|
|
|
378
|
-
const
|
|
379
|
-
const session = (
|
|
365
|
+
const sessionCookie = cookies.find((c) => c.includes('sessionid='));
|
|
366
|
+
const session = (sessionCookie.match(/sessionid=(.*?);/) ?? [])[1];
|
|
367
|
+
|
|
368
|
+
const signCookie = cookies.find((c) => c.includes('sessionid_sign='));
|
|
369
|
+
const signature = (signCookie.match(/sessionid_sign=(.*?);/) ?? [])[1];
|
|
380
370
|
|
|
381
371
|
return {
|
|
382
372
|
id: data.user.id,
|
|
@@ -388,6 +378,7 @@ module.exports = {
|
|
|
388
378
|
followers: data.user.followers,
|
|
389
379
|
notifications: data.user.notification_count,
|
|
390
380
|
session,
|
|
381
|
+
signature,
|
|
391
382
|
sessionHash: data.user.session_hash,
|
|
392
383
|
privateChannel: data.user.private_channel,
|
|
393
384
|
authToken: data.user.auth_token,
|
|
@@ -399,93 +390,74 @@ module.exports = {
|
|
|
399
390
|
* Get user from 'sessionid' cookie
|
|
400
391
|
* @function getUser
|
|
401
392
|
* @param {string} session User 'sessionid' cookie
|
|
393
|
+
* @param {string} [signature] User 'sessionid_sign' cookie
|
|
402
394
|
* @param {string} [location] Auth page location (For france: https://fr.tradingview.com/)
|
|
403
395
|
* @returns {Promise<User>} Token
|
|
404
396
|
*/
|
|
405
|
-
async getUser(session, location = 'https://www.tradingview.com/') {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
res.on('data', (d) => { rs += d; });
|
|
412
|
-
res.on('end', async () => {
|
|
413
|
-
if (res.headers.location && location !== res.headers.location) {
|
|
414
|
-
cb(await module.exports.getUser(session, res.headers.location));
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
if (rs.includes('auth_token')) {
|
|
418
|
-
cb({
|
|
419
|
-
id: /"id":([0-9]{1,10}),/.exec(rs)[1],
|
|
420
|
-
username: /"username":"(.*?)"/.exec(rs)[1],
|
|
421
|
-
firstName: /"first_name":"(.*?)"/.exec(rs)[1],
|
|
422
|
-
lastName: /"last_name":"(.*?)"/.exec(rs)[1],
|
|
423
|
-
reputation: parseFloat(/"reputation":(.*?),/.exec(rs)[1] || 0),
|
|
424
|
-
following: parseFloat(/,"following":([0-9]*?),/.exec(rs)[1] || 0),
|
|
425
|
-
followers: parseFloat(/,"followers":([0-9]*?),/.exec(rs)[1] || 0),
|
|
426
|
-
notifications: {
|
|
427
|
-
following: parseFloat(/"notification_count":\{"following":([0-9]*),/.exec(rs)[1] || 0),
|
|
428
|
-
user: parseFloat(/"notification_count":\{"following":[0-9]*,"user":([0-9]*)/.exec(rs)[1] || 0),
|
|
429
|
-
},
|
|
430
|
-
session,
|
|
431
|
-
sessionHash: /"session_hash":"(.*?)"/.exec(rs)[1],
|
|
432
|
-
privateChannel: /"private_channel":"(.*?)"/.exec(rs)[1],
|
|
433
|
-
authToken: /"auth_token":"(.*?)"/.exec(rs)[1],
|
|
434
|
-
joinDate: new Date(/"date_joined":"(.*?)"/.exec(rs)[1] || 0),
|
|
435
|
-
});
|
|
436
|
-
} else err(new Error('Wrong or expired sessionid'));
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
res.on('error', err);
|
|
440
|
-
}).end();
|
|
397
|
+
async getUser(session, signature = '', location = 'https://www.tradingview.com/') {
|
|
398
|
+
const { data } = await axios.get(location, {
|
|
399
|
+
validateStatus,
|
|
400
|
+
headers: {
|
|
401
|
+
cookie: `sessionid=${session}${signature ? `;sessionid_sign=${signature};` : ''}`,
|
|
402
|
+
},
|
|
441
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');
|
|
442
428
|
},
|
|
443
429
|
|
|
444
430
|
/**
|
|
445
431
|
* Get user's private indicators from a 'sessionid' cookie
|
|
446
432
|
* @function getPrivateIndicators
|
|
447
433
|
* @param {string} session User 'sessionid' cookie
|
|
434
|
+
* @param {string} [signature] User 'sessionid_sign' cookie
|
|
448
435
|
* @returns {Promise<SearchIndicatorResult[]>} Search results
|
|
449
436
|
*/
|
|
450
|
-
async getPrivateIndicators(session) {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
res.on('data', (d) => { rs += d; });
|
|
457
|
-
res.on('end', async () => {
|
|
458
|
-
try {
|
|
459
|
-
rs = JSON.parse(rs);
|
|
460
|
-
} catch (error) {
|
|
461
|
-
err(new Error('Can\'t parse private indicator list'));
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
cb(rs.map((ind) => ({
|
|
466
|
-
id: ind.scriptIdPart,
|
|
467
|
-
version: ind.version,
|
|
468
|
-
name: ind.scriptName,
|
|
469
|
-
author: {
|
|
470
|
-
id: -1,
|
|
471
|
-
username: '@ME@',
|
|
472
|
-
},
|
|
473
|
-
image: ind.imageUrl,
|
|
474
|
-
access: 'private',
|
|
475
|
-
source: ind.scriptSource,
|
|
476
|
-
type: (ind.extra && ind.extra.kind) ? ind.extra.kind : 'study',
|
|
477
|
-
get() {
|
|
478
|
-
return module.exports.getIndicator(ind.scriptIdPart, ind.version);
|
|
479
|
-
},
|
|
480
|
-
getManager() {
|
|
481
|
-
return new PinePermManager(ind.scriptIdPart);
|
|
482
|
-
},
|
|
483
|
-
})));
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
res.on('error', err);
|
|
487
|
-
}).end();
|
|
437
|
+
async getPrivateIndicators(session, signature = '') {
|
|
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
|
+
},
|
|
488
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
|
+
}));
|
|
489
461
|
},
|
|
490
462
|
|
|
491
463
|
/**
|
|
@@ -493,25 +465,34 @@ module.exports = {
|
|
|
493
465
|
* @typedef {Object} UserCredentials
|
|
494
466
|
* @prop {number} id User ID
|
|
495
467
|
* @prop {string} session User session ('sessionid' cookie)
|
|
468
|
+
* @prop {string} [signature] User session signature ('sessionid_sign' cookie)
|
|
496
469
|
*/
|
|
497
470
|
|
|
498
471
|
/**
|
|
499
472
|
* Get a chart token from a layout ID and the user credentials if the layout is not public
|
|
500
473
|
* @function getChartToken
|
|
501
474
|
* @param {string} layout The layout ID found in the layout URL (Like: 'XXXXXXXX')
|
|
502
|
-
* @param {UserCredentials} [credentials] User credentials (id + session)
|
|
475
|
+
* @param {UserCredentials} [credentials] User credentials (id + session + [signature])
|
|
503
476
|
* @returns {Promise<string>} Token
|
|
504
477
|
*/
|
|
505
478
|
async getChartToken(layout, credentials = {}) {
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
+
);
|
|
515
496
|
|
|
516
497
|
if (!data.token) throw new Error('Wrong layout or credentials');
|
|
517
498
|
|
|
@@ -545,21 +526,25 @@ module.exports = {
|
|
|
545
526
|
* @function getDrawings
|
|
546
527
|
* @param {string} layout The layout ID found in the layout URL (Like: 'XXXXXXXX')
|
|
547
528
|
* @param {string | ''} [symbol] Market filter (Like: 'BINANCE:BTCEUR')
|
|
548
|
-
* @param {UserCredentials} [credentials] User credentials (id + session)
|
|
529
|
+
* @param {UserCredentials} [credentials] User credentials (id + session + [signature])
|
|
549
530
|
* @param {number} [chartID] Chart ID
|
|
550
531
|
* @returns {Promise<Drawing[]>} Drawings
|
|
551
532
|
*/
|
|
552
|
-
async getDrawings(layout, symbol = '', credentials = {}, chartID =
|
|
533
|
+
async getDrawings(layout, symbol = '', credentials = {}, chartID = '_shared') {
|
|
553
534
|
const chartToken = await module.exports.getChartToken(layout, credentials);
|
|
554
|
-
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
}&jwt=${
|
|
561
|
-
|
|
562
|
-
|
|
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
|
+
);
|
|
563
548
|
|
|
564
549
|
if (!data.payload) throw new Error('Wrong layout, user credentials, or chart id.');
|
|
565
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
|
-
};
|