@mathieuc/tradingview 2.0.91 → 3.2.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.
Files changed (56) hide show
  1. package/.eslintrc.js +18 -16
  2. package/README.md +19 -98
  3. package/examples/AllPrivateIndicators.js +35 -0
  4. package/examples/BuiltInIndicator.js +41 -0
  5. package/examples/CurrentTimeframe.js +34 -0
  6. package/examples/CustomChartType.js +122 -0
  7. package/examples/CustomTimeframe.js +29 -0
  8. package/examples/Errors.js +147 -0
  9. package/examples/FakeReplayMode.js +41 -0
  10. package/examples/FromToData.js +29 -0
  11. package/examples/GraphicIndicator.js +36 -0
  12. package/examples/MultipleSyncFetch.js +38 -0
  13. package/examples/PinePermManage.js +57 -0
  14. package/examples/ReplayMode.js +97 -0
  15. package/examples/Search.js +15 -0
  16. package/examples/SimpleChart.js +63 -0
  17. package/examples/_RenkoIndicator.js +41 -0
  18. package/main.js +11 -504
  19. package/package.json +9 -8
  20. package/src/FormData.js +35 -0
  21. package/src/chart/graphicParser.js +308 -0
  22. package/src/chart/session.js +546 -0
  23. package/src/chart/study.js +403 -0
  24. package/src/classes/BuiltInIndicator.js +124 -0
  25. package/src/classes/PineIndicator.js +132 -0
  26. package/src/classes/PinePermManager.js +142 -0
  27. package/src/client.js +280 -0
  28. package/{miscRequests.js → src/miscRequests.js} +102 -89
  29. package/src/protocol.js +54 -0
  30. package/src/quote/market.js +123 -0
  31. package/src/quote/session.js +125 -0
  32. package/src/request.js +37 -0
  33. package/src/types.js +32 -0
  34. package/src/utils.js +8 -0
  35. package/test.js +64 -0
  36. package/tests/1. Search.js +18 -0
  37. package/tests/2. QuoteSession.js +54 -0
  38. package/tests/3. SimpleChart.js +61 -0
  39. package/tests/4. Indicators.js +99 -0
  40. package/tests/5. BuiltInIndicator.js +41 -0
  41. package/tests/6. FakeReplayMode.js +59 -0
  42. package/tests/7. ReplayMode.js +51 -0
  43. package/tests/8. CustomChartTypes.js +115 -0
  44. package/tests/9. AllErrors.js +146 -0
  45. package/tests/disabled/x. Template.js +24 -0
  46. package/.github/FUNDING.yml +0 -1
  47. package/tests/alerts/discordWH.js +0 -76
  48. package/tests/alerts/drawings.js +0 -41
  49. package/tests/alerts/envLoader.js +0 -11
  50. package/tests/alerts/manualAlert.js +0 -77
  51. package/tests/alerts/sentNotifs.js +0 -16
  52. package/tests/alerts/strategyAlert.js +0 -94
  53. package/tests/analysis.js +0 -13
  54. package/tests/indicator.js +0 -42
  55. package/tests/prices.js +0 -33
  56. package/tests/strategy.js +0 -56
@@ -0,0 +1,142 @@
1
+ const request = require('../request');
2
+ const FormData = require('../FormData');
3
+
4
+ /**
5
+ * @typedef {Object} AuthorizationUser
6
+ * @prop {id} id User id
7
+ * @prop {string} username User's username
8
+ * @prop {string} userpic User's profile picture URL
9
+ * @prop {string} expiration Authorization expiration date
10
+ * @prop {string} created Authorization creation date
11
+ */
12
+
13
+ class PinePermManager {
14
+ sessionId;
15
+
16
+ pineId;
17
+
18
+ /**
19
+ * Creates a PinePermManager instance
20
+ * @param {string} sessionId Token from sessionid cookie
21
+ * @param {string} pineId Indicator ID (Like: PUB;XXXXXXXXXXXXXXXXXXXXX)
22
+ */
23
+ constructor(sessionId, pineId) {
24
+ if (!sessionId) throw new Error('Please provide a SessionID');
25
+ if (!pineId) throw new Error('Please provide a PineID');
26
+ this.pineId = pineId;
27
+ this.sessionId = sessionId;
28
+ }
29
+
30
+ /**
31
+ * Get list of authorized users
32
+ * @param {number} limit Fetching limit
33
+ * @param {'user__username'
34
+ * | '-user__username'
35
+ * | 'created' | 'created'
36
+ * | 'expiration,user__username'
37
+ * | '-expiration,user__username'
38
+ * } order Fetching order
39
+ * @returns {AuthorizationUser[]}
40
+ */
41
+ async getUsers(limit = 10, order = '-created') {
42
+ const data = await request({
43
+ method: 'POST',
44
+ host: 'www.tradingview.com',
45
+ path: `/pine_perm/list_users/?limit=${limit}&order_by=${order}`,
46
+ headers: {
47
+ origin: 'https://www.tradingview.com',
48
+ 'Content-Type': 'application/x-www-form-urlencoded',
49
+ cookie: `sessionid=${this.sessionId}`,
50
+ },
51
+ }, false, `pine_id=${this.pineId.replace(/;/g, '%3B')}`);
52
+
53
+ if (!data.results) throw new Error('Wrong sessionId or pineId');
54
+
55
+ return data.results;
56
+ }
57
+
58
+ /**
59
+ * Adds an user to the authorized list
60
+ * @param {string} username User's username
61
+ * @param {Date} [expiration] Expiration date
62
+ * @returns {'ok' | 'exists' | null}
63
+ */
64
+ async addUser(username, expiration = null) {
65
+ const formData = new FormData();
66
+ formData.append('pine_id', this.pineId);
67
+ formData.append('username_recip', username);
68
+ if (expiration && expiration instanceof Date) {
69
+ formData.append('expiration', expiration.toString());
70
+ }
71
+
72
+ const data = await request({
73
+ method: 'POST',
74
+ host: 'www.tradingview.com',
75
+ path: '/pine_perm/add/',
76
+ headers: {
77
+ origin: 'https://www.tradingview.com',
78
+ 'Content-Type': `multipart/form-data; boundary=${formData.boundary}`,
79
+ cookie: `sessionid=${this.sessionId}`,
80
+ },
81
+ }, false, formData.toString());
82
+
83
+ if (!data.status) throw new Error('Wrong sessionId or pineId');
84
+ return data.status;
85
+ }
86
+
87
+ /**
88
+ * Modify an authorization expiration date
89
+ * @param {string} username User's username
90
+ * @param {Date} [expiration] New expiration date
91
+ * @returns {'ok' | null}
92
+ */
93
+ async modifyExpiration(username, expiration = null) {
94
+ const formData = new FormData();
95
+ formData.append('pine_id', this.pineId);
96
+ formData.append('username_recip', username);
97
+ if (expiration && expiration instanceof Date) {
98
+ formData.append('expiration', expiration.toISOString());
99
+ }
100
+
101
+ const data = await request({
102
+ method: 'POST',
103
+ host: 'www.tradingview.com',
104
+ path: '/pine_perm/modify_user_expiration/',
105
+ headers: {
106
+ origin: 'https://www.tradingview.com',
107
+ 'Content-Type': `multipart/form-data; boundary=${formData.boundary}`,
108
+ cookie: `sessionid=${this.sessionId}`,
109
+ },
110
+ }, false, formData.toString());
111
+
112
+ if (!data.status) throw new Error('Wrong sessionId or pineId');
113
+ return data.status;
114
+ }
115
+
116
+ /**
117
+ * Removes an user to the authorized list
118
+ * @param {string} username User's username
119
+ * @returns {'ok' | null}
120
+ */
121
+ async removeUser(username) {
122
+ const formData = new FormData();
123
+ formData.append('pine_id', this.pineId);
124
+ formData.append('username_recip', username);
125
+
126
+ const data = await request({
127
+ method: 'POST',
128
+ host: 'www.tradingview.com',
129
+ path: '/pine_perm/remove/',
130
+ headers: {
131
+ origin: 'https://www.tradingview.com',
132
+ 'Content-Type': `multipart/form-data; boundary=${formData.boundary}`,
133
+ cookie: `sessionid=${this.sessionId}`,
134
+ },
135
+ }, false, formData.toString());
136
+
137
+ if (!data.status) throw new Error('Wrong sessionId or pineId');
138
+ return data.status;
139
+ }
140
+ }
141
+
142
+ module.exports = PinePermManager;
package/src/client.js ADDED
@@ -0,0 +1,280 @@
1
+ const WebSocket = require('ws');
2
+
3
+ const misc = require('./miscRequests');
4
+ const protocol = require('./protocol');
5
+
6
+ const quoteSessionGenerator = require('./quote/session');
7
+ const chartSessionGenerator = require('./chart/session');
8
+
9
+ /**
10
+ * @typedef {Object} Session
11
+ * @prop {'quote' | 'chart' | 'replay'} type Session type
12
+ * @prop {(data: {}) => null} onData When there is a data
13
+ */
14
+
15
+ /** @typedef {Object<string, Session>} SessionList Session list */
16
+
17
+ /**
18
+ * @typedef {(t: string, p: []) => null} SendPacket Send a custom packet
19
+ * @param {string} t Packet type
20
+ * @param {string[]} p Packet data
21
+ */
22
+
23
+ /**
24
+ * @typedef {Object} ClientBridge
25
+ * @prop {SessionList} sessions
26
+ * @prop {SendPacket} send
27
+ */
28
+
29
+ /**
30
+ * @typedef { 'connected' | 'disconnected'
31
+ * | 'logged' | 'ping' | 'data'
32
+ * | 'error' | 'event'
33
+ * } ClientEvent
34
+ */
35
+
36
+ module.exports = class Client {
37
+ #ws;
38
+
39
+ #logged = false;
40
+
41
+ /** If the client is logged in */
42
+ get isLogged() {
43
+ return this.#logged;
44
+ }
45
+
46
+ /** If the cient was closed */
47
+ get isOpen() {
48
+ return this.#ws.readyState === this.#ws.OPEN;
49
+ }
50
+
51
+ /** @type {SessionList} */
52
+ #sessions = {};
53
+
54
+ #callbacks = {
55
+ connected: [],
56
+ disconnected: [],
57
+ logged: [],
58
+ ping: [],
59
+ data: [],
60
+
61
+ error: [],
62
+ event: [],
63
+ };
64
+
65
+ /**
66
+ * @param {ClientEvent} ev Client event
67
+ * @param {...{}} data Packet data
68
+ */
69
+ #handleEvent(ev, ...data) {
70
+ this.#callbacks[ev].forEach((e) => e(...data));
71
+ this.#callbacks.event.forEach((e) => e(ev, ...data));
72
+ }
73
+
74
+ #handleError(...msgs) {
75
+ if (this.#callbacks.error.length === 0) console.error(...msgs);
76
+ else this.#handleEvent('error', ...msgs);
77
+ }
78
+
79
+ /**
80
+ * When client is connected
81
+ * @param {() => void} cb Callback
82
+ * @event
83
+ */
84
+ onConnected(cb) {
85
+ this.#callbacks.connected.push(cb);
86
+ }
87
+
88
+ /**
89
+ * When client is disconnected
90
+ * @param {() => void} cb Callback
91
+ * @event
92
+ */
93
+ onDisconnected(cb) {
94
+ this.#callbacks.disconnected.push(cb);
95
+ }
96
+
97
+ /**
98
+ * @typedef {Object} SocketSession
99
+ * @prop {string} session_id Socket session ID
100
+ * @prop {number} timestamp Session start timestamp
101
+ * @prop {number} timestampMs Session start milliseconds timestamp
102
+ * @prop {string} release Release
103
+ * @prop {string} studies_metadata_hash Studies metadata hash
104
+ * @prop {'json' | string} protocol Used protocol
105
+ * @prop {string} javastudies Javastudies
106
+ * @prop {number} auth_scheme_vsn Auth scheme type
107
+ * @prop {string} via Socket IP
108
+
109
+ * When client is logged
110
+ * @param {(SocketSession: SocketSession) => void} cb Callback
111
+ * @event
112
+ */
113
+ onLogged(cb) {
114
+ this.#callbacks.logged.push(cb);
115
+ }
116
+
117
+ /**
118
+ * When server is pinging the client
119
+ * @param {(i: number) => void} cb Callback
120
+ * @event
121
+ */
122
+ onPing(cb) {
123
+ this.#callbacks.ping.push(cb);
124
+ }
125
+
126
+ /**
127
+ * When unparsed data is received
128
+ * @param {(...{}) => void} cb Callback
129
+ * @event
130
+ */
131
+ onData(cb) {
132
+ this.#callbacks.data.push(cb);
133
+ }
134
+
135
+ /**
136
+ * When a client error happens
137
+ * @param {(...{}) => void} cb Callback
138
+ * @event
139
+ */
140
+ onError(cb) {
141
+ this.#callbacks.error.push(cb);
142
+ }
143
+
144
+ /**
145
+ * When a client event happens
146
+ * @param {(...{}) => void} cb Callback
147
+ * @event
148
+ */
149
+ onEvent(cb) {
150
+ this.#callbacks.event.push(cb);
151
+ }
152
+
153
+ #parsePacket(str) {
154
+ if (!this.isOpen) return;
155
+
156
+ protocol.parseWSPacket(str).forEach((packet) => {
157
+ if (global.TW_DEBUG) console.log('§90§30§107 CLIENT §0 PACKET', packet);
158
+ if (typeof packet === 'number') { // Ping
159
+ this.#ws.send(protocol.formatWSPacket(`~h~${packet}`));
160
+ this.#handleEvent('ping', packet);
161
+ return;
162
+ }
163
+
164
+ if (packet.m === 'protocol_error') { // Error
165
+ this.#handleError('Client critical error:', packet.p);
166
+ this.#ws.close();
167
+ return;
168
+ }
169
+
170
+ if (packet.m && packet.p) { // Normal packet
171
+ const parsed = {
172
+ type: packet.m,
173
+ data: packet.p,
174
+ };
175
+
176
+ const session = packet.p[0];
177
+
178
+ if (session && this.#sessions[session]) {
179
+ this.#sessions[session].onData(parsed);
180
+ return;
181
+ }
182
+ }
183
+
184
+ if (!this.#logged) {
185
+ this.#handleEvent('logged', packet);
186
+ return;
187
+ }
188
+
189
+ this.#handleEvent('data', packet);
190
+ });
191
+ }
192
+
193
+ #sendQueue = [];
194
+
195
+ /** @type {SendPacket} Send a custom packet */
196
+ send(t, p = []) {
197
+ this.#sendQueue.push(protocol.formatWSPacket({ m: t, p }));
198
+ this.sendQueue();
199
+ }
200
+
201
+ /** Send all waiting packets */
202
+ sendQueue() {
203
+ while (this.isOpen && this.#logged && this.#sendQueue.length > 0) {
204
+ const packet = this.#sendQueue.shift();
205
+ this.#ws.send(packet);
206
+ }
207
+ }
208
+
209
+ /**
210
+ * @typedef {Object} ClientOptions
211
+ * @prop {string} [token] User auth token (in 'sessionid' cookie)
212
+ * @prop {boolean} [DEBUG] Enable debug mode
213
+ */
214
+
215
+ /** Client object
216
+ * @param {ClientOptions} clientOptions TradingView client options
217
+ */
218
+ constructor(clientOptions = {}) {
219
+ if (clientOptions.DEBUG) global.TW_DEBUG = clientOptions.DEBUG;
220
+
221
+ this.#ws = new WebSocket('wss://widgetdata.tradingview.com/socket.io/websocket', {
222
+ origin: 'https://s.tradingview.com',
223
+ });
224
+
225
+ if (clientOptions.token) {
226
+ misc.getUser(clientOptions.token).then((user) => {
227
+ this.#sendQueue.unshift(protocol.formatWSPacket({
228
+ m: 'set_auth_token',
229
+ p: [user.authToken],
230
+ }));
231
+ this.#logged = true;
232
+ this.sendQueue();
233
+ }).catch((err) => {
234
+ this.#handleError('Credentials error:', err.message);
235
+ });
236
+ } else {
237
+ this.#sendQueue.unshift(protocol.formatWSPacket({
238
+ m: 'set_auth_token',
239
+ p: ['unauthorized_user_token'],
240
+ }));
241
+ this.#logged = true;
242
+ this.sendQueue();
243
+ }
244
+
245
+ this.#ws.on('open', () => {
246
+ this.#handleEvent('connected');
247
+ this.sendQueue();
248
+ });
249
+
250
+ this.#ws.on('close', () => {
251
+ this.#logged = false;
252
+ this.#handleEvent('disconnected');
253
+ });
254
+
255
+ this.#ws.on('message', (data) => this.#parsePacket(data));
256
+ }
257
+
258
+ /** @type {ClientBridge} */
259
+ #clientBridge = {
260
+ sessions: this.#sessions,
261
+ send: (t, p) => this.send(t, p),
262
+ };
263
+
264
+ /** @namespace Session */
265
+ Session = {
266
+ Quote: quoteSessionGenerator(this.#clientBridge),
267
+ Chart: chartSessionGenerator(this.#clientBridge),
268
+ };
269
+
270
+ /**
271
+ * Close the websocket connection
272
+ * @return {Promise<void>} When websocket is closed
273
+ */
274
+ end() {
275
+ return new Promise((cb) => {
276
+ if (this.#ws.readyState) this.#ws.close();
277
+ cb();
278
+ });
279
+ }
280
+ };