market-data-tradingview-ws 0.0.14 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -108,6 +108,8 @@ module.exports = class Client {
108
108
  };
109
109
 
110
110
  #token = null;
111
+ #sessionid = null;
112
+ #sessionidsign = null;
111
113
 
112
114
  constructor() {
113
115
  // this.#resolved = new Map();
@@ -130,16 +132,8 @@ module.exports = class Client {
130
132
  return { charts: this.#charts.values };
131
133
  }
132
134
 
133
- // getResolved() {
134
- // return { resolved: this.#resolved };
135
- // }
136
-
137
- // getSessionKeys() {
138
- // return this.#sessions.values.keys();
139
- // }
140
-
141
- async getToken({ login, pass }) {
142
- return tv.getToken({ login, pass });
135
+ async getToken({ sessionid, sessionidsign, tvecuid, imageurl }) {
136
+ return tv.getToken({ sessionid, sessionidsign, tvecuid, imageurl });
143
137
  }
144
138
 
145
139
  auth(token = 'unauthorized_user_token') {
@@ -151,38 +145,54 @@ module.exports = class Client {
151
145
  });
152
146
  }
153
147
 
154
- async connect({ url, options, login, pass, result, token }) {
155
- this.#token = token || await this.getToken({ login, pass });
156
-
157
- if (this.#token.length > 10) {
158
- return new Promise((resolv, reject) => {
159
- url += '?from=chart%2FKq8EfKQU%2F';
160
- const date = new Date();
161
- url += '&date=' + date.getFullYear() + '_' + String(date.getMonth() + 1).padStart(2, '0') + '_' + String(date.getDate()).padStart(2, '0') + '-11_25';
162
- url += '&type=chart';
163
- ws = new WebSocket(url, options);
164
-
165
- const originalSend = ws.send;
166
- ws.send = function(data) {
167
- // console.log('Message sent:', data);
168
- originalSend.apply(ws, arguments);
169
- };
170
-
171
- ws.onclose = () => console.log('disconnected');
172
-
173
- ws.onerror = (error) => reject(console.error('error: ', error));
174
- ws.onopen = () => {
175
- ws.send(this.auth(this.#token));
176
- ws.send(tv.setLocaleRu());
177
-
178
- const quoteKey = this.createQuoteSmallSession();
179
-
180
- this.messageListner({ result });
181
- resolv('open');
182
- };
183
- });
148
+ async connect({ url, options, result, sessionid, sessionidsign, tvecuid, imageurl }) {
149
+ try {
150
+ this.#token = await this.getToken({ sessionid, sessionidsign, tvecuid, imageurl });
151
+ this.#sessionid = sessionid;
152
+ this.#sessionidsign = sessionidsign;
153
+
154
+ if (this.#token.length > 10) {
155
+ return new Promise((resolv, reject) => {
156
+ try {
157
+ url += '?from=chart%2F' + imageurl + '%2F';
158
+ const date = new Date();
159
+ url += '&date=' + date.getFullYear() + '_' + String(date.getMonth() + 1).padStart(2, '0') + '_' + String(date.getDate()).padStart(2, '0') + '-11_25';
160
+ url += '&type=chart';
161
+ ws = new WebSocket(url, options);
162
+
163
+ const originalSend = ws.send;
164
+ ws.send = function(data) {
165
+ // console.log('Message sent:', data);
166
+ originalSend.apply(ws, arguments);
167
+ };
168
+
169
+ ws.onclose = () => console.log('disconnected');
170
+
171
+ ws.onerror = (error) => reject(console.error('error: ', error));
172
+ ws.onopen = () => {
173
+ ws.send(this.auth(this.#token));
174
+ ws.send(tv.setLocaleRu());
175
+
176
+ const quoteKey = this.createQuoteSmallSession();
177
+
178
+ this.messageListner({ result });
179
+ resolv('open');
180
+ };
181
+ } catch (error) {
182
+ reject(error);
183
+ }
184
+ });
185
+ }
186
+ return null;
187
+ } catch (error) {
188
+ console.error('Error in connect:', error.message);
189
+ throw error;
184
190
  }
185
- return null;
191
+ }
192
+
193
+ async disconnect() {
194
+ tv.disconnect({ sessionid: this.#sessionid, sessionidsign: this.#sessionidsign });
195
+ return 'close';
186
196
  }
187
197
 
188
198
  createQuoteSmallSession() {
@@ -235,6 +245,10 @@ module.exports = class Client {
235
245
  }
236
246
 
237
247
  addChartSymbol({ symbol, period, limit }) {
248
+ if (!symbol || typeof symbol !== 'string') throw new Error('Invalid symbol parameter');
249
+ if (!period || typeof period !== 'number') throw new Error('Invalid period parameter');
250
+ if (!limit || typeof limit !== 'number') throw new Error('Invalid limit parameter');
251
+
238
252
  let key = this.#charts.getBySymbolPeriod({ symbol, period });
239
253
  if (key) {
240
254
  console.warn('addChartSymbol exist:', key, symbol, period);
@@ -260,7 +274,7 @@ module.exports = class Client {
260
274
  frame: data.frame,
261
275
  increment: data.i,
262
276
  symbolKey: data.symbolKey,
263
- interval: periods[period],
277
+ period: periods[period],
264
278
  limit,
265
279
  }),
266
280
  );
@@ -272,15 +286,6 @@ module.exports = class Client {
272
286
  updateChartSymbol({ symbol, period, limit, last }) {
273
287
  const key = this.#charts.getBySymbolPeriod({ symbol: last.symbol, period: last.period });
274
288
  const data = this.#charts.get({ key });
275
-
276
- // console.warn('modules updateChartSymbol 1: ', symbol, period, limit, last, data);
277
- // const alreadyKey = this.#charts.getBySymbolPeriod({ symbol, period })
278
- // if (alreadyKey) {
279
- // // console.warn('updateChartSymbol exist: ', alreadyKey);
280
- // this.#charts.delete({ key });
281
- // // console.warn('updateChartSymbol exist');
282
- // return 'exist';
283
- // }
284
289
 
285
290
  data.symbolKey = this.#symbols.getSymbol({ key, symbol }).key;
286
291
  data.symbol = symbol;
@@ -293,7 +298,7 @@ module.exports = class Client {
293
298
  frame: data.frame,
294
299
  increment: data.i,
295
300
  symbolKey: data.symbolKey,
296
- interval: periods[period],
301
+ period: periods[period],
297
302
  }),
298
303
  );
299
304
  this.#charts.set({ key, data });
@@ -318,52 +323,68 @@ module.exports = class Client {
318
323
  // console.log('raw: ', JSON.stringify(raw.toString()));
319
324
  let session = '';
320
325
  tv.parsePacket({ str: raw }).forEach((packet) => {
321
- let name = null;
322
- let send = null;
323
- if (packet === undefined) {
324
- console.warn('undefined: ', raw.toString());
325
- } else {
326
- if (packet.type === 'ping') {
327
- // console.info('pong', packet);
328
- ws.send(tv.createPong({ value: packet.data }));
329
- } else if (packet.type === 'qsd') {
330
- send = packet.data[1].v;
331
- name = send.bid === undefined && send.ask === undefined ? 'data' : 'levelI';
332
- // console.log(packet.data[1].n);
333
- send.symbol =(extractJSON(packet.data[1].n) || {symbol: packet.data[1].n}).symbol;
334
- result(name, send);
335
- } else if (['du', 'timescale_update'].includes(packet.type)) {
336
- session = this.#charts.get({ key: packet.data[0] });
337
- if (packet.data[1][session.frame].s.length > 0) {
338
- send = {
339
- symbol: session.symbol,
340
- period: parseInt(session.period),
341
- chart: [],
342
- end: packet.data[1][session.frame].lbs.bar_close_time,
343
- };
344
- for (let each of packet.data[1][session.frame].s) {
345
- send.chart.push({
346
- open: each.v[1],
347
- high: each.v[2],
348
- low: each.v[3],
349
- close: each.v[4],
350
- timestamp: each.v[0] * 1000,
351
- // turnover: bar.TradeCount,
352
- volume: each.v[5],
353
- });
354
- }
355
- result(packet.type === 'du' ? 'chart_update' : 'chart_history', send);
356
- }
357
- } else if (['series', 'symbol', 'quote', 'session'].includes(packet.type)) {
358
- // console.info(packet);
359
- } else {
360
- // console.error('error', raw.toString());
361
- result('error', raw.toString())
362
- }
363
- }
364
- // name = null;
365
- // send = null;
326
+ this.handlePacket({ packet, result, raw });
366
327
  });
367
328
  });
368
329
  }
330
+
331
+ handlePacket({ packet, result, raw }) {
332
+ if (!packet) {
333
+ console.warn('Undefined packet:', raw.toString());
334
+ return;
335
+ }
336
+
337
+ switch (packet.type) {
338
+ case 'ping':
339
+ ws.send(tv.createPong({ value: packet.data }));
340
+ break;
341
+ case 'qsd':
342
+ this.handleQsdPacket({ packet, result });
343
+ break;
344
+ case 'du':
345
+ case 'timescale_update':
346
+ this.handleChartPacket({ packet, result });
347
+ break;
348
+ case 'session':
349
+ console.info(packet);
350
+ break;
351
+ case 'symbol':
352
+ case 'series':
353
+ case 'quote':
354
+ break;
355
+ default:
356
+ console.error('Unhandled packet type:', packet.type);
357
+ result('error', raw.toString());
358
+ }
359
+ }
360
+
361
+ handleQsdPacket({ packet, result }) {
362
+ const send = packet.data[1].v;
363
+ const name = send.bid === undefined && send.ask === undefined ? 'data' : 'levelI';
364
+ send.symbol = (extractJSON(packet.data[1].n) || { symbol: packet.data[1].n }).symbol;
365
+ result(name, send);
366
+ }
367
+
368
+ handleChartPacket({ packet, result }) {
369
+ const session = this.#charts.get({ key: packet.data[0] });
370
+ if (packet.data[1][session.frame].s.length > 0) {
371
+ const send = {
372
+ symbol: session.symbol,
373
+ period: parseInt(session.period),
374
+ chart: [],
375
+ end: packet.data[1][session.frame].lbs.bar_close_time,
376
+ };
377
+ packet.data[1][session.frame].s.forEach((each) => {
378
+ send.chart.push({
379
+ open: each.v[1],
380
+ high: each.v[2],
381
+ low: each.v[3],
382
+ close: each.v[4],
383
+ timestamp: each.v[0] * 1000,
384
+ volume: each.v[5],
385
+ });
386
+ });
387
+ result(packet.type === 'du' ? 'chart_update' : 'chart_history', send);
388
+ }
389
+ }
369
390
  };
@@ -1,19 +1,17 @@
1
- /* eslint-disable no-unused-vars */
2
1
  'use strict';
3
2
 
4
- // const Utils = require('./utils.js');
5
- // const utils = new Utils();
6
-
7
3
  const letters = 'abcdefghijklmnopqrstuvwxyz';
8
4
  const cleanerRgx = /~h~/g;
9
5
  const splitterRgx = /~m~[0-9]{1,}~m~/g;
10
6
  const urlSymbolSearch = 'https://symbol-search.tradingview.com/symbol_search';
7
+ const urlwwwTV = 'https://www.tradingview.com';
8
+ const urlRuTV = 'https://ru.tradingview.com';
11
9
 
12
10
  class TradingView {
13
- async getToken({ login, pass }) {
14
- const url = 'https://www.tradingview.com/accounts/signin/';
11
+ async authentication({ login, pass }) {
12
+ const url = urlwwwTV + '/accounts/signin/';
15
13
  const headers = {
16
- Referer: 'https://www.tradingview.com',
14
+ Referer: urlwwwTV,
17
15
  };
18
16
 
19
17
  const form = new FormData();
@@ -21,35 +19,91 @@ class TradingView {
21
19
  form.append('password', pass);
22
20
  form.append('remember', 'on');
23
21
 
24
- // const res = await fetch(url, {
25
- // method: 'POST',
26
- // headers,
27
- // body: form,
28
- // });
29
-
30
- // if (res.status === 200) {
31
- // const data = await res.json();
32
- // if (data.user.auth_token === undefined) throw new Error('Error get auth_token', data);
33
- // console.log(data.user.auth_token);
34
- // return data.user.auth_token;
35
- // }
36
-
37
- // await utils.pause();
38
- return 'eyJhbGciOiJSUzUxMiIsImtpZCI6IkdaeFUiLCJ0eXAiOiJKV1QifQ.eyJ1c2VyX2lkIjoxMjI1ODcxMiwiZXhwIjoxNzI0OTI1MjkyLCJpYXQiOjE3MjQ5MTA4OTIsInBsYW4iOiJwcm9fcHJlbWl1bSIsImV4dF9ob3VycyI6MSwicGVybSI6InVzLXN0b2NrcyxuYXNkYXFfZ2lkcyxvdGMsbnlzZSxuYXNkYXEsYW1leCIsInN0dWR5X3Blcm0iOiJ0di1jaGFydHBhdHRlcm5zLHR2LXByb3N0dWRpZXMsdHYtY2hhcnRfcGF0dGVybnMsdHYtdm9sdW1lYnlwcmljZSIsIm1heF9zdHVkaWVzIjoyNSwibWF4X2Z1bmRhbWVudGFscyI6MTAsIm1heF9jaGFydHMiOjgsIm1heF9hY3RpdmVfYWxlcnRzIjo0MDAsIm1heF9zdHVkeV9vbl9zdHVkeSI6MjQsImZpZWxkc19wZXJtaXNzaW9ucyI6WyJyZWZib25kcyJdLCJtYXhfb3ZlcmFsbF9hbGVydHMiOjIwMDAsIm1heF9hY3RpdmVfcHJpbWl0aXZlX2FsZXJ0cyI6NDAwLCJtYXhfYWN0aXZlX2NvbXBsZXhfYWxlcnRzIjo0MDAsIm1heF9jb25uZWN0aW9ucyI6NTB9.UT2AdPBEAWk6HJEa6isX4gjVqlfThn8bgOVl3Xa36U36Qifg5xXz9Va8q7DWSY3Smaap2wX4cBF4UQXv1t66Q0TQvFudLuvA2dnetBAHYZSOJIi2PGdG1KuMaCRshUuuH9mxgw_xPGITAuQsbBPZJLTOxq5pQN05pZnedC7SaMs';
22
+ try {
23
+ const res = await fetch(url, {
24
+ method: 'POST',
25
+ headers,
26
+ body: form,
27
+ });
28
+
29
+ if (!res.ok) throw new Error('Failed to get auth_token, status: ' + res.status);
30
+
31
+ const rawSetCookie = response.headers.raw()['set-cookie'];
32
+ console.log('Raw Set-Cookie headers:', rawSetCookie);
33
+ let sessionid, sessionid_sign;
34
+ for (const cookie of rawSetCookie) {
35
+ if (cookie.startsWith('sessionid=')) sessionid = cookie.split(';')[0].split('=')[1];
36
+ if (cookie.startsWith('sessionid_sign=')) sessionid_sign = cookie.split(';')[0].split('=')[1];
37
+ }
38
+
39
+ const data = await res.json();
40
+ if (!data?.user?.auth_token) throw new Error('Error get auth_token, no auth_token in response');
41
+ return { sessionid, sessionid_sign, token: data.user.auth_token };
42
+ } catch (error) {
43
+ throw new Error('Authentication failed: ' + error.message);
44
+ }
45
+ }
46
+
47
+ async getToken({ sessionid, sessionidsign, tvecuid, imageurl }) {
48
+ const url = urlRuTV + '/chart/' + imageurl;
49
+ const headers = {
50
+ 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
51
+ 'accept-language': 'ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7',
52
+ 'cache-control': 'max-age=0',
53
+ 'upgrade-insecure-requests': '1',
54
+ 'cookie': 'tv_ecuid=' + tvecuid + '; sessionid=' + sessionid + '; sessionid_sign=' + sessionidsign + ';cookiesSettings={\"analytics\":true,\"advertising\":true};backend=test_backend',
55
+ 'Referer': urlRuTV + '/u/PartnerFinance/',
56
+ 'Referrer-Policy': 'origin-when-cross-origin',
57
+ };
58
+
59
+ try {
60
+ const res = await fetch(url, { method: 'GET', headers });
61
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
62
+ const html = await res.text();
63
+ const match = html.match(/"auth_token"\s*:\s*"([^"]+)"/);
64
+ return match[1];
65
+ } catch (err) {
66
+ console.error('❌ Error fetching token:', err.message);
67
+ return null;
68
+ }
69
+ }
70
+
71
+ async disconnect({ sessionid, sessionidsign }) {
72
+ const url = urlRuTV + '/accounts/logout/';
73
+ const headers = {
74
+ Origin: urlRuTV,
75
+ Referer: urlRuTV + '/u/PartnerFinance/',
76
+ };
77
+
78
+ try {
79
+ // const res = await fetch(url, {
80
+ // method: 'POST',
81
+ // headers,
82
+ // });
83
+
84
+ // if (!res.ok) throw new Error('Failed to disconnect, status: ' + res.status);
85
+ return true;
86
+ } catch (error) {
87
+ throw new Error('Disconnection failed: ' + error.message);
88
+ }
39
89
  }
40
90
 
41
91
  async getSymbol({ symbol, type }) {
42
92
  // type = 'stock' | 'futures' | 'forex' | 'cfd' | 'crypto' | 'index' | 'economic'
43
93
  // query = what you want to search!
44
94
  // it returns first matching item
95
+ try {
45
96
  const res = await fetch(urlSymbolSearch + '?text=' + symbol + '&type=' + type);
46
97
  if (res.status === 200) {
47
98
  const data = await res.json();
48
99
  // console.log(data);
49
- return data[0];
100
+ return data[0];
101
+ }
102
+ throw new Error('Failed to fetch symbol data');
103
+ } catch (error) {
104
+ console.error('Error in getSymbol:', error.message);
105
+ return 'Network Error!';
50
106
  }
51
-
52
- return 'Network Error!';
53
107
  }
54
108
 
55
109
  getSymbolId({ data }) {
@@ -58,6 +112,9 @@ class TradingView {
58
112
  }
59
113
 
60
114
  createSession({ startsWith }) {
115
+ if (typeof startsWith !== 'string' || startsWith.length === 0) {
116
+ throw new Error('Invalid startsWith parameter');
117
+ }
61
118
  let session = startsWith;
62
119
  while (session.length < 12) session += letters[Math.floor(Math.random() * letters.length)];
63
120
  return session;
@@ -268,7 +325,7 @@ class TradingView {
268
325
  // frame - рамка или окно для данной серии
269
326
  // increment - инкрементируемый номер изменений запрашиваемого графика в данном frame (таймефрейм)
270
327
  createSeries({ chartSession, frame = 'sds_1', increment = 's1', symbolKey, period = '60', limit = 1000 }) {
271
- // console.warn('createSeries', chartSession, frame, increment, symbolKey, interval, limit);
328
+ // console.warn('createSeries', chartSession, frame, increment, symbolKey, period, limit);
272
329
  return this.formatWSPacket({
273
330
  packet: { m: 'create_series', p: [chartSession, frame, increment, symbolKey, period, limit] },
274
331
  });
@@ -285,10 +342,10 @@ class TradingView {
285
342
  return this.formatWSPacket({ packet: { m: 'remove_study', p: [ session, st ] } });
286
343
  }
287
344
 
288
- modifySeries({ chartSession, frame = 'sds_1', increment = 's1', symbolKey, interval = '60' }) {
289
- // console.warn('modify_series', chartSession, frame, increment, symbolKey, interval, limit);
345
+ modifySeries({ chartSession, frame = 'sds_1', increment = 's1', symbolKey, period = '60' }) {
346
+ // console.warn('modify_series', chartSession, frame, increment, symbolKey, pariod, limit);
290
347
  return this.formatWSPacket({
291
- packet: { m: 'modify_series', p: [chartSession, frame, increment, symbolKey, interval] },
348
+ packet: { m: 'modify_series', p: [chartSession, frame, increment, symbolKey, period] },
292
349
  });
293
350
  }
294
351
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "market-data-tradingview-ws",
3
- "version": "0.0.14",
3
+ "version": "0.1.0",
4
4
  "description": "marketData from Tradingview ws",
5
5
  "main": "index.js",
6
6
  "scripts": {