market-data-tradingview-ws 0.0.15 → 0.1.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/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,55 @@ 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
+ // console.info('Token:', this.#token);
152
+ this.#sessionid = sessionid;
153
+ this.#sessionidsign = sessionidsign;
154
+
155
+ if (this.#token.length > 10) {
156
+ return new Promise((resolv, reject) => {
157
+ try {
158
+ url += '?from=chart%2F' + imageurl + '%2F';
159
+ const date = new Date();
160
+ url += '&date=' + date.getFullYear() + '_' + String(date.getMonth() + 1).padStart(2, '0') + '_' + String(date.getDate()).padStart(2, '0') + '-11_25';
161
+ url += '&type=chart';
162
+ ws = new WebSocket(url, options);
163
+
164
+ const originalSend = ws.send;
165
+ ws.send = function(data) {
166
+ // console.log('Message sent:', data);
167
+ originalSend.apply(ws, arguments);
168
+ };
169
+
170
+ ws.onclose = () => console.log('disconnected');
171
+
172
+ ws.onerror = (error) => reject(console.error('error: ', error));
173
+ ws.onopen = () => {
174
+ ws.send(this.auth(this.#token));
175
+ ws.send(tv.setLocaleRu());
176
+
177
+ const quoteKey = this.createQuoteSmallSession();
178
+
179
+ this.messageListner({ result });
180
+ resolv('open');
181
+ };
182
+ } catch (error) {
183
+ reject(error);
184
+ }
185
+ });
186
+ }
187
+ return null;
188
+ } catch (error) {
189
+ console.error('Error in connect:', error.message);
190
+ throw error;
184
191
  }
185
- return null;
192
+ }
193
+
194
+ async disconnect() {
195
+ tv.disconnect({ sessionid: this.#sessionid, sessionidsign: this.#sessionidsign });
196
+ return 'close';
186
197
  }
187
198
 
188
199
  createQuoteSmallSession() {
@@ -234,7 +245,11 @@ module.exports = class Client {
234
245
  return 'delete';
235
246
  }
236
247
 
237
- addChartSymbol({ symbol, period, limit }) {
248
+ addChartSymbol({ symbol, period, limit = 1500 }) {
249
+ if (!symbol || typeof symbol !== 'string') console.debug('Invalid symbol parameter', symbol); // throw new Error('Invalid symbol parameter', symbol);
250
+ if (!period || typeof period !== 'number') console.debug('Invalid period parameter', period); // throw new Error('Invalid period parameter', period);
251
+ if (!limit || typeof limit !== 'number') console.debug('Invalid limit parameter', limit); // throw new Error('Invalid limit parameter', limit);
252
+
238
253
  let key = this.#charts.getBySymbolPeriod({ symbol, period });
239
254
  if (key) {
240
255
  console.warn('addChartSymbol exist:', key, symbol, period);
@@ -272,15 +287,6 @@ module.exports = class Client {
272
287
  updateChartSymbol({ symbol, period, limit, last }) {
273
288
  const key = this.#charts.getBySymbolPeriod({ symbol: last.symbol, period: last.period });
274
289
  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
290
 
285
291
  data.symbolKey = this.#symbols.getSymbol({ key, symbol }).key;
286
292
  data.symbol = symbol;
@@ -318,52 +324,68 @@ module.exports = class Client {
318
324
  // console.log('raw: ', JSON.stringify(raw.toString()));
319
325
  let session = '';
320
326
  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;
327
+ this.handlePacket({ packet, result, raw });
366
328
  });
367
329
  });
368
330
  }
331
+
332
+ handlePacket({ packet, result, raw }) {
333
+ if (!packet) {
334
+ console.warn('Undefined packet:', raw.toString());
335
+ return;
336
+ }
337
+
338
+ switch (packet.type) {
339
+ case 'ping':
340
+ ws.send(tv.createPong({ value: packet.data }));
341
+ break;
342
+ case 'qsd':
343
+ this.handleQsdPacket({ packet, result });
344
+ break;
345
+ case 'du':
346
+ case 'timescale_update':
347
+ this.handleChartPacket({ packet, result });
348
+ break;
349
+ case 'session':
350
+ console.info(packet);
351
+ break;
352
+ case 'symbol':
353
+ case 'series':
354
+ case 'quote':
355
+ break;
356
+ default:
357
+ console.error('Unhandled packet type:', packet.type);
358
+ result('error', raw.toString());
359
+ }
360
+ }
361
+
362
+ handleQsdPacket({ packet, result }) {
363
+ const send = packet.data[1].v;
364
+ const name = send.bid === undefined && send.ask === undefined ? 'data' : 'levelI';
365
+ send.symbol = (extractJSON(packet.data[1].n) || { symbol: packet.data[1].n }).symbol;
366
+ result(name, send);
367
+ }
368
+
369
+ handleChartPacket({ packet, result }) {
370
+ const session = this.#charts.get({ key: packet.data[0] });
371
+ if (packet.data[1][session.frame].s.length > 0) {
372
+ const send = {
373
+ symbol: session.symbol,
374
+ period: parseInt(session.period),
375
+ chart: [],
376
+ end: packet.data[1][session.frame].lbs.bar_close_time,
377
+ };
378
+ packet.data[1][session.frame].s.forEach((each) => {
379
+ send.chart.push({
380
+ open: each.v[1],
381
+ high: each.v[2],
382
+ low: each.v[3],
383
+ close: each.v[4],
384
+ timestamp: each.v[0] * 1000,
385
+ volume: each.v[5],
386
+ });
387
+ });
388
+ result(packet.type === 'du' ? 'chart_update' : 'chart_history', send);
389
+ }
390
+ }
369
391
  };
@@ -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,43 +19,104 @@ 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 cookie = 'tv_ecuid=' + tvecuid + '; sessionid=' + sessionid + '; sessionid_sign=' + sessionidsign;
50
+ const headers = {
51
+ 'accept': 'text/html',
52
+ 'cookie': cookie,
53
+ };
54
+
55
+ try {
56
+ const res = await fetch(url, { method: 'GET', headers });
57
+ console.warn('Response status:', res.status);
58
+ const html = await res.text();
59
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
60
+ const match = html.match(/"auth_token"\s*:\s*"([^"]+)"/);
61
+ const token = match ? match[1] : null;
62
+ console.warn('✅ Auth token found');
63
+ if (!token) {
64
+ throw new Error('❌ Auth token not found in HTML');
65
+ }
66
+ return token;
67
+ } catch (err) {
68
+ console.error('❌ Error fetching token:', err.message);
69
+ return null;
70
+ }
71
+ }
72
+
73
+ async disconnect({ sessionid, sessionidsign }) {
74
+ const url = urlRuTV + '/accounts/logout/';
75
+ const headers = {
76
+ Origin: urlRuTV,
77
+ Referer: urlRuTV + '/u/PartnerFinance/',
78
+ };
79
+
80
+ try {
81
+ // const res = await fetch(url, {
82
+ // method: 'POST',
83
+ // headers,
84
+ // });
85
+
86
+ // if (!res.ok) throw new Error('Failed to disconnect, status: ' + res.status);
87
+ return true;
88
+ } catch (error) {
89
+ throw new Error('Disconnection failed: ' + error.message);
90
+ }
39
91
  }
40
92
 
41
93
  async getSymbol({ symbol, type }) {
42
94
  // type = 'stock' | 'futures' | 'forex' | 'cfd' | 'crypto' | 'index' | 'economic'
43
95
  // query = what you want to search!
44
96
  // it returns first matching item
97
+ try {
45
98
  const res = await fetch(urlSymbolSearch + '?text=' + symbol + '&type=' + type);
46
99
  if (res.status === 200) {
47
100
  const data = await res.json();
48
101
  // console.log(data);
49
- return data[0];
102
+ return data[0];
103
+ }
104
+ throw new Error('Failed to fetch symbol data');
105
+ } catch (error) {
106
+ console.error('Error in getSymbol:', error.message);
107
+ return 'Network Error!';
50
108
  }
51
-
52
- return 'Network Error!';
53
109
  }
54
110
 
55
- getSymbolId({ data }) {
56
- const name = data.type === 'futures' ? data.contracts[0].symbol : data.symbol;
57
- return data.exchange.toUpperCase() + ':' + name.toUpperCase();
58
- }
111
+ // getSymbolId({ data }) {
112
+ // const name = data.type === 'futures' ? data.contracts[0].symbol : data.symbol;
113
+ // return data.exchange.toUpperCase() + ':' + name.toUpperCase();
114
+ // }
59
115
 
60
116
  createSession({ startsWith }) {
117
+ if (typeof startsWith !== 'string' || startsWith.length === 0) {
118
+ throw new Error('Invalid startsWith parameter');
119
+ }
61
120
  let session = startsWith;
62
121
  while (session.length < 12) session += letters[Math.floor(Math.random() * letters.length)];
63
122
  return session;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "market-data-tradingview-ws",
3
- "version": "0.0.15",
3
+ "version": "0.1.1",
4
4
  "description": "marketData from Tradingview ws",
5
5
  "main": "index.js",
6
6
  "scripts": {