@mathieuc/tradingview 2.0.5 → 2.0.7

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.
@@ -0,0 +1 @@
1
+ github: Mathieu2301
package/README.md CHANGED
@@ -56,9 +56,9 @@ const marketAPI = require('tradingview');
56
56
 
57
57
  const marketAPI = require('tradingview');
58
58
 
59
- const market = marketAPI();
59
+ (async () => {
60
+ const market = marketAPI(false);
60
61
 
61
- market.on('logged', async () => {
62
62
  const searchBTC = (await market.search('bitcoin euro', 'crypto'))[0];
63
63
  console.log('Found Bitcoin / Euro:', searchBTC);
64
64
 
@@ -66,7 +66,7 @@ market.on('logged', async () => {
66
66
  console.log('Full technical analysis for Bitcoin:', TA);
67
67
 
68
68
  // You can also use this way: await market.getTA('crypto', 'BINANCE:BTCEUR');
69
- });
69
+ })();
70
70
  ```
71
71
 
72
72
  ```javascript
package/main.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const WebSocket = require('ws');
2
2
  const {
3
- search, getScreener, getTA, getIndicator, getUserToken,
3
+ search, getScreener, getTA, getIndicator,
4
+ getUser, getChartToken, getDrawings,
4
5
  } = require('./miscRequests');
5
6
 
6
7
  let onPacket = () => null;
@@ -88,6 +89,9 @@ module.exports = (autoInit = true) => {
88
89
  /** List of subscribed symbols */
89
90
  let subscribed = [];
90
91
 
92
+ /** Websockets status calls */
93
+ let isEnded = false;
94
+
91
95
  /**
92
96
  * Send a custom packet
93
97
  * @param {string} t Packet type
@@ -183,10 +187,19 @@ module.exports = (autoInit = true) => {
183
187
 
184
188
  /**
185
189
  * Close the websocket connection
186
- * @param {string} symbol Market symbol (Example: BTCEUR or COINBASE:BTCEUR)
190
+ * @return {Promise<void>} When websocket is closed
187
191
  */
188
- end() { ws.close(); },
192
+ end() {
193
+ return new Promise((cb) => {
194
+ isEnded = true;
195
+ ws.close();
196
+ cb();
197
+ });
198
+ },
189
199
 
200
+ getUser,
201
+ getChartToken,
202
+ getDrawings,
190
203
  search,
191
204
  getScreener,
192
205
  getTA,
@@ -286,6 +299,8 @@ module.exports = (autoInit = true) => {
286
299
  }
287
300
 
288
301
  chartCBs[chartSession] = (packet) => {
302
+ if (isEnded) return;
303
+
289
304
  if (['timescale_update', 'du'].includes(packet.type)) {
290
305
  updatePeriods(packet);
291
306
  onUpdate([...periods].reverse());
@@ -297,7 +312,7 @@ module.exports = (autoInit = true) => {
297
312
  }
298
313
  };
299
314
 
300
- if (chart.session) send('set_auth_token', [await getUserToken(chart.session)]);
315
+ if (chart.session) send('set_auth_token', [(await getUser(chart.session)).authToken]);
301
316
  send('chart_create_session', [chartSession, '']);
302
317
  if (chart.timezone) send('switch_timezone', [chartSession, chart.timezone]);
303
318
  send('resolve_symbol', [chartSession, 'sds_sym_1', `={"symbol":"${chart.symbol || 'BTCEUR'}","adjustment":"splits"}`]);
package/miscRequests.js CHANGED
@@ -1,285 +1,420 @@
1
- const https = require('https');
2
-
3
- const indicators = ['Recommend.Other', 'Recommend.All', 'Recommend.MA'];
4
-
5
- function fetchScanData(tickers = [], type = '', columns = []) {
6
- return new Promise((cb, err) => {
7
- const req = https.request({
8
- method: 'POST',
9
- hostname: 'scanner.tradingview.com',
10
- path: `/${type}/scan`,
11
- headers: {
12
- 'Content-Type': 'application/json',
13
- },
14
- }, (res) => {
15
- let data = '';
16
- res.on('data', (c) => { data += c; });
17
- res.on('end', () => {
18
- if (!data.startsWith('{')) {
19
- err(new Error('Wrong screener or symbol'));
20
- return;
21
- }
22
- try {
23
- cb(JSON.parse(data));
24
- } catch (error) {
25
- err(new Error('Can\'t parse server response'));
26
- }
27
- });
28
- });
29
-
30
- req.on('error', err);
31
- req.end(JSON.stringify({ symbols: { tickers }, columns }));
32
- });
33
- }
34
-
35
- /**
36
- * @typedef {number} advice
37
- *
38
- * @typedef {{ Other: advice, All: advice, MA: advice }} Period
39
- *
40
- * @typedef {{
41
- * '1': Period,
42
- * '5': Period,
43
- * '15': Period,
44
- * '60': Period,
45
- * '240': Period,
46
- * '1D': Period,
47
- * '1W': Period,
48
- * '1M': Period,
49
- * }} Periods
50
- *
51
- * @typedef {string | 'forex' | 'crypto'
52
- * | 'america' | 'australia' | 'canada' | 'egypt'
53
- * | 'germany' | 'india' | 'israel' | 'italy'
54
- * | 'luxembourg' | 'poland' | 'sweden' | 'turkey'
55
- * | 'uk' | 'vietnam'} screener
56
- * You can use `getScreener(exchange)` function for non-forex and non-crypto markets.
57
- */
58
-
59
- module.exports = {
60
- /**
61
- * @param {string} exchange Example: BINANCE, EURONEXT, NASDAQ
62
- * @returns {screener}
63
- */
64
- getScreener(exchange) {
65
- const e = exchange.toUpperCase();
66
- if (['NASDAQ', 'NYSE', 'NYSE ARCA', 'OTC'].includes(e)) return 'america';
67
- if (['ASX'].includes(e)) return 'australia';
68
- if (['TSX', 'TSXV', 'CSE', 'NEO'].includes(e)) return 'canada';
69
- if (['EGX'].includes(e)) return 'egypt';
70
- if (['FWB', 'SWB', 'XETR'].includes(e)) return 'germany';
71
- if (['BSE', 'NSE'].includes(e)) return 'india';
72
- if (['TASE'].includes(e)) return 'israel';
73
- if (['MIL', 'MILSEDEX'].includes(e)) return 'italy';
74
- if (['LUXSE'].includes(e)) return 'luxembourg';
75
- if (['NEWCONNECT'].includes(e)) return 'poland';
76
- if (['NGM'].includes(e)) return 'sweden';
77
- if (['BIST'].includes(e)) return 'turkey';
78
- if (['LSE', 'LSIN'].includes(e)) return 'uk';
79
- if (['HNX'].includes(e)) return 'vietnam';
80
- return exchange.toLowerCase();
81
- },
82
-
83
- /** Get technical analysis
84
- * @param {screener} screener
85
- * @param {string} id Full market id (Example: COINBASE:BTCEUR)
86
- * @returns {Promise<Periods>} results
87
- */
88
- async getTA(screener, id) {
89
- const advice = {};
90
-
91
- const cols = ['1', '5', '15', '60', '240', '1D', '1W', '1M']
92
- .map((t) => indicators.map((i) => (t !== '1D' ? `${i}|${t}` : i)))
93
- .flat();
94
-
95
- const rs = await fetchScanData([id], screener, cols);
96
- if (!rs.data || !rs.data[0]) return false;
97
-
98
- rs.data[0].d.forEach((val, i) => {
99
- const [name, period] = cols[i].split('|');
100
- const pName = period || '1D';
101
- if (!advice[pName]) advice[pName] = {};
102
- advice[pName][name.split('.').pop()] = Math.round(val * 1000) / 500;
103
- });
104
-
105
- return advice;
106
- },
107
-
108
- /**
109
- * @typedef {Object} SearchResult
110
- * @property {string} id
111
- * @property {string} exchange
112
- * @property {string} fullExchange
113
- * @property {string} screener
114
- * @property {string} symbol
115
- * @property {string} description
116
- * @property {string} type
117
- * @property {() => Promise<Periods>} getTA
118
- */
119
-
120
- /**
121
- * @param {string} search Search a symbol
122
- * @param {'stock' | 'futures' | 'forex' | 'cfd' | 'crypto' | 'index' | 'economic'} filter
123
- * @returns {Promise<SearchResult[]>} Search results
124
- */
125
- async search(search, filter = '') {
126
- return new Promise((cb, err) => {
127
- https.get({
128
- host: 'symbol-search.tradingview.com',
129
- path: `/symbol_search/?text=${search.replace(/ /g, '%20')}&type=${filter}`,
130
- origin: 'https://www.tradingview.com',
131
- }, (res) => {
132
- let rs = '';
133
- res.on('data', (d) => { rs += d; });
134
- res.on('end', () => {
135
- try {
136
- rs = JSON.parse(rs);
137
- } catch (e) {
138
- err(new Error('Can\'t parse server response'));
139
- return;
140
- }
141
-
142
- cb(rs.map((s) => {
143
- const exchange = s.exchange.split(' ')[0];
144
- const id = `${exchange}:${s.symbol}`;
145
-
146
- const screener = (['forex', 'crypto'].includes(s.type)
147
- ? s.type
148
- : this.getScreener(exchange)
149
- );
150
-
151
- return {
152
- id,
153
- exchange,
154
- fullExchange: s.exchange,
155
- screener,
156
- symbol: s.symbol,
157
- description: s.description,
158
- type: s.type,
159
- getTA: () => this.getTA(screener, id),
160
- };
161
- }));
162
- });
163
-
164
- res.on('error', err);
165
- });
166
- });
167
- },
168
-
169
- /**
170
- * @typedef {Object} indicatorInput
171
- * @property {string} name
172
- * @property {string} inline Inline name
173
- * @property { 'text' | 'source' | 'integer' | 'float' | 'resolution' | 'bool' } type
174
- * @property {string | number | boolean} value
175
- * @property {string | number | boolean} defVal
176
- * @property {boolean} hidden
177
- * @property {boolean} isFake
178
- * @property {string[]} [options]
179
- */
180
-
181
- /**
182
- * @typedef {Object} Indicator
183
- * @property {string} pineId Indicator ID
184
- * @property {string} pineVersion Indicator version
185
- * @property {string} description Indicator description
186
- * @property {string} shortDescription Indicator short description
187
- * @property {Object<string, indicatorInput>} inputs Indicator inputs
188
- * @property {Object<string, string>} plots Indicator plots
189
- * @property {string} script Indicator script
190
- */
191
-
192
- /**
193
- * Get an indicator
194
- * @param {string} id Indicator ID (Like: PUB;XXXXXXXXXXXXXXXXXXXXX)
195
- * @param {string} version Wanted version of the indicator
196
- * @returns {Promise<Indicator>} Indicator
197
- */
198
- async getIndicator(id, version, settings = []) {
199
- return new Promise((cb, err) => {
200
- https.get(`https://pine-facade.tradingview.com/pine-facade/translate/${id}/${version}`, (res) => {
201
- let rs = '';
202
- res.on('data', (d) => { rs += d; });
203
- res.on('end', () => {
204
- try {
205
- rs = JSON.parse(rs);
206
- } catch (e) {
207
- err(new Error('Inexistent or unsupported indicator'));
208
- return;
209
- }
210
-
211
- if (!rs.success || !rs.result.metaInfo || !rs.result.metaInfo.inputs) {
212
- err(new Error('Inexistent or unsupported indicator'));
213
- return;
214
- }
215
-
216
- const inputs = {};
217
-
218
- rs.result.metaInfo.inputs.forEach((input) => {
219
- if (['text', 'pineId', 'pineVersion'].includes(input.id)) return;
220
- const inline = input.inline || input.name.replace(/ /g, '_').replace(/[^a-zA-Z0-9]/g, '');
221
-
222
- const i = parseInt(input.id.replace(/[^0-9]/g, ''), 10);
223
-
224
- inputs[input.id] = {
225
- name: input.name,
226
- type: input.type,
227
- value: settings[i] ?? input.defval,
228
- defVal: input.defval,
229
- hidden: !!input.isHidden,
230
- isFake: !!input.isFake,
231
- inline,
232
- };
233
-
234
- if (input.options) inputs[input.id].options = input.options;
235
- });
236
-
237
- const plots = {};
238
-
239
- Object.keys(rs.result.metaInfo.styles).forEach((plotId) => {
240
- plots[plotId] = rs.result.metaInfo.styles[plotId].title.replace(/ /g, '_').replace(/[^a-zA-Z0-9]/g, '');
241
- });
242
-
243
- cb({
244
- pineId: id,
245
- pineVersion: version,
246
- description: rs.result.metaInfo.description,
247
- shortDescription: rs.result.metaInfo.shortDescription,
248
- inputs,
249
- plots,
250
- script: rs.result.ilTemplate,
251
- });
252
- });
253
-
254
- res.on('error', err);
255
- });
256
- });
257
- },
258
-
259
- /**
260
- * Get a token for an user from a 'sessionid' cookie
261
- * @param {string} session User 'sessionid' cookie
262
- * @param {string} [location] Auth page location (For france: https://fr.tradingview.com/)
263
- * @returns {Promise<string>} Token
264
- */
265
- async getUserToken(session, location = 'https://www.tradingview.com/') {
266
- return new Promise((cb, err) => {
267
- https.get(location, {
268
- headers: { cookie: `sessionid=${session}` },
269
- }, (res) => {
270
- let rs = '';
271
- res.on('data', (d) => { rs += d; });
272
- res.on('end', async () => {
273
- if (res.headers.location && location !== res.headers.location) {
274
- cb(await module.exports.getUserToken(session, res.headers.location));
275
- return;
276
- }
277
- if (rs.includes('auth_token')) cb(/"auth_token":"(.*?)"/g.exec(rs)[1]);
278
- else err(new Error('Wrong or expired sessionid'));
279
- });
280
-
281
- res.on('error', err);
282
- }).end();
283
- });
284
- },
285
- };
1
+ const https = require('https');
2
+
3
+ const indicators = ['Recommend.Other', 'Recommend.All', 'Recommend.MA'];
4
+
5
+ function fetchScanData(tickers = [], type = '', columns = []) {
6
+ return new Promise((cb, err) => {
7
+ const req = https.request({
8
+ method: 'POST',
9
+ hostname: 'scanner.tradingview.com',
10
+ path: `/${type}/scan`,
11
+ headers: {
12
+ 'Content-Type': 'application/json',
13
+ },
14
+ }, (res) => {
15
+ let data = '';
16
+ res.on('data', (c) => { data += c; });
17
+ res.on('end', () => {
18
+ if (!data.startsWith('{')) {
19
+ err(new Error('Wrong screener or symbol'));
20
+ return;
21
+ }
22
+ try {
23
+ cb(JSON.parse(data));
24
+ } catch (error) {
25
+ err(new Error('Can\'t parse server response'));
26
+ }
27
+ });
28
+ });
29
+
30
+ req.on('error', err);
31
+ req.end(JSON.stringify({ symbols: { tickers }, columns }));
32
+ });
33
+ }
34
+
35
+ /**
36
+ * @typedef {number} advice
37
+ *
38
+ * @typedef {{ Other: advice, All: advice, MA: advice }} Period
39
+ *
40
+ * @typedef {{
41
+ * '1': Period,
42
+ * '5': Period,
43
+ * '15': Period,
44
+ * '60': Period,
45
+ * '240': Period,
46
+ * '1D': Period,
47
+ * '1W': Period,
48
+ * '1M': Period,
49
+ * }} Periods
50
+ *
51
+ * @typedef {string | 'forex' | 'crypto'
52
+ * | 'america' | 'australia' | 'canada' | 'egypt'
53
+ * | 'germany' | 'india' | 'israel' | 'italy'
54
+ * | 'luxembourg' | 'poland' | 'sweden' | 'turkey'
55
+ * | 'uk' | 'vietnam'} screener
56
+ * You can use `getScreener(exchange)` function for non-forex and non-crypto markets.
57
+ */
58
+
59
+ module.exports = {
60
+ /**
61
+ * @param {string} exchange Example: BINANCE, EURONEXT, NASDAQ
62
+ * @returns {screener}
63
+ */
64
+ getScreener(exchange) {
65
+ const e = exchange.toUpperCase();
66
+ if (['NASDAQ', 'NYSE', 'NYSE ARCA', 'OTC'].includes(e)) return 'america';
67
+ if (['ASX'].includes(e)) return 'australia';
68
+ if (['TSX', 'TSXV', 'CSE', 'NEO'].includes(e)) return 'canada';
69
+ if (['EGX'].includes(e)) return 'egypt';
70
+ if (['FWB', 'SWB', 'XETR'].includes(e)) return 'germany';
71
+ if (['BSE', 'NSE'].includes(e)) return 'india';
72
+ if (['TASE'].includes(e)) return 'israel';
73
+ if (['MIL', 'MILSEDEX'].includes(e)) return 'italy';
74
+ if (['LUXSE'].includes(e)) return 'luxembourg';
75
+ if (['NEWCONNECT'].includes(e)) return 'poland';
76
+ if (['NGM'].includes(e)) return 'sweden';
77
+ if (['BIST'].includes(e)) return 'turkey';
78
+ if (['LSE', 'LSIN'].includes(e)) return 'uk';
79
+ if (['HNX'].includes(e)) return 'vietnam';
80
+ return exchange.toLowerCase();
81
+ },
82
+
83
+ /**
84
+ * Get technical analysis
85
+ * @param {screener} screener
86
+ * @param {string} id Full market id (Example: COINBASE:BTCEUR)
87
+ * @returns {Promise<Periods>} results
88
+ */
89
+ async getTA(screener, id) {
90
+ const advice = {};
91
+
92
+ const cols = ['1', '5', '15', '60', '240', '1D', '1W', '1M']
93
+ .map((t) => indicators.map((i) => (t !== '1D' ? `${i}|${t}` : i)))
94
+ .flat();
95
+
96
+ const rs = await fetchScanData([id], screener, cols);
97
+ if (!rs.data || !rs.data[0]) return false;
98
+
99
+ rs.data[0].d.forEach((val, i) => {
100
+ const [name, period] = cols[i].split('|');
101
+ const pName = period || '1D';
102
+ if (!advice[pName]) advice[pName] = {};
103
+ advice[pName][name.split('.').pop()] = Math.round(val * 1000) / 500;
104
+ });
105
+
106
+ return advice;
107
+ },
108
+
109
+ /**
110
+ * @typedef {Object} SearchResult
111
+ * @property {string} id
112
+ * @property {string} exchange
113
+ * @property {string} fullExchange
114
+ * @property {string} screener
115
+ * @property {string} symbol
116
+ * @property {string} description
117
+ * @property {string} type
118
+ * @property {() => Promise<Periods>} getTA
119
+ */
120
+
121
+ /**
122
+ * Find a symbol
123
+ * @param {string} search Keywords
124
+ * @param {'stock' | 'futures' | 'forex' | 'cfd' | 'crypto' | 'index' | 'economic'} [filter]
125
+ * @returns {Promise<SearchResult[]>} Search results
126
+ */
127
+ async search(search, filter = '') {
128
+ return new Promise((cb, err) => {
129
+ https.get({
130
+ host: 'symbol-search.tradingview.com',
131
+ path: `/symbol_search/?text=${search.replace(/ /g, '%20')}&type=${filter}`,
132
+ origin: 'https://www.tradingview.com',
133
+ }, (res) => {
134
+ let rs = '';
135
+ res.on('data', (d) => { rs += d; });
136
+ res.on('end', () => {
137
+ try {
138
+ rs = JSON.parse(rs);
139
+ } catch (e) {
140
+ err(new Error('Can\'t parse server response'));
141
+ return;
142
+ }
143
+
144
+ cb(rs.map((s) => {
145
+ const exchange = s.exchange.split(' ')[0];
146
+ const id = `${exchange}:${s.symbol}`;
147
+
148
+ const screener = (['forex', 'crypto'].includes(s.type)
149
+ ? s.type
150
+ : this.getScreener(exchange)
151
+ );
152
+
153
+ return {
154
+ id,
155
+ exchange,
156
+ fullExchange: s.exchange,
157
+ screener,
158
+ symbol: s.symbol,
159
+ description: s.description,
160
+ type: s.type,
161
+ getTA: () => this.getTA(screener, id),
162
+ };
163
+ }));
164
+ });
165
+
166
+ res.on('error', err);
167
+ });
168
+ });
169
+ },
170
+
171
+ /**
172
+ * @typedef {Object} indicatorInput
173
+ * @property {string} name
174
+ * @property {string} inline Inline name
175
+ * @property { 'text' | 'source' | 'integer' | 'float' | 'resolution' | 'bool' } type
176
+ * @property {string | number | boolean} value
177
+ * @property {string | number | boolean} defVal
178
+ * @property {boolean} hidden
179
+ * @property {boolean} isFake
180
+ * @property {string[]} [options]
181
+ */
182
+
183
+ /**
184
+ * @typedef {Object} Indicator
185
+ * @property {string} pineId Indicator ID
186
+ * @property {string} pineVersion Indicator version
187
+ * @property {string} description Indicator description
188
+ * @property {string} shortDescription Indicator short description
189
+ * @property {Object<string, indicatorInput>} inputs Indicator inputs
190
+ * @property {Object<string, string>} plots Indicator plots
191
+ * @property {string} script Indicator script
192
+ */
193
+
194
+ /**
195
+ * Get an indicator
196
+ * @param {string} id Indicator ID (Like: PUB;XXXXXXXXXXXXXXXXXXXXX)
197
+ * @param {string} version Wanted version of the indicator
198
+ * @returns {Promise<Indicator>} Indicator
199
+ */
200
+ async getIndicator(id, version, settings = []) {
201
+ return new Promise((cb, err) => {
202
+ https.get(`https://pine-facade.tradingview.com/pine-facade/translate/${id}/${version}`, (res) => {
203
+ let rs = '';
204
+ res.on('data', (d) => { rs += d; });
205
+ res.on('end', () => {
206
+ try {
207
+ rs = JSON.parse(rs);
208
+ } catch (e) {
209
+ err(new Error('Inexistent or unsupported indicator'));
210
+ return;
211
+ }
212
+
213
+ if (!rs.success || !rs.result.metaInfo || !rs.result.metaInfo.inputs) {
214
+ err(new Error('Inexistent or unsupported indicator'));
215
+ return;
216
+ }
217
+
218
+ const inputs = {};
219
+
220
+ rs.result.metaInfo.inputs.forEach((input) => {
221
+ if (['text', 'pineId', 'pineVersion'].includes(input.id)) return;
222
+ const inline = input.inline || input.name.replace(/ /g, '_').replace(/[^a-zA-Z0-9]/g, '');
223
+
224
+ const i = parseInt(input.id.replace(/[^0-9]/g, ''), 10);
225
+
226
+ inputs[input.id] = {
227
+ name: input.name,
228
+ type: input.type,
229
+ value: settings[i] ?? input.defval,
230
+ defVal: input.defval,
231
+ hidden: !!input.isHidden,
232
+ isFake: !!input.isFake,
233
+ inline,
234
+ };
235
+
236
+ if (input.options) inputs[input.id].options = input.options;
237
+ });
238
+
239
+ const plots = {};
240
+
241
+ Object.keys(rs.result.metaInfo.styles).forEach((plotId) => {
242
+ plots[plotId] = rs.result.metaInfo.styles[plotId].title.replace(/ /g, '_').replace(/[^a-zA-Z0-9]/g, '');
243
+ });
244
+
245
+ cb({
246
+ pineId: id,
247
+ pineVersion: version,
248
+ description: rs.result.metaInfo.description,
249
+ shortDescription: rs.result.metaInfo.shortDescription,
250
+ inputs,
251
+ plots,
252
+ script: rs.result.ilTemplate,
253
+ });
254
+ });
255
+
256
+ res.on('error', err);
257
+ });
258
+ });
259
+ },
260
+
261
+ /**
262
+ * @typedef {Object} User Instance of User
263
+ * @property {number} id User ID
264
+ * @property {string} username User username
265
+ * @property {string} firstName User first name
266
+ * @property {string} lastName User last name
267
+ * @property {number} reputation User reputation
268
+ * @property {number} following Number of following accounts
269
+ * @property {number} followers Number of followers
270
+ * @property {Object} notifications User's notifications
271
+ * @property {number} notifications.user User notifications
272
+ * @property {number} notifications.following Notification from following accounts
273
+ * @property {string} session User session
274
+ * @property {string} sessionHash User session hash
275
+ * @property {string} privateChannel User private channel
276
+ * @property {string} authToken User auth token
277
+ * @property {Date} joinDate Account creation date
278
+ */
279
+
280
+ /**
281
+ * Get a token for an user from a 'sessionid' cookie
282
+ * @param {string} session User 'sessionid' cookie
283
+ * @param {string} [location] Auth page location (For france: https://fr.tradingview.com/)
284
+ * @returns {Promise<User>} Token
285
+ */
286
+ async getUser(session, location = 'https://www.tradingview.com/') {
287
+ return new Promise((cb, err) => {
288
+ https.get(location, {
289
+ headers: { cookie: `sessionid=${session}` },
290
+ }, (res) => {
291
+ let rs = '';
292
+ res.on('data', (d) => { rs += d; });
293
+ res.on('end', async () => {
294
+ if (res.headers.location && location !== res.headers.location) {
295
+ cb(await module.exports.getUser(session, res.headers.location));
296
+ return;
297
+ }
298
+ if (rs.includes('auth_token')) {
299
+ cb({
300
+ id: /"id":([0-9]{1,10}),/.exec(rs)[1],
301
+ username: /"username":"(.*?)"/.exec(rs)[1],
302
+ firstName: /"first_name":"(.*?)"/.exec(rs)[1],
303
+ lastName: /"last_name":"(.*?)"/.exec(rs)[1],
304
+ reputation: parseFloat(/"reputation":(.*?),/.exec(rs)[1] || 0),
305
+ following: parseFloat(/,"following":([0-9]*?),/.exec(rs)[1] || 0),
306
+ followers: parseFloat(/,"followers":([0-9]*?),/.exec(rs)[1] || 0),
307
+ notifications: {
308
+ following: parseFloat(/"notification_count":\{"following":([0-9]*),/.exec(rs)[1] || 0),
309
+ user: parseFloat(/"notification_count":\{"following":[0-9]*,"user":([0-9]*)/.exec(rs)[1] || 0),
310
+ },
311
+ session,
312
+ sessionHash: /"session_hash":"(.*?)"/.exec(rs)[1],
313
+ privateChannel: /"private_channel":"(.*?)"/.exec(rs)[1],
314
+ authToken: /"auth_token":"(.*?)"/.exec(rs)[1],
315
+ joinDate: new Date(/"date_joined":"(.*?)"/.exec(rs)[1] || 0),
316
+ });
317
+ } else err(new Error('Wrong or expired sessionid'));
318
+ });
319
+
320
+ res.on('error', err);
321
+ }).end();
322
+ });
323
+ },
324
+
325
+ /**
326
+ * User credentials
327
+ * @typedef {Object} UserCredentials
328
+ * @property {number} id User ID
329
+ * @property {string} session User session ('sessionid' cookie)
330
+ */
331
+
332
+ /**
333
+ * Get a chart token from a layout ID and the user credentials if the layout is not public
334
+ * @param {string} layout The layout ID found in the layout URL (Like: 'XXXXXXXX')
335
+ * @param {UserCredentials} [credentials] User credentials (id + session)
336
+ * @returns {Promise<string>} Token
337
+ */
338
+ async getChartToken(layout, credentials = {}) {
339
+ return new Promise((cb, err) => {
340
+ const creds = credentials.id && credentials.session;
341
+ const userID = creds ? credentials.id : -1;
342
+ const session = creds ? credentials.session : null;
343
+
344
+ https.get(`https://www.tradingview.com/chart-token/?image_url=${layout}&user_id=${userID}`, {
345
+ headers: { cookie: session ? `sessionid=${session}` : '' },
346
+ }, (res) => {
347
+ let rs = '';
348
+ res.on('data', (d) => { rs += d; });
349
+ res.on('end', async () => {
350
+ rs = JSON.parse(rs);
351
+
352
+ if (rs.token) cb(rs.token);
353
+ else err(new Error('Wrong layout or credentials'));
354
+ });
355
+
356
+ res.on('error', err);
357
+ }).end();
358
+ });
359
+ },
360
+
361
+ /**
362
+ * @typedef {Object} DrawingPoint
363
+ * @property {number} time_t Point X time position
364
+ * @property {number} price Point Y price position
365
+ * @property {number} offset Point offset
366
+ */
367
+
368
+ /**
369
+ * @typedef {Object} Drawing
370
+ * @property {string} id Drawing ID (Like: 'XXXXXX')
371
+ * @property {string} symbol Layout market symbol (Like: 'BINANCE:BUCEUR')
372
+ * @property {string} ownerSource Owner user ID (Like: 'XXXXXX')
373
+ * @property {string} serverUpdateTime Drawing last update timestamp
374
+ * @property {string} currencyId Currency ID (Like: 'EUR')
375
+ * @property {any} unitId Unit ID
376
+ * @property {string} type Drawing type
377
+ * @property {DrawingPoint[]} points List of drawing points
378
+ * @property {number} zorder Drawing Z order
379
+ * @property {string} linkKey Drawing link key
380
+ * @property {Object} state Drawing state
381
+ */
382
+
383
+ /**
384
+ * Get a chart token from a layout ID and the user credentials if the layout is not public
385
+ * @param {string} layout The layout ID found in the layout URL (Like: 'XXXXXXXX')
386
+ * @param {string | ''} [symbol] Market filter (Like: 'BINANCE:BTCEUR')
387
+ * @param {UserCredentials} [credentials] User credentials (id + session)
388
+ * @param {number} [chartID = 1] Chart ID
389
+ * @returns {Promise<Drawing[]>} Drawings
390
+ */
391
+ async getDrawings(layout, symbol = '', credentials = {}, chartID = 1) {
392
+ const chartToken = await module.exports.getChartToken(layout, credentials);
393
+
394
+ return new Promise((cb, err) => {
395
+ const creds = credentials.id && credentials.session;
396
+ const session = creds ? credentials.session : null;
397
+
398
+ const url = `https://charts-storage.tradingview.com/charts-storage/layout/${
399
+ layout}/sources?chart_id=${chartID}&jwt=${chartToken}${symbol ? `&symbol=${symbol}` : ''}`;
400
+
401
+ https.get(url, {
402
+ headers: { cookie: session ? `sessionid=${session}` : '' },
403
+ }, (res) => {
404
+ let rs = '';
405
+ res.on('data', (d) => { rs += d; });
406
+ res.on('end', async () => {
407
+ rs = JSON.parse(rs);
408
+
409
+ if (rs.payload) {
410
+ cb(Object.values(rs.payload.sources || {}).map((drawing) => ({
411
+ ...drawing, ...drawing.state,
412
+ })));
413
+ } else err(new Error('Wrong layout, user credentials, or chart id.'));
414
+ });
415
+
416
+ res.on('error', err);
417
+ }).end();
418
+ });
419
+ },
420
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mathieuc/tradingview",
3
- "version": "2.0.5",
3
+ "version": "2.0.7",
4
4
  "description": "Tradingview instant stocks API, indicator alerts, trading bot, and more !",
5
5
  "main": "main.js",
6
6
  "scripts": {
@@ -0,0 +1,41 @@
1
+ require('./envLoader');
2
+ const marketAPI = require('../../main');
3
+
4
+ if (!process.env.LAYOUT_ID) console.error('You need to set a layout id');
5
+
6
+ const market = marketAPI(false);
7
+
8
+ console.log('Connecting...');
9
+
10
+ (async () => {
11
+ /*
12
+ * Important ! If you want to get a layout without login, you have to
13
+ * set it in public mode. If you get an error, please wait few minutes.
14
+ * Try getting to https://www.tradingview.com/chart/LAYOUT_ID/ in incognito mode.
15
+ */
16
+ const user = (process.env.SESSION_ID) ? await market.getUser(process.env.SESSION_ID) : {};
17
+ console.log('Connected as', user.firstName || user.username || 'anonymous', '!');
18
+
19
+ console.log(`Gettings drawings of '${process.env.LAYOUT_ID}' layout...`);
20
+
21
+ const drawings = await market.getDrawings(process.env.LAYOUT_ID, '', {
22
+ id: user.id,
23
+ session: user.session,
24
+ });
25
+
26
+ const lines = drawings.filter((d) => (
27
+ ['LineToolHorzLine', 'LineToolRay', 'LineToolTrendLine', 'LineToolHorzRay'].includes(d.type)
28
+ && d.state.linestyle === 0
29
+ && d.state.linewidth === 4
30
+ ));
31
+
32
+ console.log(
33
+ `Found ${drawings.length} result(s) including ${lines.length} thick line(s):`,
34
+ lines.map((l) => ({
35
+ market: l.symbol,
36
+ type: l.type,
37
+ points: l.points,
38
+ text: l.state.text,
39
+ })),
40
+ );
41
+ })();
package/tests/analysis.js CHANGED
@@ -1,13 +1,13 @@
1
- const marketAPI = require('../main');
2
-
3
- const market = marketAPI();
4
-
5
- market.on('logged', async () => {
6
- const searchBTC = (await market.search('bitcoin euro', 'crypto'))[0];
7
- console.log('Found Bitcoin / Euro:', searchBTC);
8
-
9
- const TA = await searchBTC.getTA();
10
- console.log('Full technical analysis for Bitcoin:', TA);
11
-
12
- // You can also use this way: await market.getTA('crypto', 'BINANCE:BTCEUR');
13
- });
1
+ const marketAPI = require('../main');
2
+
3
+ (async () => {
4
+ const market = marketAPI(false);
5
+
6
+ const searchBTC = (await market.search('bitcoin euro', 'crypto'))[0];
7
+ console.log('Found Bitcoin / Euro:', searchBTC);
8
+
9
+ const TA = await searchBTC.getTA();
10
+ console.log('Full technical analysis for Bitcoin:', TA);
11
+
12
+ // You can also use this way: await market.getTA('crypto', 'BINANCE:BTCEUR');
13
+ })();