@mathieuc/tradingview 3.0.1 → 3.3.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/.eslintrc.js +6 -0
- package/README.md +28 -2
- package/docs/DOCS.md +3 -0
- package/examples/AllPrivateIndicators.js +4 -4
- package/examples/BuiltInIndicator.js +1 -1
- package/examples/CustomChartType.js +1 -0
- package/examples/Errors.js +147 -153
- package/examples/FakeReplayMode.js +41 -0
- package/examples/GetDrawings.js +26 -0
- package/examples/PinePermManage.js +57 -0
- package/examples/ReplayMode.js +97 -0
- package/examples/UserLogin.js +16 -0
- package/main.js +2 -0
- package/package.json +6 -9
- package/src/FormData.js +36 -0
- package/src/chart/session.js +193 -17
- package/src/chart/study.js +68 -68
- package/src/classes/BuiltInIndicator.js +1 -0
- package/src/classes/PineIndicator.js +7 -7
- package/src/classes/PinePermManager.js +143 -0
- package/src/client.js +17 -10
- package/src/miscRequests.js +148 -103
- package/src/protocol.js +7 -1
- package/src/quote/session.js +1 -0
- package/src/request.js +37 -0
- package/src/types.js +6 -2
- package/src/utils.js +6 -0
- package/test.js +58 -147
- 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 +100 -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/.github/FUNDING.yml +0 -1
- package/examples/CurrentTimeframe.js +0 -34
- package/examples/_RenkoIndicator.js +0 -41
- 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,143 @@
|
|
|
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 */
|
|
14
|
+
class PinePermManager {
|
|
15
|
+
sessionId;
|
|
16
|
+
|
|
17
|
+
pineId;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates a PinePermManager instance
|
|
21
|
+
* @param {string} sessionId Token from sessionid cookie
|
|
22
|
+
* @param {string} pineId Indicator ID (Like: PUB;XXXXXXXXXXXXXXXXXXXXX)
|
|
23
|
+
*/
|
|
24
|
+
constructor(sessionId, pineId) {
|
|
25
|
+
if (!sessionId) throw new Error('Please provide a SessionID');
|
|
26
|
+
if (!pineId) throw new Error('Please provide a PineID');
|
|
27
|
+
this.pineId = pineId;
|
|
28
|
+
this.sessionId = sessionId;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get list of authorized users
|
|
33
|
+
* @param {number} limit Fetching limit
|
|
34
|
+
* @param {'user__username'
|
|
35
|
+
* | '-user__username'
|
|
36
|
+
* | 'created' | 'created'
|
|
37
|
+
* | 'expiration,user__username'
|
|
38
|
+
* | '-expiration,user__username'
|
|
39
|
+
* } order Fetching order
|
|
40
|
+
* @returns {AuthorizationUser[]}
|
|
41
|
+
*/
|
|
42
|
+
async getUsers(limit = 10, order = '-created') {
|
|
43
|
+
const { data } = await request({
|
|
44
|
+
method: 'POST',
|
|
45
|
+
host: 'www.tradingview.com',
|
|
46
|
+
path: `/pine_perm/list_users/?limit=${limit}&order_by=${order}`,
|
|
47
|
+
headers: {
|
|
48
|
+
origin: 'https://www.tradingview.com',
|
|
49
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
50
|
+
cookie: `sessionid=${this.sessionId}`,
|
|
51
|
+
},
|
|
52
|
+
}, false, `pine_id=${this.pineId.replace(/;/g, '%3B')}`);
|
|
53
|
+
|
|
54
|
+
if (!data.results) throw new Error('Wrong sessionId or pineId');
|
|
55
|
+
|
|
56
|
+
return data.results;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Adds an user to the authorized list
|
|
61
|
+
* @param {string} username User's username
|
|
62
|
+
* @param {Date} [expiration] Expiration date
|
|
63
|
+
* @returns {'ok' | 'exists' | null}
|
|
64
|
+
*/
|
|
65
|
+
async addUser(username, expiration = null) {
|
|
66
|
+
const formData = new FormData();
|
|
67
|
+
formData.append('pine_id', this.pineId);
|
|
68
|
+
formData.append('username_recip', username);
|
|
69
|
+
if (expiration && expiration instanceof Date) {
|
|
70
|
+
formData.append('expiration', expiration.toString());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const { data } = await request({
|
|
74
|
+
method: 'POST',
|
|
75
|
+
host: 'www.tradingview.com',
|
|
76
|
+
path: '/pine_perm/add/',
|
|
77
|
+
headers: {
|
|
78
|
+
origin: 'https://www.tradingview.com',
|
|
79
|
+
'Content-Type': `multipart/form-data; boundary=${formData.boundary}`,
|
|
80
|
+
cookie: `sessionid=${this.sessionId}`,
|
|
81
|
+
},
|
|
82
|
+
}, false, formData.toString());
|
|
83
|
+
|
|
84
|
+
if (!data.status) throw new Error('Wrong sessionId or pineId');
|
|
85
|
+
return data.status;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Modify an authorization expiration date
|
|
90
|
+
* @param {string} username User's username
|
|
91
|
+
* @param {Date} [expiration] New expiration date
|
|
92
|
+
* @returns {'ok' | null}
|
|
93
|
+
*/
|
|
94
|
+
async modifyExpiration(username, expiration = null) {
|
|
95
|
+
const formData = new FormData();
|
|
96
|
+
formData.append('pine_id', this.pineId);
|
|
97
|
+
formData.append('username_recip', username);
|
|
98
|
+
if (expiration && expiration instanceof Date) {
|
|
99
|
+
formData.append('expiration', expiration.toISOString());
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const { data } = await request({
|
|
103
|
+
method: 'POST',
|
|
104
|
+
host: 'www.tradingview.com',
|
|
105
|
+
path: '/pine_perm/modify_user_expiration/',
|
|
106
|
+
headers: {
|
|
107
|
+
origin: 'https://www.tradingview.com',
|
|
108
|
+
'Content-Type': `multipart/form-data; boundary=${formData.boundary}`,
|
|
109
|
+
cookie: `sessionid=${this.sessionId}`,
|
|
110
|
+
},
|
|
111
|
+
}, false, formData.toString());
|
|
112
|
+
|
|
113
|
+
if (!data.status) throw new Error('Wrong sessionId or pineId');
|
|
114
|
+
return data.status;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Removes an user to the authorized list
|
|
119
|
+
* @param {string} username User's username
|
|
120
|
+
* @returns {'ok' | null}
|
|
121
|
+
*/
|
|
122
|
+
async removeUser(username) {
|
|
123
|
+
const formData = new FormData();
|
|
124
|
+
formData.append('pine_id', this.pineId);
|
|
125
|
+
formData.append('username_recip', username);
|
|
126
|
+
|
|
127
|
+
const { data } = await request({
|
|
128
|
+
method: 'POST',
|
|
129
|
+
host: 'www.tradingview.com',
|
|
130
|
+
path: '/pine_perm/remove/',
|
|
131
|
+
headers: {
|
|
132
|
+
origin: 'https://www.tradingview.com',
|
|
133
|
+
'Content-Type': `multipart/form-data; boundary=${formData.boundary}`,
|
|
134
|
+
cookie: `sessionid=${this.sessionId}`,
|
|
135
|
+
},
|
|
136
|
+
}, false, formData.toString());
|
|
137
|
+
|
|
138
|
+
if (!data.status) throw new Error('Wrong sessionId or pineId');
|
|
139
|
+
return data.status;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = PinePermManager;
|
package/src/client.js
CHANGED
|
@@ -15,9 +15,10 @@ const chartSessionGenerator = require('./chart/session');
|
|
|
15
15
|
/** @typedef {Object<string, Session>} SessionList Session list */
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* @
|
|
18
|
+
* @callback SendPacket Send a custom packet
|
|
19
19
|
* @param {string} t Packet type
|
|
20
20
|
* @param {string[]} p Packet data
|
|
21
|
+
* @returns {void}
|
|
21
22
|
*/
|
|
22
23
|
|
|
23
24
|
/**
|
|
@@ -33,6 +34,7 @@ const chartSessionGenerator = require('./chart/session');
|
|
|
33
34
|
* } ClientEvent
|
|
34
35
|
*/
|
|
35
36
|
|
|
37
|
+
/** @class */
|
|
36
38
|
module.exports = class Client {
|
|
37
39
|
#ws;
|
|
38
40
|
|
|
@@ -79,7 +81,7 @@ module.exports = class Client {
|
|
|
79
81
|
/**
|
|
80
82
|
* When client is connected
|
|
81
83
|
* @param {() => void} cb Callback
|
|
82
|
-
* @event
|
|
84
|
+
* @event onConnected
|
|
83
85
|
*/
|
|
84
86
|
onConnected(cb) {
|
|
85
87
|
this.#callbacks.connected.push(cb);
|
|
@@ -88,7 +90,7 @@ module.exports = class Client {
|
|
|
88
90
|
/**
|
|
89
91
|
* When client is disconnected
|
|
90
92
|
* @param {() => void} cb Callback
|
|
91
|
-
* @event
|
|
93
|
+
* @event onDisconnected
|
|
92
94
|
*/
|
|
93
95
|
onDisconnected(cb) {
|
|
94
96
|
this.#callbacks.disconnected.push(cb);
|
|
@@ -105,10 +107,12 @@ module.exports = class Client {
|
|
|
105
107
|
* @prop {string} javastudies Javastudies
|
|
106
108
|
* @prop {number} auth_scheme_vsn Auth scheme type
|
|
107
109
|
* @prop {string} via Socket IP
|
|
110
|
+
*/
|
|
108
111
|
|
|
112
|
+
/**
|
|
109
113
|
* When client is logged
|
|
110
114
|
* @param {(SocketSession: SocketSession) => void} cb Callback
|
|
111
|
-
* @event
|
|
115
|
+
* @event onLogged
|
|
112
116
|
*/
|
|
113
117
|
onLogged(cb) {
|
|
114
118
|
this.#callbacks.logged.push(cb);
|
|
@@ -117,7 +121,7 @@ module.exports = class Client {
|
|
|
117
121
|
/**
|
|
118
122
|
* When server is pinging the client
|
|
119
123
|
* @param {(i: number) => void} cb Callback
|
|
120
|
-
* @event
|
|
124
|
+
* @event onPing
|
|
121
125
|
*/
|
|
122
126
|
onPing(cb) {
|
|
123
127
|
this.#callbacks.ping.push(cb);
|
|
@@ -126,7 +130,7 @@ module.exports = class Client {
|
|
|
126
130
|
/**
|
|
127
131
|
* When unparsed data is received
|
|
128
132
|
* @param {(...{}) => void} cb Callback
|
|
129
|
-
* @event
|
|
133
|
+
* @event onData
|
|
130
134
|
*/
|
|
131
135
|
onData(cb) {
|
|
132
136
|
this.#callbacks.data.push(cb);
|
|
@@ -135,7 +139,7 @@ module.exports = class Client {
|
|
|
135
139
|
/**
|
|
136
140
|
* When a client error happens
|
|
137
141
|
* @param {(...{}) => void} cb Callback
|
|
138
|
-
* @event
|
|
142
|
+
* @event onError
|
|
139
143
|
*/
|
|
140
144
|
onError(cb) {
|
|
141
145
|
this.#callbacks.error.push(cb);
|
|
@@ -144,7 +148,7 @@ module.exports = class Client {
|
|
|
144
148
|
/**
|
|
145
149
|
* When a client event happens
|
|
146
150
|
* @param {(...{}) => void} cb Callback
|
|
147
|
-
* @event
|
|
151
|
+
* @event onEvent
|
|
148
152
|
*/
|
|
149
153
|
onEvent(cb) {
|
|
150
154
|
this.#callbacks.event.push(cb);
|
|
@@ -203,6 +207,7 @@ module.exports = class Client {
|
|
|
203
207
|
while (this.isOpen && this.#logged && this.#sendQueue.length > 0) {
|
|
204
208
|
const packet = this.#sendQueue.shift();
|
|
205
209
|
this.#ws.send(packet);
|
|
210
|
+
if (global.TW_DEBUG) console.log('§90§30§107 > §0', packet);
|
|
206
211
|
}
|
|
207
212
|
}
|
|
208
213
|
|
|
@@ -210,6 +215,7 @@ module.exports = class Client {
|
|
|
210
215
|
* @typedef {Object} ClientOptions
|
|
211
216
|
* @prop {string} [token] User auth token (in 'sessionid' cookie)
|
|
212
217
|
* @prop {boolean} [DEBUG] Enable debug mode
|
|
218
|
+
* @prop {'data' | 'prodata' | 'widgetdata'} [server] Server type
|
|
213
219
|
*/
|
|
214
220
|
|
|
215
221
|
/** Client object
|
|
@@ -218,7 +224,8 @@ module.exports = class Client {
|
|
|
218
224
|
constructor(clientOptions = {}) {
|
|
219
225
|
if (clientOptions.DEBUG) global.TW_DEBUG = clientOptions.DEBUG;
|
|
220
226
|
|
|
221
|
-
|
|
227
|
+
const server = clientOptions.server || 'data';
|
|
228
|
+
this.#ws = new WebSocket(`wss://${server}.tradingview.com/socket.io/websocket`, {
|
|
222
229
|
origin: 'https://s.tradingview.com',
|
|
223
230
|
});
|
|
224
231
|
|
|
@@ -273,7 +280,7 @@ module.exports = class Client {
|
|
|
273
280
|
*/
|
|
274
281
|
end() {
|
|
275
282
|
return new Promise((cb) => {
|
|
276
|
-
this.#ws.close();
|
|
283
|
+
if (this.#ws.readyState) this.#ws.close();
|
|
277
284
|
cb();
|
|
278
285
|
});
|
|
279
286
|
}
|
package/src/miscRequests.js
CHANGED
|
@@ -1,45 +1,16 @@
|
|
|
1
|
+
const os = require('os');
|
|
1
2
|
const https = require('https');
|
|
3
|
+
const request = require('./request');
|
|
4
|
+
const FormData = require('./FormData');
|
|
2
5
|
|
|
6
|
+
const PinePermManager = require('./classes/PinePermManager');
|
|
3
7
|
const PineIndicator = require('./classes/PineIndicator');
|
|
4
8
|
|
|
5
9
|
const indicators = ['Recommend.Other', 'Recommend.All', 'Recommend.MA'];
|
|
6
10
|
const builtInIndicList = [];
|
|
7
11
|
|
|
8
|
-
/**
|
|
9
|
-
* @param {https.RequestOptions} options HTTPS Request options
|
|
10
|
-
* @param {boolean} [raw] Get raw or JSON data
|
|
11
|
-
* @param {string} [content] Request body content
|
|
12
|
-
* @returns {Promise<string | object | array>} Result
|
|
13
|
-
*/
|
|
14
|
-
function request(options = {}, raw = false, content = '') {
|
|
15
|
-
return new Promise((cb, err) => {
|
|
16
|
-
const req = https.request(options, (res) => {
|
|
17
|
-
let data = '';
|
|
18
|
-
res.on('data', (c) => { data += c; });
|
|
19
|
-
res.on('end', () => {
|
|
20
|
-
if (raw) {
|
|
21
|
-
cb(data);
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
data = JSON.parse(data);
|
|
27
|
-
} catch (error) {
|
|
28
|
-
err(new Error('Can\'t parse server response'));
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
cb(data);
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
req.on('error', err);
|
|
37
|
-
req.end(content);
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
12
|
async function fetchScanData(tickers = [], type = '', columns = []) {
|
|
42
|
-
let data = await request({
|
|
13
|
+
let { data } = await request({
|
|
43
14
|
method: 'POST',
|
|
44
15
|
hostname: 'scanner.tradingview.com',
|
|
45
16
|
path: `/${type}/scan`,
|
|
@@ -59,11 +30,17 @@ async function fetchScanData(tickers = [], type = '', columns = []) {
|
|
|
59
30
|
return data;
|
|
60
31
|
}
|
|
61
32
|
|
|
33
|
+
/** @typedef {number} advice */
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {{
|
|
37
|
+
* Other: advice,
|
|
38
|
+
* All: advice,
|
|
39
|
+
* MA: advice
|
|
40
|
+
* }} Period
|
|
41
|
+
*/
|
|
42
|
+
|
|
62
43
|
/**
|
|
63
|
-
* @typedef {number} advice
|
|
64
|
-
*
|
|
65
|
-
* @typedef {{ Other: advice, All: advice, MA: advice }} Period
|
|
66
|
-
*
|
|
67
44
|
* @typedef {{
|
|
68
45
|
* '1': Period,
|
|
69
46
|
* '5': Period,
|
|
@@ -72,21 +49,25 @@ async function fetchScanData(tickers = [], type = '', columns = []) {
|
|
|
72
49
|
* '240': Period,
|
|
73
50
|
* '1D': Period,
|
|
74
51
|
* '1W': Period,
|
|
75
|
-
* '1M': Period
|
|
52
|
+
* '1M': Period
|
|
76
53
|
* }} Periods
|
|
77
|
-
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
78
57
|
* @typedef {string | 'forex' | 'crypto'
|
|
79
58
|
* | 'america' | 'australia' | 'canada' | 'egypt'
|
|
80
59
|
* | 'germany' | 'india' | 'israel' | 'italy'
|
|
81
60
|
* | 'luxembourg' | 'poland' | 'sweden' | 'turkey'
|
|
82
|
-
* | 'uk' | 'vietnam'}
|
|
61
|
+
* | 'uk' | 'vietnam'} Screener
|
|
83
62
|
* You can use `getScreener(exchange)` function for non-forex and non-crypto markets.
|
|
84
63
|
*/
|
|
85
64
|
|
|
86
65
|
module.exports = {
|
|
87
66
|
/**
|
|
67
|
+
* Get a screener from an exchange
|
|
68
|
+
* @function getScreener
|
|
88
69
|
* @param {string} exchange Example: BINANCE, EURONEXT, NASDAQ
|
|
89
|
-
* @returns {
|
|
70
|
+
* @returns {Screener}
|
|
90
71
|
*/
|
|
91
72
|
getScreener(exchange) {
|
|
92
73
|
const e = exchange.toUpperCase();
|
|
@@ -109,7 +90,8 @@ module.exports = {
|
|
|
109
90
|
|
|
110
91
|
/**
|
|
111
92
|
* Get technical analysis
|
|
112
|
-
* @
|
|
93
|
+
* @function getTA
|
|
94
|
+
* @param {Screener} screener Market screener
|
|
113
95
|
* @param {string} id Full market id (Example: COINBASE:BTCEUR)
|
|
114
96
|
* @returns {Promise<Periods>} results
|
|
115
97
|
*/
|
|
@@ -135,24 +117,28 @@ module.exports = {
|
|
|
135
117
|
|
|
136
118
|
/**
|
|
137
119
|
* @typedef {Object} SearchMarketResult
|
|
138
|
-
* @
|
|
139
|
-
* @
|
|
140
|
-
* @
|
|
141
|
-
* @
|
|
142
|
-
* @
|
|
143
|
-
* @
|
|
144
|
-
* @
|
|
145
|
-
* @
|
|
120
|
+
* @prop {string} id Market full symbol
|
|
121
|
+
* @prop {string} exchange Market exchange name
|
|
122
|
+
* @prop {string} fullExchange Market exchange full name
|
|
123
|
+
* @prop {Screener | 'forex' | 'crypto'} screener Market screener
|
|
124
|
+
* @prop {string} symbol Market symbol
|
|
125
|
+
* @prop {string} description Market name
|
|
126
|
+
* @prop {string} type Market type
|
|
127
|
+
* @prop {() => Promise<Periods>} getTA Get market technical analysis
|
|
146
128
|
*/
|
|
147
129
|
|
|
148
130
|
/**
|
|
149
131
|
* Find a symbol
|
|
132
|
+
* @function searchMarket
|
|
150
133
|
* @param {string} search Keywords
|
|
151
|
-
* @param {'stock'
|
|
134
|
+
* @param {'stock'
|
|
135
|
+
* | 'futures' | 'forex' | 'cfd'
|
|
136
|
+
* | 'crypto' | 'index' | 'economic'
|
|
137
|
+
* } [filter] Caterogy filter
|
|
152
138
|
* @returns {Promise<SearchMarketResult[]>} Search results
|
|
153
139
|
*/
|
|
154
140
|
async searchMarket(search, filter = '') {
|
|
155
|
-
const data = await request({
|
|
141
|
+
const { data } = await request({
|
|
156
142
|
host: 'symbol-search.tradingview.com',
|
|
157
143
|
path: `/symbol_search/?text=${search.replace(/ /g, '%20')}&type=${filter}`,
|
|
158
144
|
origin: 'https://www.tradingview.com',
|
|
@@ -182,34 +168,35 @@ module.exports = {
|
|
|
182
168
|
|
|
183
169
|
/**
|
|
184
170
|
* @typedef {Object} SearchIndicatorResult
|
|
185
|
-
* @
|
|
186
|
-
* @
|
|
187
|
-
* @
|
|
188
|
-
* @
|
|
189
|
-
* @
|
|
190
|
-
* @
|
|
191
|
-
* @
|
|
192
|
-
* @
|
|
171
|
+
* @prop {string} id Script ID
|
|
172
|
+
* @prop {string} version Script version
|
|
173
|
+
* @prop {string} name Script complete name
|
|
174
|
+
* @prop {{ id: number, username: string }} author Author user ID
|
|
175
|
+
* @prop {string} image Image ID https://tradingview.com/i/${image}
|
|
176
|
+
* @prop {string | ''} source Script source (if available)
|
|
177
|
+
* @prop {'study' | 'strategy'} type Script type (study / strategy)
|
|
178
|
+
* @prop {'open_source' | 'closed_source' | 'invite_only'
|
|
193
179
|
* | 'private' | 'other'} access Script access type
|
|
194
|
-
* @
|
|
180
|
+
* @prop {() => Promise<PineIndicator>} get Get the full indicator informations
|
|
195
181
|
*/
|
|
196
182
|
|
|
197
183
|
/**
|
|
198
184
|
* Find an indicator
|
|
185
|
+
* @function searchIndicator
|
|
199
186
|
* @param {string} search Keywords
|
|
200
187
|
* @returns {Promise<SearchIndicatorResult[]>} Search results
|
|
201
188
|
*/
|
|
202
189
|
async searchIndicator(search = '') {
|
|
203
190
|
if (!builtInIndicList.length) {
|
|
204
191
|
await Promise.all(['standard', 'candlestick', 'fundamental'].map(async (type) => {
|
|
205
|
-
builtInIndicList.push(...await request({
|
|
192
|
+
builtInIndicList.push(...(await request({
|
|
206
193
|
host: 'pine-facade.tradingview.com',
|
|
207
194
|
path: `/pine-facade/list/?filter=${type}`,
|
|
208
|
-
}));
|
|
195
|
+
})).data);
|
|
209
196
|
}));
|
|
210
197
|
}
|
|
211
198
|
|
|
212
|
-
const data = await request({
|
|
199
|
+
const { data } = await request({
|
|
213
200
|
host: 'www.tradingview.com',
|
|
214
201
|
path: `/pubscripts-suggest-json/?search=${search.replace(/ /g, '%20')}`,
|
|
215
202
|
});
|
|
@@ -260,6 +247,7 @@ module.exports = {
|
|
|
260
247
|
|
|
261
248
|
/**
|
|
262
249
|
* Get an indicator
|
|
250
|
+
* @function getIndicator
|
|
263
251
|
* @param {string} id Indicator ID (Like: PUB;XXXXXXXXXXXXXXXXXXXXX)
|
|
264
252
|
* @param {'last' | string} [version] Wanted version of the indicator
|
|
265
253
|
* @returns {Promise<PineIndicator>} Indicator
|
|
@@ -267,7 +255,7 @@ module.exports = {
|
|
|
267
255
|
async getIndicator(id, version = 'last') {
|
|
268
256
|
const indicID = id.replace(/ |%/g, '%25');
|
|
269
257
|
|
|
270
|
-
let data = await request({
|
|
258
|
+
let { data } = await request({
|
|
271
259
|
host: 'pine-facade.tradingview.com',
|
|
272
260
|
path: `/pine-facade/translate/${indicID}/${version}`,
|
|
273
261
|
}, true);
|
|
@@ -287,10 +275,12 @@ module.exports = {
|
|
|
287
275
|
data.result.metaInfo.inputs.forEach((input) => {
|
|
288
276
|
if (['text', 'pineId', 'pineVersion'].includes(input.id)) return;
|
|
289
277
|
|
|
278
|
+
const inlineName = input.name.replace(/ /g, '_').replace(/[^a-zA-Z0-9_]/g, '');
|
|
279
|
+
|
|
290
280
|
inputs[input.id] = {
|
|
291
281
|
name: input.name,
|
|
292
|
-
inline: input.inline ||
|
|
293
|
-
internalID: input.internalID,
|
|
282
|
+
inline: input.inline || inlineName,
|
|
283
|
+
internalID: input.internalID || inlineName,
|
|
294
284
|
tooltip: input.tooltip,
|
|
295
285
|
|
|
296
286
|
type: input.type,
|
|
@@ -340,25 +330,74 @@ module.exports = {
|
|
|
340
330
|
|
|
341
331
|
/**
|
|
342
332
|
* @typedef {Object} User Instance of User
|
|
343
|
-
* @
|
|
344
|
-
* @
|
|
345
|
-
* @
|
|
346
|
-
* @
|
|
347
|
-
* @
|
|
348
|
-
* @
|
|
349
|
-
* @
|
|
350
|
-
* @
|
|
351
|
-
* @
|
|
352
|
-
* @
|
|
353
|
-
* @
|
|
354
|
-
* @
|
|
355
|
-
* @
|
|
356
|
-
* @
|
|
357
|
-
* @
|
|
333
|
+
* @prop {number} id User ID
|
|
334
|
+
* @prop {string} username User username
|
|
335
|
+
* @prop {string} firstName User first name
|
|
336
|
+
* @prop {string} lastName User last name
|
|
337
|
+
* @prop {number} reputation User reputation
|
|
338
|
+
* @prop {number} following Number of following accounts
|
|
339
|
+
* @prop {number} followers Number of followers
|
|
340
|
+
* @prop {Object} notifications User's notifications
|
|
341
|
+
* @prop {number} notifications.user User notifications
|
|
342
|
+
* @prop {number} notifications.following Notification from following accounts
|
|
343
|
+
* @prop {string} session User session
|
|
344
|
+
* @prop {string} sessionHash User session hash
|
|
345
|
+
* @prop {string} privateChannel User private channel
|
|
346
|
+
* @prop {string} authToken User auth token
|
|
347
|
+
* @prop {Date} joinDate Account creation date
|
|
358
348
|
*/
|
|
359
349
|
|
|
360
350
|
/**
|
|
361
|
-
* Get
|
|
351
|
+
* Get user and sessionid from username/email and password
|
|
352
|
+
* @function loginUser
|
|
353
|
+
* @param {string} username User username/email
|
|
354
|
+
* @param {string} password User password
|
|
355
|
+
* @param {boolean} [remember] Remember the session (default: false)
|
|
356
|
+
* @param {string} [UA] Custom UserAgent
|
|
357
|
+
* @returns {Promise<User>} Token
|
|
358
|
+
*/
|
|
359
|
+
async loginUser(username, password, remember = true, UA = 'TWAPI/3.0') {
|
|
360
|
+
const formData = new FormData();
|
|
361
|
+
formData.append('username', username);
|
|
362
|
+
formData.append('password', password);
|
|
363
|
+
if (remember) formData.append('remember', 'on');
|
|
364
|
+
|
|
365
|
+
const { data, cookies } = await request({
|
|
366
|
+
method: 'POST',
|
|
367
|
+
host: 'www.tradingview.com',
|
|
368
|
+
path: '/accounts/signin/',
|
|
369
|
+
headers: {
|
|
370
|
+
referer: 'https://www.tradingview.com',
|
|
371
|
+
'Content-Type': `multipart/form-data; boundary=${formData.boundary}`,
|
|
372
|
+
'User-agent': `${UA} (${os.version()}; ${os.platform()}; ${os.arch()})`,
|
|
373
|
+
},
|
|
374
|
+
}, false, formData.toString());
|
|
375
|
+
|
|
376
|
+
if (data.error) throw new Error(data.error);
|
|
377
|
+
|
|
378
|
+
const cookie = cookies.find((c) => c.includes('sessionid='));
|
|
379
|
+
const session = (cookie.match(/sessionid=(.*?);/) ?? [])[1];
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
id: data.user.id,
|
|
383
|
+
username: data.user.username,
|
|
384
|
+
firstName: data.user.first_name,
|
|
385
|
+
lastName: data.user.last_name,
|
|
386
|
+
reputation: data.user.reputation,
|
|
387
|
+
following: data.user.following,
|
|
388
|
+
followers: data.user.followers,
|
|
389
|
+
notifications: data.user.notification_count,
|
|
390
|
+
session,
|
|
391
|
+
sessionHash: data.user.session_hash,
|
|
392
|
+
privateChannel: data.user.private_channel,
|
|
393
|
+
authToken: data.user.auth_token,
|
|
394
|
+
joinDate: new Date(data.user.date_joined),
|
|
395
|
+
};
|
|
396
|
+
},
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Get user from 'sessionid' cookie
|
|
400
|
+
* @function getUser
|
|
362
401
|
* @param {string} session User 'sessionid' cookie
|
|
363
402
|
* @param {string} [location] Auth page location (For france: https://fr.tradingview.com/)
|
|
364
403
|
* @returns {Promise<User>} Token
|
|
@@ -404,6 +443,7 @@ module.exports = {
|
|
|
404
443
|
|
|
405
444
|
/**
|
|
406
445
|
* Get user's private indicators from a 'sessionid' cookie
|
|
446
|
+
* @function getPrivateIndicators
|
|
407
447
|
* @param {string} session User 'sessionid' cookie
|
|
408
448
|
* @returns {Promise<SearchIndicatorResult[]>} Search results
|
|
409
449
|
*/
|
|
@@ -437,6 +477,9 @@ module.exports = {
|
|
|
437
477
|
get() {
|
|
438
478
|
return module.exports.getIndicator(ind.scriptIdPart, ind.version);
|
|
439
479
|
},
|
|
480
|
+
getManager() {
|
|
481
|
+
return new PinePermManager(ind.scriptIdPart);
|
|
482
|
+
},
|
|
440
483
|
})));
|
|
441
484
|
});
|
|
442
485
|
|
|
@@ -448,12 +491,13 @@ module.exports = {
|
|
|
448
491
|
/**
|
|
449
492
|
* User credentials
|
|
450
493
|
* @typedef {Object} UserCredentials
|
|
451
|
-
* @
|
|
452
|
-
* @
|
|
494
|
+
* @prop {number} id User ID
|
|
495
|
+
* @prop {string} session User session ('sessionid' cookie)
|
|
453
496
|
*/
|
|
454
497
|
|
|
455
498
|
/**
|
|
456
499
|
* Get a chart token from a layout ID and the user credentials if the layout is not public
|
|
500
|
+
* @function getChartToken
|
|
457
501
|
* @param {string} layout The layout ID found in the layout URL (Like: 'XXXXXXXX')
|
|
458
502
|
* @param {UserCredentials} [credentials] User credentials (id + session)
|
|
459
503
|
* @returns {Promise<string>} Token
|
|
@@ -463,7 +507,7 @@ module.exports = {
|
|
|
463
507
|
const userID = creds ? credentials.id : -1;
|
|
464
508
|
const session = creds ? credentials.session : null;
|
|
465
509
|
|
|
466
|
-
const data = await request({
|
|
510
|
+
const { data } = await request({
|
|
467
511
|
host: 'www.tradingview.com',
|
|
468
512
|
path: `/chart-token/?image_url=${layout}&user_id=${userID}`,
|
|
469
513
|
headers: { cookie: session ? `sessionid=${session}` : '' },
|
|
@@ -476,32 +520,33 @@ module.exports = {
|
|
|
476
520
|
|
|
477
521
|
/**
|
|
478
522
|
* @typedef {Object} DrawingPoint Drawing poitn
|
|
479
|
-
* @
|
|
480
|
-
* @
|
|
481
|
-
* @
|
|
523
|
+
* @prop {number} time_t Point X time position
|
|
524
|
+
* @prop {number} price Point Y price position
|
|
525
|
+
* @prop {number} offset Point offset
|
|
482
526
|
*/
|
|
483
527
|
|
|
484
528
|
/**
|
|
485
529
|
* @typedef {Object} Drawing
|
|
486
|
-
* @
|
|
487
|
-
* @
|
|
488
|
-
* @
|
|
489
|
-
* @
|
|
490
|
-
* @
|
|
491
|
-
* @
|
|
492
|
-
* @
|
|
493
|
-
* @
|
|
494
|
-
* @
|
|
495
|
-
* @
|
|
496
|
-
* @
|
|
530
|
+
* @prop {string} id Drawing ID (Like: 'XXXXXX')
|
|
531
|
+
* @prop {string} symbol Layout market symbol (Like: 'BINANCE:BUCEUR')
|
|
532
|
+
* @prop {string} ownerSource Owner user ID (Like: 'XXXXXX')
|
|
533
|
+
* @prop {string} serverUpdateTime Drawing last update timestamp
|
|
534
|
+
* @prop {string} currencyId Currency ID (Like: 'EUR')
|
|
535
|
+
* @prop {any} unitId Unit ID
|
|
536
|
+
* @prop {string} type Drawing type
|
|
537
|
+
* @prop {DrawingPoint[]} points List of drawing points
|
|
538
|
+
* @prop {number} zorder Drawing Z order
|
|
539
|
+
* @prop {string} linkKey Drawing link key
|
|
540
|
+
* @prop {Object} state Drawing state
|
|
497
541
|
*/
|
|
498
542
|
|
|
499
543
|
/**
|
|
500
544
|
* Get a chart token from a layout ID and the user credentials if the layout is not public
|
|
545
|
+
* @function getDrawings
|
|
501
546
|
* @param {string} layout The layout ID found in the layout URL (Like: 'XXXXXXXX')
|
|
502
547
|
* @param {string | ''} [symbol] Market filter (Like: 'BINANCE:BTCEUR')
|
|
503
548
|
* @param {UserCredentials} [credentials] User credentials (id + session)
|
|
504
|
-
* @param {number} [chartID
|
|
549
|
+
* @param {number} [chartID] Chart ID
|
|
505
550
|
* @returns {Promise<Drawing[]>} Drawings
|
|
506
551
|
*/
|
|
507
552
|
async getDrawings(layout, symbol = '', credentials = {}, chartID = 1) {
|
|
@@ -509,7 +554,7 @@ module.exports = {
|
|
|
509
554
|
const creds = credentials.id && credentials.session;
|
|
510
555
|
const session = creds ? credentials.session : null;
|
|
511
556
|
|
|
512
|
-
const data = await request({
|
|
557
|
+
const { data } = await request({
|
|
513
558
|
host: 'charts-storage.tradingview.com',
|
|
514
559
|
path: `/charts-storage/layout/${layout}/sources?chart_id=${chartID
|
|
515
560
|
}&jwt=${chartToken}${symbol ? `&symbol=${symbol}` : ''}`,
|