@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.
- package/.eslintrc.js +18 -16
- package/README.md +19 -98
- package/examples/AllPrivateIndicators.js +35 -0
- package/examples/BuiltInIndicator.js +41 -0
- package/examples/CurrentTimeframe.js +34 -0
- package/examples/CustomChartType.js +122 -0
- package/examples/CustomTimeframe.js +29 -0
- package/examples/Errors.js +147 -0
- package/examples/FakeReplayMode.js +41 -0
- package/examples/FromToData.js +29 -0
- package/examples/GraphicIndicator.js +36 -0
- package/examples/MultipleSyncFetch.js +38 -0
- package/examples/PinePermManage.js +57 -0
- package/examples/ReplayMode.js +97 -0
- package/examples/Search.js +15 -0
- package/examples/SimpleChart.js +63 -0
- package/examples/_RenkoIndicator.js +41 -0
- package/main.js +11 -504
- package/package.json +9 -8
- package/src/FormData.js +35 -0
- package/src/chart/graphicParser.js +308 -0
- package/src/chart/session.js +546 -0
- package/src/chart/study.js +403 -0
- package/src/classes/BuiltInIndicator.js +124 -0
- package/src/classes/PineIndicator.js +132 -0
- package/src/classes/PinePermManager.js +142 -0
- package/src/client.js +280 -0
- package/{miscRequests.js → src/miscRequests.js} +102 -89
- package/src/protocol.js +54 -0
- package/src/quote/market.js +123 -0
- package/src/quote/session.js +125 -0
- package/src/request.js +37 -0
- package/src/types.js +32 -0
- package/src/utils.js +8 -0
- package/test.js +64 -0
- package/tests/1. Search.js +18 -0
- package/tests/2. QuoteSession.js +54 -0
- package/tests/3. SimpleChart.js +61 -0
- package/tests/4. Indicators.js +99 -0
- package/tests/5. BuiltInIndicator.js +41 -0
- package/tests/6. FakeReplayMode.js +59 -0
- package/tests/7. ReplayMode.js +51 -0
- package/tests/8. CustomChartTypes.js +115 -0
- package/tests/9. AllErrors.js +146 -0
- package/tests/disabled/x. Template.js +24 -0
- package/.github/FUNDING.yml +0 -1
- package/tests/alerts/discordWH.js +0 -76
- package/tests/alerts/drawings.js +0 -41
- package/tests/alerts/envLoader.js +0 -11
- package/tests/alerts/manualAlert.js +0 -77
- package/tests/alerts/sentNotifs.js +0 -16
- package/tests/alerts/strategyAlert.js +0 -94
- package/tests/analysis.js +0 -13
- package/tests/indicator.js +0 -42
- package/tests/prices.js +0 -33
- 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
|
+
};
|