@mathieuc/tradingview 3.3.3 → 3.4.1

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 CHANGED
@@ -20,5 +20,7 @@ module.exports = {
20
20
  devDependencies: ['./test.js', './tests/**'],
21
21
  },
22
22
  ],
23
+ 'no-restricted-syntax': 'off',
24
+ 'no-await-in-loop': 'off',
23
25
  },
24
26
  };
package/.gitattributes CHANGED
@@ -1,2 +1,2 @@
1
- # Auto detect text files and perform LF normalization
2
- * text=auto
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
package/README.md CHANGED
@@ -1,66 +1,66 @@
1
- # TradingView API [![GitHub stars](https://img.shields.io/github/stars/Mathieu2301/TradingView-API.svg?style=social&label=Star&maxAge=2592000)](https://GitHub.com/Mathieu2301/TradingView-API/stargazers/)
2
-
3
- [![Tests](https://github.com/Mathieu2301/TradingView-API/actions/workflows/node.js.yml/badge.svg)](https://github.com/Mathieu2301/TradingView-API/actions/workflows/node.js.yml)
4
- [![CodeFactor](https://www.codefactor.io/repository/github/mathieu2301/tradingview-api/badge/main)](https://www.codefactor.io/repository/github/mathieu2301/tradingview-api/overview/main)
5
- [![GitHub latest commit](https://img.shields.io/github/last-commit/Mathieu2301/TradingView-API)](https://GitHub.com/Mathieu2301/TradingView-API/commit/)
6
- [![Npm package yearly downloads](https://badgen.net/npm/dt/@mathieuc/tradingview)](https://npmjs.com/@mathieuc/tradingview)
7
- [![Minimum node.js version](https://badgen.net/npm/node/@mathieuc/tradingview)](https://npmjs.com/@mathieuc/tradingview)
8
- [![Npm package version](https://badgen.net/npm/v/@mathieuc/tradingview)](https://npmjs.com/package/@mathieuc/tradingview)
9
-
10
- Get realtime market prices and indicator values from Tradingview !
11
-
12
- ## Features
13
-
14
- - [x] Premium features
15
- - [x] Automatically backtest many strategies and try many settings in a very little time
16
- - [x] Get drawings you made on your chart
17
- - [x] Works with invite-only indicators
18
- - [x] Unlimited simultaneous indicators
19
- - [x] Realtime
20
- - [x] Get TradingView's technical analysis
21
- - [x] Replay mode + Fake Replay mode (for free plan)
22
- - [x] Get values from a specific date range
23
- - [ ] TradingView socket server emulation
24
- - [ ] Interract with public chats
25
- - [ ] Get Screener top values
26
- - [ ] Get Hotlists
27
- - [ ] Get Calendar
28
- - IF YOU WANT A FEATURE, ASK ME !
29
-
30
- ## Possibilities
31
-
32
- - Trading bot
33
- - Discord alerts
34
- - Hard backtest
35
- - Machine Learning based indicator
36
- - Free replay mode for all timeframes
37
-
38
- ___
39
-
40
- ## Installation
41
-
42
- Stable version:
43
-
44
- ```ruby
45
- npm i @mathieuc/tradingview
46
- ```
47
-
48
- Last version:
49
-
50
- ```ruby
51
- npm i github:Mathieu2301/TradingView-API
52
- ```
53
-
54
- ## Examples
55
-
56
- You can find all the examples and snippets in `./examples` folder.
57
-
58
- ## Before opening an issue
59
-
60
- Please look at examples and previously resolved issues before opening a new one. I can't help everyone (especially for questions that are not library related but JavaScript related). Thank you for your understanding.
61
- ___
62
-
63
- ## Problems
64
-
65
- If you have errors in console or unwanted behavior,
66
- please create an issue [here](https://github.com/Mathieu2301/Tradingview-API/issues).
1
+ # TradingView API [![GitHub stars](https://img.shields.io/github/stars/Mathieu2301/TradingView-API.svg?style=social&label=Star&maxAge=2592000)](https://GitHub.com/Mathieu2301/TradingView-API/stargazers/)
2
+
3
+ [![Tests](https://github.com/Mathieu2301/TradingView-API/actions/workflows/node.js.yml/badge.svg)](https://github.com/Mathieu2301/TradingView-API/actions/workflows/node.js.yml)
4
+ [![CodeFactor](https://www.codefactor.io/repository/github/mathieu2301/tradingview-api/badge/main)](https://www.codefactor.io/repository/github/mathieu2301/tradingview-api/overview/main)
5
+ [![GitHub latest commit](https://img.shields.io/github/last-commit/Mathieu2301/TradingView-API)](https://GitHub.com/Mathieu2301/TradingView-API/commit/)
6
+ [![Npm package yearly downloads](https://badgen.net/npm/dt/@mathieuc/tradingview)](https://npmjs.com/@mathieuc/tradingview)
7
+ [![Minimum node.js version](https://badgen.net/npm/node/@mathieuc/tradingview)](https://npmjs.com/@mathieuc/tradingview)
8
+ [![Npm package version](https://badgen.net/npm/v/@mathieuc/tradingview)](https://npmjs.com/package/@mathieuc/tradingview)
9
+
10
+ Get realtime market prices and indicator values from Tradingview !
11
+
12
+ ## Features
13
+
14
+ - [x] Premium features
15
+ - [x] Automatically backtest many strategies and try many settings in a very little time
16
+ - [x] Get drawings you made on your chart
17
+ - [x] Works with invite-only indicators
18
+ - [x] Unlimited simultaneous indicators
19
+ - [x] Realtime
20
+ - [x] Get TradingView's technical analysis
21
+ - [x] Replay mode + Fake Replay mode (for free plan)
22
+ - [x] Get values from a specific date range
23
+ - [ ] TradingView socket server emulation
24
+ - [ ] Interract with public chats
25
+ - [ ] Get Screener top values
26
+ - [ ] Get Hotlists
27
+ - [ ] Get Calendar
28
+ - IF YOU WANT A FEATURE, ASK ME !
29
+
30
+ ## Possibilities
31
+
32
+ - Trading bot
33
+ - Discord alerts
34
+ - Hard backtest
35
+ - Machine Learning based indicator
36
+ - Free replay mode for all timeframes
37
+
38
+ ___
39
+
40
+ ## Installation
41
+
42
+ Stable version:
43
+
44
+ ```ruby
45
+ npm i @mathieuc/tradingview
46
+ ```
47
+
48
+ Last version:
49
+
50
+ ```ruby
51
+ npm i github:Mathieu2301/TradingView-API
52
+ ```
53
+
54
+ ## Examples
55
+
56
+ You can find all the examples and snippets in `./examples` folder.
57
+
58
+ ## Before opening an issue
59
+
60
+ Please look at examples and previously resolved issues before opening a new one. I can't help everyone (especially for questions that are not library related but JavaScript related). Thank you for your understanding.
61
+ ___
62
+
63
+ ## Problems
64
+
65
+ If you have errors in console or unwanted behavior,
66
+ please create an issue [here](https://github.com/Mathieu2301/Tradingview-API/issues).
@@ -1,147 +1,147 @@
1
- const TradingView = require('../main');
2
-
3
- /*
4
- This example tests many types of errors
5
- */
6
-
7
- const client = new TradingView.Client(); // Creates a websocket client
8
-
9
- const tests = [
10
- (next) => { /* Testing "Credentials error" */
11
- console.info('\nTesting "Credentials error" error:');
12
-
13
- const client2 = new TradingView.Client({
14
- token: 'FAKE_CREDENTIALS', // Set wrong credentials
15
- });
16
-
17
- client2.onError((...err) => {
18
- console.error(' => Client error:', err);
19
- client2.end();
20
- next();
21
- });
22
- },
23
-
24
- (next) => { /* Testing "Invalid symbol" */
25
- console.info('\nTesting "Invalid symbol" error:');
26
-
27
- const chart = new client.Session.Chart();
28
- chart.onError((...err) => { // Listen for errors
29
- console.error(' => Chart error:', err);
30
- chart.delete();
31
- next();
32
- });
33
-
34
- chart.setMarket('XXXXX'); // Set a wrong market
35
- },
36
-
37
- (next) => { /* Testing "Invalid timezone" */
38
- console.info('\nTesting "Invalid timezone" error:');
39
-
40
- const chart = new client.Session.Chart();
41
- chart.onError((...err) => { // Listen for errors
42
- console.error(' => Chart error:', err);
43
- next();
44
- });
45
-
46
- chart.setMarket('BINANCE:BTCEUR'); // Set a market
47
- chart.setTimezone('Nowhere/Nowhere'); // Set a fake timezone
48
- },
49
-
50
- (next) => { /* Testing "Custom timeframe" */
51
- console.info('\nTesting "Custom timeframe" error:');
52
-
53
- const chart = new client.Session.Chart();
54
- chart.onError((...err) => { // Listen for errors
55
- console.error(' => Chart error:', err);
56
- chart.delete();
57
- next();
58
- });
59
-
60
- chart.setMarket('BINANCE:BTCEUR', { // Set a market
61
- timeframe: '20', // Set a custom timeframe
62
- /*
63
- Timeframe '20' isn't available because we are
64
- not logged in as a premium TradingView account
65
- */
66
- });
67
- },
68
-
69
- (next) => { /* Testing "Invalid timeframe" */
70
- console.info('\nTesting "Invalid timeframe" error:');
71
-
72
- const chart = new client.Session.Chart();
73
- chart.onError((...err) => { // Listen for errors
74
- console.error(' => Chart error:', err);
75
- next();
76
- });
77
-
78
- chart.setMarket('BINANCE:BTCEUR', { // Set a market
79
- timeframe: 'XX', // Set a wrong timeframe
80
- });
81
- },
82
-
83
- (next) => { /* Testing "Study not auth" */
84
- console.info('\nTesting "Study not auth" error:');
85
-
86
- const chart = new client.Session.Chart();
87
- chart.onError((...err) => { // Listen for errors
88
- console.error(' => Chart error:', err);
89
- next();
90
- });
91
-
92
- chart.setMarket('BINANCE:BTCEUR', { // Set a market
93
- timeframe: '15',
94
- type: 'Renko',
95
- });
96
-
97
- chart.onUpdate(() => {
98
- console.log('DATA', chart.periods[0]);
99
- });
100
- },
101
-
102
- (next) => { /* Testing "Set the market before" */
103
- console.info('\nTesting "Set the market before..." error:');
104
-
105
- const chart = new client.Session.Chart();
106
- chart.onError((...err) => { // Listen for errors
107
- console.error(' => Chart error:', err);
108
- chart.delete();
109
- next();
110
- });
111
-
112
- chart.setSeries('15'); // Set series before setting the market
113
- },
114
-
115
- (next) => { /* Testing "Inexistent indicator" */
116
- console.info('\nTesting "Inexistent indicator" error:');
117
-
118
- TradingView.getIndicator('STD;XXXXXXX')
119
- .catch((err) => {
120
- console.error(' => API error:', [err.message]);
121
- next();
122
- });
123
- },
124
-
125
- async (next) => { /* Testing "Invalid value" */
126
- console.info('\nTesting "Invalid value" error:');
127
-
128
- const chart = new client.Session.Chart();
129
- chart.setMarket('BINANCE:BTCEUR'); // Set a market
130
-
131
- const ST = await TradingView.getIndicator('STD;Supertrend');
132
- ST.setOption('Factor', -1); // This will cause an error
133
-
134
- const Supertrend = new chart.Study(ST);
135
- Supertrend.onError((...err) => {
136
- console.error(' => Study error:', err);
137
- chart.delete();
138
- next();
139
- });
140
- },
141
- ];
142
-
143
- (async () => {
144
- // eslint-disable-next-line no-restricted-syntax, no-await-in-loop
145
- for (const t of tests) await new Promise(t);
146
- console.log(`\nTests ${tests.length}/${tests.length} done !`);
147
- })();
1
+ const TradingView = require('../main');
2
+
3
+ /*
4
+ This example tests many types of errors
5
+ */
6
+
7
+ const client = new TradingView.Client(); // Creates a websocket client
8
+
9
+ const tests = [
10
+ (next) => { /* Testing "Credentials error" */
11
+ console.info('\nTesting "Credentials error" error:');
12
+
13
+ const client2 = new TradingView.Client({
14
+ token: 'FAKE_CREDENTIALS', // Set wrong credentials
15
+ });
16
+
17
+ client2.onError((...err) => {
18
+ console.error(' => Client error:', err);
19
+ client2.end();
20
+ next();
21
+ });
22
+ },
23
+
24
+ (next) => { /* Testing "Invalid symbol" */
25
+ console.info('\nTesting "Invalid symbol" error:');
26
+
27
+ const chart = new client.Session.Chart();
28
+ chart.onError((...err) => { // Listen for errors
29
+ console.error(' => Chart error:', err);
30
+ chart.delete();
31
+ next();
32
+ });
33
+
34
+ chart.setMarket('XXXXX'); // Set a wrong market
35
+ },
36
+
37
+ (next) => { /* Testing "Invalid timezone" */
38
+ console.info('\nTesting "Invalid timezone" error:');
39
+
40
+ const chart = new client.Session.Chart();
41
+ chart.onError((...err) => { // Listen for errors
42
+ console.error(' => Chart error:', err);
43
+ next();
44
+ });
45
+
46
+ chart.setMarket('BINANCE:BTCEUR'); // Set a market
47
+ chart.setTimezone('Nowhere/Nowhere'); // Set a fake timezone
48
+ },
49
+
50
+ (next) => { /* Testing "Custom timeframe" */
51
+ console.info('\nTesting "Custom timeframe" error:');
52
+
53
+ const chart = new client.Session.Chart();
54
+ chart.onError((...err) => { // Listen for errors
55
+ console.error(' => Chart error:', err);
56
+ chart.delete();
57
+ next();
58
+ });
59
+
60
+ chart.setMarket('BINANCE:BTCEUR', { // Set a market
61
+ timeframe: '20', // Set a custom timeframe
62
+ /*
63
+ Timeframe '20' isn't available because we are
64
+ not logged in as a premium TradingView account
65
+ */
66
+ });
67
+ },
68
+
69
+ (next) => { /* Testing "Invalid timeframe" */
70
+ console.info('\nTesting "Invalid timeframe" error:');
71
+
72
+ const chart = new client.Session.Chart();
73
+ chart.onError((...err) => { // Listen for errors
74
+ console.error(' => Chart error:', err);
75
+ next();
76
+ });
77
+
78
+ chart.setMarket('BINANCE:BTCEUR', { // Set a market
79
+ timeframe: 'XX', // Set a wrong timeframe
80
+ });
81
+ },
82
+
83
+ (next) => { /* Testing "Study not auth" */
84
+ console.info('\nTesting "Study not auth" error:');
85
+
86
+ const chart = new client.Session.Chart();
87
+ chart.onError((...err) => { // Listen for errors
88
+ console.error(' => Chart error:', err);
89
+ next();
90
+ });
91
+
92
+ chart.setMarket('BINANCE:BTCEUR', { // Set a market
93
+ timeframe: '15',
94
+ type: 'Renko',
95
+ });
96
+
97
+ chart.onUpdate(() => {
98
+ console.log('DATA', chart.periods[0]);
99
+ });
100
+ },
101
+
102
+ (next) => { /* Testing "Set the market before" */
103
+ console.info('\nTesting "Set the market before..." error:');
104
+
105
+ const chart = new client.Session.Chart();
106
+ chart.onError((...err) => { // Listen for errors
107
+ console.error(' => Chart error:', err);
108
+ chart.delete();
109
+ next();
110
+ });
111
+
112
+ chart.setSeries('15'); // Set series before setting the market
113
+ },
114
+
115
+ (next) => { /* Testing "Inexistent indicator" */
116
+ console.info('\nTesting "Inexistent indicator" error:');
117
+
118
+ TradingView.getIndicator('STD;XXXXXXX')
119
+ .catch((err) => {
120
+ console.error(' => API error:', [err.message]);
121
+ next();
122
+ });
123
+ },
124
+
125
+ async (next) => { /* Testing "Invalid value" */
126
+ console.info('\nTesting "Invalid value" error:');
127
+
128
+ const chart = new client.Session.Chart();
129
+ chart.setMarket('BINANCE:BTCEUR'); // Set a market
130
+
131
+ const ST = await TradingView.getIndicator('STD;Supertrend');
132
+ ST.setOption('Factor', -1); // This will cause an error
133
+
134
+ const Supertrend = new chart.Study(ST);
135
+ Supertrend.onError((...err) => {
136
+ console.error(' => Study error:', err);
137
+ chart.delete();
138
+ next();
139
+ });
140
+ },
141
+ ];
142
+
143
+ (async () => {
144
+ // eslint-disable-next-line no-restricted-syntax, no-await-in-loop
145
+ for (const t of tests) await new Promise(t);
146
+ console.log(`\nTests ${tests.length}/${tests.length} done !`);
147
+ })();
package/package.json CHANGED
@@ -1,39 +1,39 @@
1
- {
2
- "name": "@mathieuc/tradingview",
3
- "version": "3.3.3",
4
- "description": "Tradingview instant stocks API, indicator alerts, trading bot, and more !",
5
- "main": "main.js",
6
- "scripts": {
7
- "test": "node test"
8
- },
9
- "repository": {
10
- "type": "git",
11
- "url": "git+https://github.com/Mathieu2301/TradingView-API.git"
12
- },
13
- "engines": {
14
- "node": ">=14.0.0"
15
- },
16
- "keywords": [
17
- "tradingwiew",
18
- "market",
19
- "stocks",
20
- "crypto",
21
- "forex",
22
- "indicator",
23
- "bitcoin",
24
- "api"
25
- ],
26
- "author": "Mathieu Colmon",
27
- "license": "ISC",
28
- "dependencies": {
29
- "jszip": "^3.7.1",
30
- "ws": "^7.4.3"
31
- },
32
- "devDependencies": {
33
- "@babel/eslint-parser": "^7.15.7",
34
- "@mathieuc/console": "^1.0.1",
35
- "eslint": "^7.25.0",
36
- "eslint-config-airbnb-base": "^14.2.1",
37
- "eslint-plugin-import": "^2.22.1"
38
- }
39
- }
1
+ {
2
+ "name": "@mathieuc/tradingview",
3
+ "version": "3.4.1",
4
+ "description": "Tradingview instant stocks API, indicator alerts, trading bot, and more !",
5
+ "main": "main.js",
6
+ "scripts": {
7
+ "test": "node test"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/Mathieu2301/TradingView-API.git"
12
+ },
13
+ "engines": {
14
+ "node": ">=14.0.0"
15
+ },
16
+ "keywords": [
17
+ "tradingwiew",
18
+ "market",
19
+ "stocks",
20
+ "crypto",
21
+ "forex",
22
+ "indicator",
23
+ "bitcoin",
24
+ "api"
25
+ ],
26
+ "author": "Mathieu Colmon",
27
+ "license": "ISC",
28
+ "dependencies": {
29
+ "jszip": "^3.7.1",
30
+ "ws": "^7.4.3"
31
+ },
32
+ "devDependencies": {
33
+ "@babel/eslint-parser": "^7.15.7",
34
+ "@mathieuc/console": "^1.0.1",
35
+ "eslint": "^7.25.0",
36
+ "eslint-config-airbnb-base": "^14.2.1",
37
+ "eslint-plugin-import": "^2.22.1"
38
+ }
39
+ }
@@ -402,6 +402,7 @@ module.exports = (chartSession) => class ChartStudy {
402
402
  * @typedef {'plots' | 'report.currency'
403
403
  * | 'report.settings' | 'report.perf'
404
404
  * | 'report.trades' | 'report.history'
405
+ * | 'graphic'
405
406
  * } UpdateChangeType
406
407
  */
407
408
 
package/src/client.js CHANGED
@@ -214,6 +214,7 @@ module.exports = class Client {
214
214
  /**
215
215
  * @typedef {Object} ClientOptions
216
216
  * @prop {string} [token] User auth token (in 'sessionid' cookie)
217
+ * @prop {string} [signature] User auth token signature (in 'sessionid_sign' cookie)
217
218
  * @prop {boolean} [DEBUG] Enable debug mode
218
219
  * @prop {'data' | 'prodata' | 'widgetdata'} [server] Server type
219
220
  */
@@ -225,12 +226,15 @@ module.exports = class Client {
225
226
  if (clientOptions.DEBUG) global.TW_DEBUG = clientOptions.DEBUG;
226
227
 
227
228
  const server = clientOptions.server || 'data';
228
- this.#ws = new WebSocket(`wss://${server}.tradingview.com/socket.io/websocket`, {
229
+ this.#ws = new WebSocket(`wss://${server}.tradingview.com/socket.io/websocket?&type=chart`, {
229
230
  origin: 'https://s.tradingview.com',
230
231
  });
231
232
 
232
233
  if (clientOptions.token) {
233
- misc.getUser(clientOptions.token).then((user) => {
234
+ misc.getUser(
235
+ clientOptions.token,
236
+ clientOptions.signature ? clientOptions.signature : '',
237
+ ).then((user) => {
234
238
  this.#sendQueue.unshift(protocol.formatWSPacket({
235
239
  m: 'set_auth_token',
236
240
  p: [user.authToken],
@@ -375,8 +375,11 @@ module.exports = {
375
375
 
376
376
  if (data.error) throw new Error(data.error);
377
377
 
378
- const cookie = cookies.find((c) => c.includes('sessionid='));
379
- const session = (cookie.match(/sessionid=(.*?);/) ?? [])[1];
378
+ const sessionCookie = cookies.find((c) => c.includes('sessionid='));
379
+ const session = (sessionCookie.match(/sessionid=(.*?);/) ?? [])[1];
380
+
381
+ const signCookie = cookies.find((c) => c.includes('sessionid_sign='));
382
+ const signature = (signCookie.match(/sessionid_sign=(.*?);/) ?? [])[1];
380
383
 
381
384
  return {
382
385
  id: data.user.id,
@@ -388,6 +391,7 @@ module.exports = {
388
391
  followers: data.user.followers,
389
392
  notifications: data.user.notification_count,
390
393
  session,
394
+ signature,
391
395
  sessionHash: data.user.session_hash,
392
396
  privateChannel: data.user.private_channel,
393
397
  authToken: data.user.auth_token,
@@ -399,19 +403,20 @@ module.exports = {
399
403
  * Get user from 'sessionid' cookie
400
404
  * @function getUser
401
405
  * @param {string} session User 'sessionid' cookie
406
+ * @param {string} [signature] User 'sessionid_sign' cookie
402
407
  * @param {string} [location] Auth page location (For france: https://fr.tradingview.com/)
403
408
  * @returns {Promise<User>} Token
404
409
  */
405
- async getUser(session, location = 'https://www.tradingview.com/') {
410
+ async getUser(session, signature = '', location = 'https://www.tradingview.com/') {
406
411
  return new Promise((cb, err) => {
407
412
  https.get(location, {
408
- headers: { cookie: `sessionid=${session}` },
413
+ headers: { cookie: `sessionid=${session}${signature ? `;sessionid_sign=${signature};` : ''}` },
409
414
  }, (res) => {
410
415
  let rs = '';
411
416
  res.on('data', (d) => { rs += d; });
412
417
  res.on('end', async () => {
413
418
  if (res.headers.location && location !== res.headers.location) {
414
- cb(await module.exports.getUser(session, res.headers.location));
419
+ cb(await module.exports.getUser(session, signature, res.headers.location));
415
420
  return;
416
421
  }
417
422
  if (rs.includes('auth_token')) {
@@ -428,12 +433,13 @@ module.exports = {
428
433
  user: parseFloat(/"notification_count":\{"following":[0-9]*,"user":([0-9]*)/.exec(rs)[1] || 0),
429
434
  },
430
435
  session,
436
+ signature,
431
437
  sessionHash: /"session_hash":"(.*?)"/.exec(rs)[1],
432
438
  privateChannel: /"private_channel":"(.*?)"/.exec(rs)[1],
433
439
  authToken: /"auth_token":"(.*?)"/.exec(rs)[1],
434
440
  joinDate: new Date(/"date_joined":"(.*?)"/.exec(rs)[1] || 0),
435
441
  });
436
- } else err(new Error('Wrong or expired sessionid'));
442
+ } else err(new Error('Wrong or expired sessionid/signature'));
437
443
  });
438
444
 
439
445
  res.on('error', err);
@@ -445,12 +451,13 @@ module.exports = {
445
451
  * Get user's private indicators from a 'sessionid' cookie
446
452
  * @function getPrivateIndicators
447
453
  * @param {string} session User 'sessionid' cookie
454
+ * @param {string} [signature] User 'sessionid_sign' cookie
448
455
  * @returns {Promise<SearchIndicatorResult[]>} Search results
449
456
  */
450
- async getPrivateIndicators(session) {
457
+ async getPrivateIndicators(session, signature = '') {
451
458
  return new Promise((cb, err) => {
452
459
  https.get('https://pine-facade.tradingview.com/pine-facade/list?filter=saved', {
453
- headers: { cookie: `sessionid=${session}` },
460
+ headers: { cookie: `sessionid=${session}${signature ? `;sessionid_sign=${signature};` : ''}` },
454
461
  }, (res) => {
455
462
  let rs = '';
456
463
  res.on('data', (d) => { rs += d; });
@@ -499,18 +506,19 @@ module.exports = {
499
506
  * Get a chart token from a layout ID and the user credentials if the layout is not public
500
507
  * @function getChartToken
501
508
  * @param {string} layout The layout ID found in the layout URL (Like: 'XXXXXXXX')
502
- * @param {UserCredentials} [credentials] User credentials (id + session)
509
+ * @param {UserCredentials} [credentials] User credentials (id + session + [signature])
503
510
  * @returns {Promise<string>} Token
504
511
  */
505
512
  async getChartToken(layout, credentials = {}) {
506
513
  const creds = credentials.id && credentials.session;
507
514
  const userID = creds ? credentials.id : -1;
508
515
  const session = creds ? credentials.session : null;
516
+ const signature = creds ? credentials.signature : null;
509
517
 
510
518
  const { data } = await request({
511
519
  host: 'www.tradingview.com',
512
520
  path: `/chart-token/?image_url=${layout}&user_id=${userID}`,
513
- headers: { cookie: session ? `sessionid=${session}` : '' },
521
+ headers: { cookie: session ? `sessionid=${session}${signature ? `;sessionid_sign=${signature};` : ''}` : '' },
514
522
  });
515
523
 
516
524
  if (!data.token) throw new Error('Wrong layout or credentials');
@@ -545,7 +553,7 @@ module.exports = {
545
553
  * @function getDrawings
546
554
  * @param {string} layout The layout ID found in the layout URL (Like: 'XXXXXXXX')
547
555
  * @param {string | ''} [symbol] Market filter (Like: 'BINANCE:BTCEUR')
548
- * @param {UserCredentials} [credentials] User credentials (id + session)
556
+ * @param {UserCredentials} [credentials] User credentials (id + session + [signature])
549
557
  * @param {number} [chartID] Chart ID
550
558
  * @returns {Promise<Drawing[]>} Drawings
551
559
  */
@@ -553,12 +561,13 @@ module.exports = {
553
561
  const chartToken = await module.exports.getChartToken(layout, credentials);
554
562
  const creds = credentials.id && credentials.session;
555
563
  const session = creds ? credentials.session : null;
564
+ const signature = creds ? credentials.signature : null;
556
565
 
557
566
  const { data } = await request({
558
567
  host: 'charts-storage.tradingview.com',
559
568
  path: `/charts-storage/layout/${layout}/sources?chart_id=${chartID
560
569
  }&jwt=${chartToken}${symbol ? `&symbol=${symbol}` : ''}`,
561
- headers: { cookie: session ? `sessionid=${session}` : '' },
570
+ headers: { cookie: session ? `sessionid=${session}${signature ? `;sessionid_sign=${signature};` : ''}` : '' },
562
571
  });
563
572
 
564
573
  if (!data.payload) throw new Error('Wrong layout, user credentials, or chart id.');
@@ -5,11 +5,16 @@
5
5
  /**
6
6
  * @param {import('./session').QuoteSessionBridge} quoteSession
7
7
  */
8
+
8
9
  module.exports = (quoteSession) => class QuoteMarket {
9
10
  #symbolListeners = quoteSession.symbolListeners;
10
11
 
11
12
  #symbol;
12
13
 
14
+ #session;
15
+
16
+ #symbolKey;
17
+
13
18
  #symbolListenerID = 0;
14
19
 
15
20
  #lastData = {};
@@ -38,20 +43,24 @@ module.exports = (quoteSession) => class QuoteMarket {
38
43
 
39
44
  /**
40
45
  * @param {string} symbol Market symbol (like: 'BTCEUR' or 'KRAKEN:BTCEUR')
46
+ * @param {string} session Market session (like: 'regular' or 'extended')
41
47
  */
42
- constructor(symbol) {
48
+ constructor(symbol, session = 'regular') {
43
49
  this.#symbol = symbol;
50
+ this.#session = session;
51
+ this.#symbolKey = `=${JSON.stringify({ session, symbol })}`;
44
52
 
45
- if (!this.#symbolListeners[symbol]) {
46
- this.#symbolListeners[symbol] = [];
53
+ if (!this.#symbolListeners[this.#symbolKey]) {
54
+ this.#symbolListeners[this.#symbolKey] = [];
47
55
  quoteSession.send('quote_add_symbols', [
48
56
  quoteSession.sessionID,
49
- symbol,
57
+ this.#symbolKey,
50
58
  ]);
51
59
  }
52
- this.#symbolListenerID = this.#symbolListeners[symbol].length;
53
60
 
54
- this.#symbolListeners[symbol][this.#symbolListenerID] = (packet) => {
61
+ this.#symbolListenerID = this.#symbolListeners[this.#symbolKey].length;
62
+
63
+ this.#symbolListeners[this.#symbolKey][this.#symbolListenerID] = (packet) => {
55
64
  if (global.TW_DEBUG) console.log('§90§30§105 MARKET §0 DATA', packet);
56
65
 
57
66
  if (packet.type === 'qsd' && packet.data[1].s === 'ok') {
@@ -112,12 +121,12 @@ module.exports = (quoteSession) => class QuoteMarket {
112
121
 
113
122
  /** Close this listener */
114
123
  close() {
115
- if (this.#symbolListeners[this.#symbol].length <= 1) {
124
+ if (this.#symbolListeners[this.#symbolKey].length <= 1) {
116
125
  quoteSession.send('quote_remove_symbols', [
117
126
  quoteSession.sessionID,
118
- this.#symbol,
127
+ this.#symbolKey,
119
128
  ]);
120
129
  }
121
- delete this.#symbolListeners[this.#symbol][this.#symbolListenerID];
130
+ delete this.#symbolListeners[this.#symbolKey][this.#symbolListenerID];
122
131
  }
123
132
  };
@@ -79,21 +79,21 @@ module.exports = (client) => class QuoteSession {
79
79
  if (global.TW_DEBUG) console.log('§90§30§102 QUOTE SESSION §0 DATA', packet);
80
80
 
81
81
  if (packet.type === 'quote_completed') {
82
- const symbol = packet.data[1];
83
- if (!this.#symbolListeners[symbol]) {
84
- this.#client.send('quote_remove_symbols', [this.#sessionID, symbol]);
82
+ const symbolKey = packet.data[1];
83
+ if (!this.#symbolListeners[symbolKey]) {
84
+ this.#client.send('quote_remove_symbols', [this.#sessionID, symbolKey]);
85
85
  return;
86
86
  }
87
- this.#symbolListeners[symbol].forEach((h) => h(packet));
87
+ this.#symbolListeners[symbolKey].forEach((h) => h(packet));
88
88
  }
89
89
 
90
90
  if (packet.type === 'qsd') {
91
- const symbol = packet.data[1].n;
92
- if (!this.#symbolListeners[symbol]) {
93
- this.#client.send('quote_remove_symbols', [this.#sessionID, symbol]);
91
+ const symbolKey = packet.data[1].n;
92
+ if (!this.#symbolListeners[symbolKey]) {
93
+ this.#client.send('quote_remove_symbols', [this.#sessionID, symbolKey]);
94
94
  return;
95
95
  }
96
- this.#symbolListeners[symbol].forEach((h) => h(packet));
96
+ this.#symbolListeners[symbolKey].forEach((h) => h(packet));
97
97
  }
98
98
  },
99
99
  };
package/src/request.js CHANGED
@@ -1,37 +1,37 @@
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;
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 CHANGED
@@ -47,17 +47,21 @@ function success() {
47
47
  }
48
48
 
49
49
  console.info('§90§30§104 ==== ALL TESTS DONE ==== §0\n');
50
+
51
+ let errored = false;
52
+
50
53
  Object.keys(TESTS).forEach((t) => {
51
54
  let color = '2';
52
55
  if (TESTS[t].warnings) color = '3';
53
- if (TESTS[t].errors) color = '1';
56
+ if (TESTS[t].errors) {
57
+ color = '1';
58
+ errored = true;
59
+ }
54
60
 
55
61
  console.info(`§90§30§10${color} ${t} §0 §91E§0: ${TESTS[t].errors}§0 §93W§0: ${TESTS[t].warnings}`);
56
- // console.info(` - §91 Errors:§0 ${TESTS[t].errors}`);
57
- // console.info(` - §93 Warnings:§0 ${TESTS[t].warnings}`);
58
62
  });
59
63
 
60
- process.exit(0);
64
+ process.exit(errored ? 1 : 0);
61
65
  })();
62
66
 
63
67
  setTimeout(() => {
@@ -11,7 +11,6 @@ module.exports = async (log, success, warn, err, cb) => {
11
11
 
12
12
  if (markets.length < 10 || indicators.length < 10) {
13
13
  err('Not enough results');
14
- throw new Error('Not enough results');
15
14
  }
16
15
 
17
16
  cb();
@@ -5,7 +5,6 @@ module.exports = (log, success, warn, err, cb) => {
5
5
 
6
6
  client.onError((...error) => {
7
7
  err('Client error', ...error);
8
- throw new Error('Client error');
9
8
  });
10
9
 
11
10
  const quoteSession = new client.Session.Quote({
@@ -38,7 +37,6 @@ module.exports = (log, success, warn, err, cb) => {
38
37
  keys.forEach((k) => {
39
38
  if (!rsKeys.includes(k)) {
40
39
  err(`Missing '${k}' key in`, rsKeys);
41
- throw new Error('Missing key');
42
40
  }
43
41
  });
44
42
 
@@ -49,6 +47,5 @@ module.exports = (log, success, warn, err, cb) => {
49
47
 
50
48
  BTC.onError((...error) => {
51
49
  err('BTCEUR ERROR:', error);
52
- throw new Error('Missing key');
53
50
  });
54
51
  };
@@ -5,7 +5,6 @@ module.exports = async (log, success, warn, err, cb) => {
5
5
 
6
6
  client.onError((...error) => {
7
7
  err('Client error', error);
8
- throw new Error('Client error');
9
8
  });
10
9
 
11
10
  const chart = new client.Session.Chart();
@@ -16,7 +15,6 @@ module.exports = async (log, success, warn, err, cb) => {
16
15
 
17
16
  chart.onError((...error) => {
18
17
  err('Chart error', error);
19
- throw new Error('Chart error');
20
18
  });
21
19
 
22
20
  chart.onSymbolLoaded(() => { // When the symbol is successfully loaded
@@ -5,7 +5,6 @@ module.exports = async (log, success, warn, err, cb) => {
5
5
 
6
6
  client.onError((...error) => {
7
7
  err('Client error', error);
8
- throw new Error('Client error');
9
8
  });
10
9
 
11
10
  const chart = new client.Session.Chart();
@@ -15,7 +14,6 @@ module.exports = async (log, success, warn, err, cb) => {
15
14
 
16
15
  chart.onError((...error) => {
17
16
  err('Chart error', error);
18
- throw new Error('Chart error');
19
17
  });
20
18
 
21
19
  TradingView.getIndicator('STD;Supertrend%Strategy').then((indicator) => {
@@ -31,7 +29,6 @@ module.exports = async (log, success, warn, err, cb) => {
31
29
 
32
30
  SuperTrend.onError((...error) => {
33
31
  err('SuperTrend error', error[0]);
34
- throw new Error('SuperTrend error');
35
32
  });
36
33
 
37
34
  let QTY = 10;
@@ -81,7 +78,6 @@ module.exports = async (log, success, warn, err, cb) => {
81
78
 
82
79
  CipherB.onError((...error) => {
83
80
  err('MarketCipher B error:', error[0]);
84
- throw new Error('MarketCipher B error');
85
81
  });
86
82
 
87
83
  CipherB.onUpdate(() => {
@@ -5,7 +5,6 @@ module.exports = async (log, success, warn, err, cb) => {
5
5
 
6
6
  client.onError((...error) => {
7
7
  err('Client error', error);
8
- throw new Error('Client error');
9
8
  });
10
9
 
11
10
  const chart = new client.Session.Chart();
@@ -15,7 +14,6 @@ module.exports = async (log, success, warn, err, cb) => {
15
14
 
16
15
  chart.onError((...error) => {
17
16
  err('Chart error', error);
18
- throw new Error('Chart error');
19
17
  });
20
18
 
21
19
  const volumeProfile = new TradingView.BuiltInIndicator('VbPFixed@tv-basicstudies-139!');
@@ -5,7 +5,6 @@ module.exports = async (log, success, warn, err, cb) => {
5
5
 
6
6
  client.onError((...error) => {
7
7
  err('Client error', error);
8
- throw new Error('Client error');
9
8
  });
10
9
 
11
10
  const chart = new client.Session.Chart();
@@ -17,7 +16,6 @@ module.exports = async (log, success, warn, err, cb) => {
17
16
 
18
17
  chart.onError((...error) => {
19
18
  err('Chart error', error);
20
- throw new Error('Chart error');
21
19
  });
22
20
 
23
21
  let interval = NaN;
@@ -43,7 +41,7 @@ module.exports = async (log, success, warn, err, cb) => {
43
41
  if (Number.isNaN(interval) && times.length >= 2) interval = intrval;
44
42
 
45
43
  if (!Number.isNaN(interval) && interval !== intrval) {
46
- throw new Error(`Wrong interval: ${intrval} (should be ${interval})`);
44
+ err(`Wrong interval: ${intrval} (should be ${interval})`);
47
45
  }
48
46
 
49
47
  log('Next ->', times[0]);
@@ -5,7 +5,6 @@ module.exports = async (log, success, warn, err, cb) => {
5
5
 
6
6
  client.onError((...error) => {
7
7
  err('Client error', error);
8
- throw new Error('Client error');
9
8
  });
10
9
 
11
10
  const chart = new client.Session.Chart();
@@ -17,7 +16,6 @@ module.exports = async (log, success, warn, err, cb) => {
17
16
 
18
17
  chart.onError((...error) => {
19
18
  err('Chart error', error);
20
- throw new Error('Chart error');
21
19
  });
22
20
 
23
21
  let finished = false;
@@ -5,14 +5,12 @@ module.exports = async (log, success, warn, err, cb) => {
5
5
 
6
6
  client.onError((...error) => {
7
7
  err('Client error', error);
8
- throw new Error('Client error');
9
8
  });
10
9
 
11
10
  const chart = new client.Session.Chart();
12
11
 
13
12
  chart.onError((...error) => {
14
13
  err('Chart error', error);
15
- throw new Error('Chart error');
16
14
  });
17
15
 
18
16
  /* (0s) Heikin Ashi chart */
@@ -135,10 +135,48 @@ module.exports = async (log, success, warn, err, cb) => {
135
135
  next();
136
136
  });
137
137
  },
138
+
139
+ async (next) => { /* Testing "Wrong or expired sessionid/signature" using getUser method */
140
+ log('Testing "Wrong or expired sessionid/signature" error using getUser method:');
141
+
142
+ if (!process.env.SESSION) {
143
+ warn('=> Skipping test because no sessionid/signature was provided');
144
+ next();
145
+ return;
146
+ }
147
+
148
+ try {
149
+ await TradingView.getUser(process.env.SESSION);
150
+ err('=> User found !');
151
+ } catch (error) {
152
+ success('=> User not found:', error);
153
+ next();
154
+ }
155
+ },
156
+
157
+ async (next) => { /* Testing "Wrong or expired sessionid/signature" using client */
158
+ log('Testing "Wrong or expired sessionid/signature" error using client:');
159
+
160
+ if (!process.env.SESSION) {
161
+ warn('=> Skipping test because no sessionid/signature was provided');
162
+ next();
163
+ return;
164
+ }
165
+
166
+ log('Creating a new client...');
167
+ const client2 = new TradingView.Client({
168
+ token: process.env.SESSION,
169
+ });
170
+
171
+ client2.onError((...error) => {
172
+ success('=> Client error:', error);
173
+ client2.end();
174
+ next();
175
+ });
176
+ },
138
177
  ];
139
178
 
140
179
  (async () => {
141
- // eslint-disable-next-line no-restricted-syntax, no-await-in-loop
142
180
  for (const t of tests) await new Promise(t);
143
181
  success(`Crashtests ${tests.length}/${tests.length} done !`);
144
182
  cb();
@@ -0,0 +1,101 @@
1
+ const TradingView = require('../main');
2
+
3
+ const wait = (ms) => new Promise((cb) => { setTimeout(cb, ms); });
4
+
5
+ module.exports = async (log, success, warn, err, cb) => {
6
+ if (!process.env.SESSION || !process.env.SIGNATURE) {
7
+ warn('No sessionid/signature was provided');
8
+ cb();
9
+ return;
10
+ }
11
+
12
+ log('Getting user info');
13
+
14
+ const userInfos = await TradingView.getUser(process.env.SESSION, process.env.SIGNATURE);
15
+ if (userInfos && userInfos.id) {
16
+ success('User info:', {
17
+ id: userInfos.id,
18
+ username: userInfos.username,
19
+ firstName: userInfos.firstName,
20
+ lastName: userInfos.lastName,
21
+ following: userInfos.following,
22
+ followers: userInfos.followers,
23
+ notifications: userInfos.notifications,
24
+ joinDate: userInfos.joinDate,
25
+ });
26
+ } else err('Get user error. Result:', userInfos);
27
+
28
+ await wait(1000);
29
+
30
+ log('Getting user indicators');
31
+ const userIndicators = await TradingView.getPrivateIndicators(process.env.SESSION);
32
+
33
+ if (userIndicators) {
34
+ if (userIndicators.length === 0) warn('No private indicator found');
35
+ else success('User indicators:', userIndicators.map((i) => i.name));
36
+ } else err('Get indicators error. Result:', userIndicators);
37
+
38
+ await wait(1000);
39
+
40
+ log('Creating logged client');
41
+ const client = new TradingView.Client({
42
+ token: process.env.SESSION,
43
+ signature: process.env.SIGNATURE,
44
+ });
45
+
46
+ client.onError((...error) => {
47
+ err('Client error', error);
48
+ });
49
+
50
+ const chart = new client.Session.Chart();
51
+ chart.onError((...error) => {
52
+ err('Chart error', error);
53
+ });
54
+
55
+ log('Setting market to BINANCE:BTCEUR...');
56
+ chart.setMarket('BINANCE:BTCEUR', { timeframe: 'D' });
57
+
58
+ const checked = new Set();
59
+ async function check(item) {
60
+ checked.add(item);
61
+ log('Checked:', [...checked], `(${checked.size}/${userIndicators.length + 1})`);
62
+ if (checked.size < userIndicators.length + 1) return;
63
+
64
+ log('Closing client...');
65
+ chart.delete();
66
+ await client.end();
67
+ success('Client closed');
68
+ cb();
69
+ }
70
+
71
+ chart.onUpdate(async () => {
72
+ success('Market data:', {
73
+ name: chart.infos.pro_name,
74
+ description: chart.infos.short_description,
75
+ exchange: chart.infos.exchange,
76
+ price: chart.periods[0].close,
77
+ });
78
+
79
+ await check(Symbol.for('PRICE'));
80
+ });
81
+
82
+ await wait(1000);
83
+
84
+ log('Loading indicators...');
85
+ for (const indic of userIndicators) {
86
+ const privateIndic = await indic.get();
87
+ log(`[${indic.name}] Loading indicator...`);
88
+
89
+ const indicator = new chart.Study(privateIndic);
90
+
91
+ indicator.onReady(() => {
92
+ success(`[${indic.name}] Indicator loaded !`);
93
+ });
94
+
95
+ indicator.onUpdate(async () => {
96
+ success(`[${indic.name}] Last plot:`, indicator.periods[0]);
97
+ await check(indic.id);
98
+ });
99
+ }
100
+ log('Indicators loaded !');
101
+ };