@mathieuc/tradingview 3.5.2 → 4.0.0-beta.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/README.md +112 -51
- package/dist/chart/chart-session.d.ts +114 -0
- package/dist/chart/chart-session.js +339 -0
- package/dist/chart/graphics.d.ts +129 -0
- package/dist/chart/graphics.js +153 -0
- package/dist/chart/index.d.ts +11 -0
- package/dist/chart/index.js +6 -0
- package/dist/chart/strategy.d.ts +113 -0
- package/dist/chart/strategy.js +78 -0
- package/dist/chart/study.d.ts +60 -0
- package/dist/chart/study.js +179 -0
- package/dist/chart/timeframes.d.ts +7 -0
- package/dist/chart/timeframes.js +22 -0
- package/dist/chart/types.d.ts +105 -0
- package/dist/chart/types.js +9 -0
- package/dist/client/client.d.ts +89 -0
- package/dist/client/client.js +254 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.js +2 -0
- package/dist/client/transport.d.ts +27 -0
- package/dist/client/transport.js +38 -0
- package/dist/data/candles.d.ts +40 -0
- package/dist/data/candles.js +101 -0
- package/dist/data/history.d.ts +61 -0
- package/dist/data/history.js +101 -0
- package/dist/data/index.d.ts +30 -0
- package/dist/data/index.js +16 -0
- package/dist/data/indicators.d.ts +54 -0
- package/dist/data/indicators.js +139 -0
- package/dist/data/operation.d.ts +71 -0
- package/dist/data/operation.js +205 -0
- package/dist/data/provider.d.ts +23 -0
- package/dist/data/provider.js +11 -0
- package/dist/data/quotes.d.ts +33 -0
- package/dist/data/quotes.js +117 -0
- package/dist/data/symbols.d.ts +9 -0
- package/dist/data/symbols.js +19 -0
- package/dist/errors.d.ts +53 -0
- package/dist/errors.js +24 -0
- package/dist/events.d.ts +36 -0
- package/dist/events.js +70 -0
- package/dist/http/account.d.ts +47 -0
- package/dist/http/account.js +127 -0
- package/dist/http/index.d.ts +11 -0
- package/dist/http/index.js +5 -0
- package/dist/http/indicators.d.ts +37 -0
- package/dist/http/indicators.js +144 -0
- package/dist/http/layouts.d.ts +36 -0
- package/dist/http/layouts.js +24 -0
- package/dist/http/market.d.ts +45 -0
- package/dist/http/market.js +59 -0
- package/dist/http/pine-permissions.d.ts +29 -0
- package/dist/http/pine-permissions.js +56 -0
- package/dist/http/request.d.ts +44 -0
- package/dist/http/request.js +83 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +19 -0
- package/dist/indicators/builtin-indicator.d.ts +17 -0
- package/dist/indicators/builtin-indicator.js +75 -0
- package/dist/indicators/index.d.ts +8 -0
- package/dist/indicators/index.js +2 -0
- package/dist/indicators/pine-indicator.d.ts +63 -0
- package/dist/indicators/pine-indicator.js +101 -0
- package/dist/protocol/compression.d.ts +12 -0
- package/dist/protocol/compression.js +83 -0
- package/dist/protocol/framing.d.ts +59 -0
- package/dist/protocol/framing.js +68 -0
- package/dist/protocol/ids.d.ts +2 -0
- package/dist/protocol/ids.js +10 -0
- package/dist/protocol/index.d.ts +4 -0
- package/dist/protocol/index.js +3 -0
- package/dist/quote/fields.d.ts +35 -0
- package/dist/quote/fields.js +21 -0
- package/dist/quote/index.d.ts +4 -0
- package/dist/quote/index.js +2 -0
- package/dist/quote/quote-session.d.ts +73 -0
- package/dist/quote/quote-session.js +205 -0
- package/docs/README.es.md +58 -0
- package/docs/README.fr.md +58 -0
- package/docs/README.pt.md +58 -0
- package/docs/data-api.md +285 -0
- package/docs/low-level-api.md +213 -0
- package/docs/migration-v4.md +128 -0
- package/docs/release-4.0.0-beta.1.md +39 -0
- package/docs/v4-coverage.md +213 -0
- package/package.json +53 -20
- package/.env.sample +0 -2
- package/.eslintrc.js +0 -28
- package/.gitattributes +0 -2
- package/docs/DOCS.md +0 -3
- package/examples/AllPrivateIndicators.js +0 -44
- package/examples/BuiltInIndicator.js +0 -51
- package/examples/CustomChartType.js +0 -123
- package/examples/CustomTimeframe.js +0 -31
- package/examples/Errors.js +0 -155
- package/examples/FakeReplayMode.js +0 -40
- package/examples/FromToData.js +0 -35
- package/examples/GetDrawings.js +0 -27
- package/examples/GraphicIndicator.js +0 -42
- package/examples/MultipleSyncFetch.js +0 -44
- package/examples/PinePermManage.js +0 -67
- package/examples/ReplayMode.js +0 -103
- package/examples/Search.js +0 -14
- package/examples/SimpleChart.js +0 -63
- package/examples/UserLogin.js +0 -16
- package/main.js +0 -11
- package/src/chart/graphicParser.js +0 -308
- package/src/chart/session.js +0 -553
- package/src/chart/study.js +0 -435
- package/src/classes/BuiltInIndicator.js +0 -137
- package/src/classes/PineIndicator.js +0 -132
- package/src/classes/PinePermManager.js +0 -159
- package/src/client.js +0 -298
- package/src/miscRequests.js +0 -609
- package/src/protocol.js +0 -60
- package/src/quote/market.js +0 -132
- package/src/quote/session.js +0 -125
- package/src/types.js +0 -36
- package/src/utils.js +0 -20
- package/vite.config.js +0 -7
package/src/miscRequests.js
DELETED
|
@@ -1,609 +0,0 @@
|
|
|
1
|
-
const os = require('os');
|
|
2
|
-
const axios = require('axios');
|
|
3
|
-
|
|
4
|
-
const PineIndicator = require('./classes/PineIndicator');
|
|
5
|
-
const { genAuthCookies } = require('./utils');
|
|
6
|
-
|
|
7
|
-
const validateStatus = (status) => status < 500;
|
|
8
|
-
|
|
9
|
-
const indicators = ['Recommend.Other', 'Recommend.All', 'Recommend.MA'];
|
|
10
|
-
const builtInIndicList = [];
|
|
11
|
-
|
|
12
|
-
async function fetchScanData(tickers = [], columns = []) {
|
|
13
|
-
const { data } = await axios.post(
|
|
14
|
-
'https://scanner.tradingview.com/global/scan',
|
|
15
|
-
{
|
|
16
|
-
symbols: { tickers },
|
|
17
|
-
columns,
|
|
18
|
-
},
|
|
19
|
-
{ validateStatus },
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
return data;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** @typedef {number} advice */
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* @typedef {{
|
|
29
|
-
* Other: advice,
|
|
30
|
-
* All: advice,
|
|
31
|
-
* MA: advice
|
|
32
|
-
* }} Period
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* @typedef {{
|
|
37
|
-
* '1': Period,
|
|
38
|
-
* '5': Period,
|
|
39
|
-
* '15': Period,
|
|
40
|
-
* '60': Period,
|
|
41
|
-
* '240': Period,
|
|
42
|
-
* '1D': Period,
|
|
43
|
-
* '1W': Period,
|
|
44
|
-
* '1M': Period
|
|
45
|
-
* }} Periods
|
|
46
|
-
*/
|
|
47
|
-
|
|
48
|
-
module.exports = {
|
|
49
|
-
/**
|
|
50
|
-
* Get technical analysis
|
|
51
|
-
* @function getTA
|
|
52
|
-
* @param {string} id Full market id (Example: COINBASE:BTCEUR)
|
|
53
|
-
* @returns {Promise<Periods>} results
|
|
54
|
-
*/
|
|
55
|
-
async getTA(id) {
|
|
56
|
-
const advice = {};
|
|
57
|
-
|
|
58
|
-
const cols = ['1', '5', '15', '60', '240', '1D', '1W', '1M']
|
|
59
|
-
.map((t) => indicators.map((i) => (t !== '1D' ? `${i}|${t}` : i)))
|
|
60
|
-
.flat();
|
|
61
|
-
|
|
62
|
-
const rs = await fetchScanData([id], cols);
|
|
63
|
-
if (!rs.data || !rs.data[0]) return false;
|
|
64
|
-
|
|
65
|
-
rs.data[0].d.forEach((val, i) => {
|
|
66
|
-
const [name, period] = cols[i].split('|');
|
|
67
|
-
const pName = period || '1D';
|
|
68
|
-
if (!advice[pName]) advice[pName] = {};
|
|
69
|
-
advice[pName][name.split('.').pop()] = Math.round(val * 1000) / 500;
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
return advice;
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* @typedef {Object} SearchMarketResult
|
|
77
|
-
* @prop {string} id Market full symbol
|
|
78
|
-
* @prop {string} exchange Market exchange name
|
|
79
|
-
* @prop {string} fullExchange Market exchange full name
|
|
80
|
-
* @prop {string} symbol Market symbol
|
|
81
|
-
* @prop {string} description Market name
|
|
82
|
-
* @prop {string} type Market type
|
|
83
|
-
* @prop {() => Promise<Periods>} getTA Get market technical analysis
|
|
84
|
-
*/
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Find a symbol (deprecated)
|
|
88
|
-
* @function searchMarket
|
|
89
|
-
* @param {string} search Keywords
|
|
90
|
-
* @param {'stock'
|
|
91
|
-
* | 'futures' | 'forex' | 'cfd'
|
|
92
|
-
* | 'crypto' | 'index' | 'economic'
|
|
93
|
-
* } [filter] Caterogy filter
|
|
94
|
-
* @returns {Promise<SearchMarketResult[]>} Search results
|
|
95
|
-
* @deprecated Use searchMarketV3 instead
|
|
96
|
-
*/
|
|
97
|
-
async searchMarket(search, filter = '') {
|
|
98
|
-
const { data } = await axios.get(
|
|
99
|
-
'https://symbol-search.tradingview.com/symbol_search',
|
|
100
|
-
{
|
|
101
|
-
params: {
|
|
102
|
-
text: search.replace(/ /g, '%20'),
|
|
103
|
-
type: filter,
|
|
104
|
-
},
|
|
105
|
-
headers: {
|
|
106
|
-
origin: 'https://www.tradingview.com',
|
|
107
|
-
},
|
|
108
|
-
validateStatus,
|
|
109
|
-
},
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
return data.map((s) => {
|
|
113
|
-
const exchange = s.exchange.split(' ')[0];
|
|
114
|
-
const id = `${exchange}:${s.symbol}`;
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
id,
|
|
118
|
-
exchange,
|
|
119
|
-
fullExchange: s.exchange,
|
|
120
|
-
symbol: s.symbol,
|
|
121
|
-
description: s.description,
|
|
122
|
-
type: s.type,
|
|
123
|
-
getTA: () => this.getTA(id),
|
|
124
|
-
};
|
|
125
|
-
});
|
|
126
|
-
},
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Find a symbol
|
|
130
|
-
* @function searchMarketV3
|
|
131
|
-
* @param {string} search Keywords
|
|
132
|
-
* @param {'stock'
|
|
133
|
-
* | 'futures' | 'forex' | 'cfd'
|
|
134
|
-
* | 'crypto' | 'index' | 'economic'
|
|
135
|
-
* } [filter] Caterogy filter
|
|
136
|
-
* @param {number} offset Pagination offset
|
|
137
|
-
* @returns {Promise<SearchMarketResult[]>} Search results
|
|
138
|
-
*/
|
|
139
|
-
async searchMarketV3(search, filter = '', offset = 0) {
|
|
140
|
-
const splittedSearch = search.toUpperCase().replace(/ /g, '+').split(':');
|
|
141
|
-
|
|
142
|
-
const request = await axios.get(
|
|
143
|
-
'https://symbol-search.tradingview.com/symbol_search/v3',
|
|
144
|
-
{
|
|
145
|
-
params: {
|
|
146
|
-
exchange: (splittedSearch.length === 2
|
|
147
|
-
? splittedSearch[0]
|
|
148
|
-
: undefined
|
|
149
|
-
),
|
|
150
|
-
text: splittedSearch.pop(),
|
|
151
|
-
search_type: filter,
|
|
152
|
-
start: offset,
|
|
153
|
-
},
|
|
154
|
-
headers: {
|
|
155
|
-
origin: 'https://www.tradingview.com',
|
|
156
|
-
},
|
|
157
|
-
validateStatus,
|
|
158
|
-
},
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
const { data } = request;
|
|
162
|
-
|
|
163
|
-
return data.symbols.map((s) => {
|
|
164
|
-
const exchange = s.exchange.split(' ')[0];
|
|
165
|
-
const id = s.prefix ? `${s.prefix}:${s.symbol}` : `${exchange.toUpperCase()}:${s.symbol}`;
|
|
166
|
-
|
|
167
|
-
return {
|
|
168
|
-
id,
|
|
169
|
-
exchange,
|
|
170
|
-
fullExchange: s.exchange,
|
|
171
|
-
symbol: s.symbol,
|
|
172
|
-
description: s.description,
|
|
173
|
-
type: s.type,
|
|
174
|
-
getTA: () => this.getTA(id),
|
|
175
|
-
};
|
|
176
|
-
});
|
|
177
|
-
},
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* @typedef {Object} SearchIndicatorResult
|
|
181
|
-
* @prop {string} id Script ID
|
|
182
|
-
* @prop {string} version Script version
|
|
183
|
-
* @prop {string} name Script complete name
|
|
184
|
-
* @prop {{ id: number, username: string }} author Author user ID
|
|
185
|
-
* @prop {string} image Image ID https://tradingview.com/i/${image}
|
|
186
|
-
* @prop {string | ''} source Script source (if available)
|
|
187
|
-
* @prop {'study' | 'strategy'} type Script type (study / strategy)
|
|
188
|
-
* @prop {'open_source' | 'closed_source' | 'invite_only'
|
|
189
|
-
* | 'private' | 'other'} access Script access type
|
|
190
|
-
* @prop {() => Promise<PineIndicator>} get Get the full indicator informations
|
|
191
|
-
*/
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Find an indicator
|
|
195
|
-
* @function searchIndicator
|
|
196
|
-
* @param {string} search Keywords
|
|
197
|
-
* @returns {Promise<SearchIndicatorResult[]>} Search results
|
|
198
|
-
*/
|
|
199
|
-
async searchIndicator(search = '') {
|
|
200
|
-
if (!builtInIndicList.length) {
|
|
201
|
-
await Promise.all(['standard', 'candlestick', 'fundamental'].map(async (type) => {
|
|
202
|
-
const { data } = await axios.get(
|
|
203
|
-
'https://pine-facade.tradingview.com/pine-facade/list',
|
|
204
|
-
{
|
|
205
|
-
params: {
|
|
206
|
-
filter: type,
|
|
207
|
-
},
|
|
208
|
-
validateStatus,
|
|
209
|
-
},
|
|
210
|
-
);
|
|
211
|
-
builtInIndicList.push(...data);
|
|
212
|
-
}));
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const { data } = await axios.get(
|
|
216
|
-
'https://www.tradingview.com/pubscripts-suggest-json',
|
|
217
|
-
{
|
|
218
|
-
params: {
|
|
219
|
-
search: search.replace(/ /g, '%20'),
|
|
220
|
-
},
|
|
221
|
-
validateStatus,
|
|
222
|
-
},
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
function norm(str = '') {
|
|
226
|
-
return str.toUpperCase().replace(/[^A-Z]/g, '');
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return [
|
|
230
|
-
...builtInIndicList.filter((i) => (
|
|
231
|
-
norm(i.scriptName).includes(norm(search))
|
|
232
|
-
|| norm(i.extra.shortDescription).includes(norm(search))
|
|
233
|
-
)).map((ind) => ({
|
|
234
|
-
id: ind.scriptIdPart,
|
|
235
|
-
version: ind.version,
|
|
236
|
-
name: ind.scriptName,
|
|
237
|
-
author: {
|
|
238
|
-
id: ind.userId,
|
|
239
|
-
username: '@TRADINGVIEW@',
|
|
240
|
-
},
|
|
241
|
-
image: '',
|
|
242
|
-
access: 'closed_source',
|
|
243
|
-
source: '',
|
|
244
|
-
type: (ind.extra && ind.extra.kind) ? ind.extra.kind : 'study',
|
|
245
|
-
get() {
|
|
246
|
-
return module.exports.getIndicator(ind.scriptIdPart, ind.version);
|
|
247
|
-
},
|
|
248
|
-
})),
|
|
249
|
-
|
|
250
|
-
...data.results.map((ind) => ({
|
|
251
|
-
id: ind.scriptIdPart,
|
|
252
|
-
version: ind.version,
|
|
253
|
-
name: ind.scriptName,
|
|
254
|
-
author: {
|
|
255
|
-
id: ind.author.id,
|
|
256
|
-
username: ind.author.username,
|
|
257
|
-
},
|
|
258
|
-
image: ind.imageUrl,
|
|
259
|
-
access: ['open_source', 'closed_source', 'invite_only'][ind.access - 1] || 'other',
|
|
260
|
-
source: ind.scriptSource,
|
|
261
|
-
type: (ind.extra && ind.extra.kind) ? ind.extra.kind : 'study',
|
|
262
|
-
get() {
|
|
263
|
-
return module.exports.getIndicator(ind.scriptIdPart, ind.version);
|
|
264
|
-
},
|
|
265
|
-
})),
|
|
266
|
-
];
|
|
267
|
-
},
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Get an indicator
|
|
271
|
-
* @function getIndicator
|
|
272
|
-
* @param {string} id Indicator ID (Like: PUB;XXXXXXXXXXXXXXXXXXXXX)
|
|
273
|
-
* @param {'last' | string} [version] Wanted version of the indicator
|
|
274
|
-
* @param {string} [session] User 'sessionid' cookie
|
|
275
|
-
* @param {string} [signature] User 'sessionid_sign' cookie
|
|
276
|
-
* @returns {Promise<PineIndicator>} Indicator
|
|
277
|
-
*/
|
|
278
|
-
async getIndicator(id, version = 'last', session = '', signature = '') {
|
|
279
|
-
const indicID = id.replace(/ |%/g, '%25');
|
|
280
|
-
|
|
281
|
-
const { data } = await axios.get(
|
|
282
|
-
`https://pine-facade.tradingview.com/pine-facade/translate/${indicID}/${version}`,
|
|
283
|
-
{
|
|
284
|
-
headers: {
|
|
285
|
-
cookie: genAuthCookies(session, signature),
|
|
286
|
-
},
|
|
287
|
-
validateStatus,
|
|
288
|
-
},
|
|
289
|
-
);
|
|
290
|
-
|
|
291
|
-
if (!data.success || !data.result.metaInfo || !data.result.metaInfo.inputs) {
|
|
292
|
-
throw new Error(`Inexistent or unsupported indicator: "${data.reason}"`);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const inputs = {};
|
|
296
|
-
|
|
297
|
-
data.result.metaInfo.inputs.forEach((input) => {
|
|
298
|
-
if (['text', 'pineId', 'pineVersion'].includes(input.id)) return;
|
|
299
|
-
|
|
300
|
-
const inlineName = input.name.replace(/ /g, '_').replace(/[^a-zA-Z0-9_]/g, '');
|
|
301
|
-
|
|
302
|
-
inputs[input.id] = {
|
|
303
|
-
name: input.name,
|
|
304
|
-
inline: input.inline || inlineName,
|
|
305
|
-
internalID: input.internalID || inlineName,
|
|
306
|
-
tooltip: input.tooltip,
|
|
307
|
-
|
|
308
|
-
type: input.type,
|
|
309
|
-
value: input.defval,
|
|
310
|
-
isHidden: !!input.isHidden,
|
|
311
|
-
isFake: !!input.isFake,
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
if (input.options) inputs[input.id].options = input.options;
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
const plots = {};
|
|
318
|
-
|
|
319
|
-
Object.keys(data.result.metaInfo.styles).forEach((plotId) => {
|
|
320
|
-
const plotTitle = data
|
|
321
|
-
.result
|
|
322
|
-
.metaInfo
|
|
323
|
-
.styles[plotId]
|
|
324
|
-
.title
|
|
325
|
-
.replace(/ /g, '_')
|
|
326
|
-
.replace(/[^a-zA-Z0-9_]/g, '');
|
|
327
|
-
|
|
328
|
-
const titles = Object.values(plots);
|
|
329
|
-
|
|
330
|
-
if (titles.includes(plotTitle)) {
|
|
331
|
-
let i = 2;
|
|
332
|
-
while (titles.includes(`${plotTitle}_${i}`)) i += 1;
|
|
333
|
-
plots[plotId] = `${plotTitle}_${i}`;
|
|
334
|
-
} else plots[plotId] = plotTitle;
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
data.result.metaInfo.plots.forEach((plot) => {
|
|
338
|
-
if (!plot.target) return;
|
|
339
|
-
plots[plot.id] = `${plots[plot.target] ?? plot.target}_${plot.type}`;
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
return new PineIndicator({
|
|
343
|
-
pineId: data.result.metaInfo.scriptIdPart || indicID,
|
|
344
|
-
pineVersion: data.result.metaInfo.pine.version || version,
|
|
345
|
-
description: data.result.metaInfo.description,
|
|
346
|
-
shortDescription: data.result.metaInfo.shortDescription,
|
|
347
|
-
inputs,
|
|
348
|
-
plots,
|
|
349
|
-
script: data.result.ilTemplate,
|
|
350
|
-
});
|
|
351
|
-
},
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* @typedef {Object} User Instance of User
|
|
355
|
-
* @prop {number} id User ID
|
|
356
|
-
* @prop {string} username User username
|
|
357
|
-
* @prop {string} firstName User first name
|
|
358
|
-
* @prop {string} lastName User last name
|
|
359
|
-
* @prop {number} reputation User reputation
|
|
360
|
-
* @prop {number} following Number of following accounts
|
|
361
|
-
* @prop {number} followers Number of followers
|
|
362
|
-
* @prop {Object} notifications User's notifications
|
|
363
|
-
* @prop {number} notifications.user User notifications
|
|
364
|
-
* @prop {number} notifications.following Notification from following accounts
|
|
365
|
-
* @prop {string} session User session
|
|
366
|
-
* @prop {string} sessionHash User session hash
|
|
367
|
-
* @prop {string} signature User session signature
|
|
368
|
-
* @prop {string} privateChannel User private channel
|
|
369
|
-
* @prop {string} authToken User auth token
|
|
370
|
-
* @prop {Date} joinDate Account creation date
|
|
371
|
-
*/
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* Get user and sessionid from username/email and password
|
|
375
|
-
* @function loginUser
|
|
376
|
-
* @param {string} username User username/email
|
|
377
|
-
* @param {string} password User password
|
|
378
|
-
* @param {boolean} [remember] Remember the session (default: false)
|
|
379
|
-
* @param {string} [UA] Custom UserAgent
|
|
380
|
-
* @returns {Promise<User>} Token
|
|
381
|
-
*/
|
|
382
|
-
async loginUser(username, password, remember = true, UA = 'TWAPI/3.0') {
|
|
383
|
-
const { data, headers } = await axios.post(
|
|
384
|
-
'https://www.tradingview.com/accounts/signin/',
|
|
385
|
-
`username=${username}&password=${password}${remember ? '&remember=on' : ''}`,
|
|
386
|
-
{
|
|
387
|
-
headers: {
|
|
388
|
-
referer: 'https://www.tradingview.com',
|
|
389
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
390
|
-
'User-agent': `${UA} (${os.version()}; ${os.platform()}; ${os.arch()})`,
|
|
391
|
-
},
|
|
392
|
-
validateStatus,
|
|
393
|
-
},
|
|
394
|
-
);
|
|
395
|
-
|
|
396
|
-
const cookies = headers['set-cookie'];
|
|
397
|
-
|
|
398
|
-
if (data.error) throw new Error(data.error);
|
|
399
|
-
|
|
400
|
-
const sessionCookie = cookies.find((c) => c.includes('sessionid='));
|
|
401
|
-
const session = (sessionCookie.match(/sessionid=(.*?);/) ?? [])[1];
|
|
402
|
-
|
|
403
|
-
const signCookie = cookies.find((c) => c.includes('sessionid_sign='));
|
|
404
|
-
const signature = (signCookie.match(/sessionid_sign=(.*?);/) ?? [])[1];
|
|
405
|
-
|
|
406
|
-
return {
|
|
407
|
-
id: data.user.id,
|
|
408
|
-
username: data.user.username,
|
|
409
|
-
firstName: data.user.first_name,
|
|
410
|
-
lastName: data.user.last_name,
|
|
411
|
-
reputation: data.user.reputation,
|
|
412
|
-
following: data.user.following,
|
|
413
|
-
followers: data.user.followers,
|
|
414
|
-
notifications: data.user.notification_count,
|
|
415
|
-
session,
|
|
416
|
-
signature,
|
|
417
|
-
sessionHash: data.user.session_hash,
|
|
418
|
-
privateChannel: data.user.private_channel,
|
|
419
|
-
authToken: data.user.auth_token,
|
|
420
|
-
joinDate: new Date(data.user.date_joined),
|
|
421
|
-
};
|
|
422
|
-
},
|
|
423
|
-
|
|
424
|
-
/**
|
|
425
|
-
* Get user from 'sessionid' cookie
|
|
426
|
-
* @function getUser
|
|
427
|
-
* @param {string} session User 'sessionid' cookie
|
|
428
|
-
* @param {string} [signature] User 'sessionid_sign' cookie
|
|
429
|
-
* @param {string} [location] Auth page location (For france: https://fr.tradingview.com/)
|
|
430
|
-
* @returns {Promise<User>} Token
|
|
431
|
-
*/
|
|
432
|
-
async getUser(session, signature = '', location = 'https://www.tradingview.com/') {
|
|
433
|
-
const { data, headers } = await axios.get(location, {
|
|
434
|
-
headers: {
|
|
435
|
-
cookie: genAuthCookies(session, signature),
|
|
436
|
-
},
|
|
437
|
-
maxRedirects: 0,
|
|
438
|
-
validateStatus,
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
if (data.includes('auth_token')) {
|
|
442
|
-
return {
|
|
443
|
-
id: /"id":([0-9]{1,10}),/.exec(data)?.[1],
|
|
444
|
-
username: /"username":"(.*?)"/.exec(data)?.[1],
|
|
445
|
-
firstName: /"first_name":"(.*?)"/.exec(data)?.[1],
|
|
446
|
-
lastName: /"last_name":"(.*?)"/.exec(data)?.[1],
|
|
447
|
-
reputation: parseFloat(/"reputation":(.*?),/.exec(data)?.[1] || 0),
|
|
448
|
-
following: parseFloat(/,"following":([0-9]*?),/.exec(data)?.[1] || 0),
|
|
449
|
-
followers: parseFloat(/,"followers":([0-9]*?),/.exec(data)?.[1] || 0),
|
|
450
|
-
notifications: {
|
|
451
|
-
following: parseFloat(/"notification_count":\{"following":([0-9]*),/.exec(data)?.[1] ?? 0),
|
|
452
|
-
user: parseFloat(/"notification_count":\{"following":[0-9]*,"user":([0-9]*)/.exec(data)?.[1] ?? 0),
|
|
453
|
-
},
|
|
454
|
-
session,
|
|
455
|
-
signature,
|
|
456
|
-
sessionHash: /"session_hash":"(.*?)"/.exec(data)?.[1],
|
|
457
|
-
privateChannel: /"private_channel":"(.*?)"/.exec(data)?.[1],
|
|
458
|
-
authToken: /"auth_token":"(.*?)"/.exec(data)?.[1],
|
|
459
|
-
joinDate: new Date(/"date_joined":"(.*?)"/.exec(data)?.[1] || 0),
|
|
460
|
-
};
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
if (headers.location !== location) {
|
|
464
|
-
return this.getUser(session, signature, headers.location);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
throw new Error('Wrong or expired sessionid/signature');
|
|
468
|
-
},
|
|
469
|
-
|
|
470
|
-
/**
|
|
471
|
-
* Get user's private indicators from a 'sessionid' cookie
|
|
472
|
-
* @function getPrivateIndicators
|
|
473
|
-
* @param {string} session User 'sessionid' cookie
|
|
474
|
-
* @param {string} [signature] User 'sessionid_sign' cookie
|
|
475
|
-
* @returns {Promise<SearchIndicatorResult[]>} Search results
|
|
476
|
-
*/
|
|
477
|
-
async getPrivateIndicators(session, signature = '') {
|
|
478
|
-
const { data } = await axios.get(
|
|
479
|
-
'https://pine-facade.tradingview.com/pine-facade/list',
|
|
480
|
-
{
|
|
481
|
-
headers: {
|
|
482
|
-
cookie: genAuthCookies(session, signature),
|
|
483
|
-
},
|
|
484
|
-
params: {
|
|
485
|
-
filter: 'saved',
|
|
486
|
-
},
|
|
487
|
-
validateStatus,
|
|
488
|
-
},
|
|
489
|
-
);
|
|
490
|
-
|
|
491
|
-
return data.map((ind) => ({
|
|
492
|
-
id: ind.scriptIdPart,
|
|
493
|
-
version: ind.version,
|
|
494
|
-
name: ind.scriptName,
|
|
495
|
-
author: {
|
|
496
|
-
id: -1,
|
|
497
|
-
username: '@ME@',
|
|
498
|
-
},
|
|
499
|
-
image: ind.imageUrl,
|
|
500
|
-
access: 'private',
|
|
501
|
-
source: ind.scriptSource,
|
|
502
|
-
type: (ind.extra && ind.extra.kind) ? ind.extra.kind : 'study',
|
|
503
|
-
get() {
|
|
504
|
-
return module.exports.getIndicator(
|
|
505
|
-
ind.scriptIdPart,
|
|
506
|
-
ind.version,
|
|
507
|
-
session,
|
|
508
|
-
signature,
|
|
509
|
-
);
|
|
510
|
-
},
|
|
511
|
-
}));
|
|
512
|
-
},
|
|
513
|
-
|
|
514
|
-
/**
|
|
515
|
-
* User credentials
|
|
516
|
-
* @typedef {Object} UserCredentials
|
|
517
|
-
* @prop {number} id User ID
|
|
518
|
-
* @prop {string} session User session ('sessionid' cookie)
|
|
519
|
-
* @prop {string} [signature] User session signature ('sessionid_sign' cookie)
|
|
520
|
-
*/
|
|
521
|
-
|
|
522
|
-
/**
|
|
523
|
-
* Get a chart token from a layout ID and the user credentials if the layout is not public
|
|
524
|
-
* @function getChartToken
|
|
525
|
-
* @param {string} layout The layout ID found in the layout URL (Like: 'XXXXXXXX')
|
|
526
|
-
* @param {UserCredentials} [credentials] User credentials (id + session + [signature])
|
|
527
|
-
* @returns {Promise<string>} Token
|
|
528
|
-
*/
|
|
529
|
-
async getChartToken(layout, credentials = {}) {
|
|
530
|
-
const { id, session, signature } = (
|
|
531
|
-
credentials.id && credentials.session
|
|
532
|
-
? credentials
|
|
533
|
-
: { id: -1, session: null, signature: null }
|
|
534
|
-
);
|
|
535
|
-
|
|
536
|
-
const { data } = await axios.get(
|
|
537
|
-
'https://www.tradingview.com/chart-token',
|
|
538
|
-
{
|
|
539
|
-
headers: {
|
|
540
|
-
cookie: genAuthCookies(session, signature),
|
|
541
|
-
},
|
|
542
|
-
params: {
|
|
543
|
-
image_url: layout,
|
|
544
|
-
user_id: id,
|
|
545
|
-
},
|
|
546
|
-
validateStatus,
|
|
547
|
-
},
|
|
548
|
-
);
|
|
549
|
-
|
|
550
|
-
if (!data.token) throw new Error('Wrong layout or credentials');
|
|
551
|
-
|
|
552
|
-
return data.token;
|
|
553
|
-
},
|
|
554
|
-
|
|
555
|
-
/**
|
|
556
|
-
* @typedef {Object} DrawingPoint Drawing poitn
|
|
557
|
-
* @prop {number} time_t Point X time position
|
|
558
|
-
* @prop {number} price Point Y price position
|
|
559
|
-
* @prop {number} offset Point offset
|
|
560
|
-
*/
|
|
561
|
-
|
|
562
|
-
/**
|
|
563
|
-
* @typedef {Object} Drawing
|
|
564
|
-
* @prop {string} id Drawing ID (Like: 'XXXXXX')
|
|
565
|
-
* @prop {string} symbol Layout market symbol (Like: 'BINANCE:BUCEUR')
|
|
566
|
-
* @prop {string} ownerSource Owner user ID (Like: 'XXXXXX')
|
|
567
|
-
* @prop {string} serverUpdateTime Drawing last update timestamp
|
|
568
|
-
* @prop {string} currencyId Currency ID (Like: 'EUR')
|
|
569
|
-
* @prop {any} unitId Unit ID
|
|
570
|
-
* @prop {string} type Drawing type
|
|
571
|
-
* @prop {DrawingPoint[]} points List of drawing points
|
|
572
|
-
* @prop {number} zorder Drawing Z order
|
|
573
|
-
* @prop {string} linkKey Drawing link key
|
|
574
|
-
* @prop {Object} state Drawing state
|
|
575
|
-
*/
|
|
576
|
-
|
|
577
|
-
/**
|
|
578
|
-
* Get a chart token from a layout ID and the user credentials if the layout is not public
|
|
579
|
-
* @function getDrawings
|
|
580
|
-
* @param {string} layout The layout ID found in the layout URL (Like: 'XXXXXXXX')
|
|
581
|
-
* @param {string | ''} [symbol] Market filter (Like: 'BINANCE:BTCEUR')
|
|
582
|
-
* @param {UserCredentials} [credentials] User credentials (id + session + [signature])
|
|
583
|
-
* @param {number} [chartID] Chart ID
|
|
584
|
-
* @returns {Promise<Drawing[]>} Drawings
|
|
585
|
-
*/
|
|
586
|
-
async getDrawings(layout, symbol = '', credentials = {}, chartID = '_shared') {
|
|
587
|
-
const chartToken = await module.exports.getChartToken(layout, credentials);
|
|
588
|
-
|
|
589
|
-
const { data } = await axios.get(
|
|
590
|
-
`https://charts-storage.tradingview.com/charts-storage/get/layout/${
|
|
591
|
-
layout
|
|
592
|
-
}/sources`,
|
|
593
|
-
{
|
|
594
|
-
params: {
|
|
595
|
-
chart_id: chartID,
|
|
596
|
-
jwt: chartToken,
|
|
597
|
-
symbol,
|
|
598
|
-
},
|
|
599
|
-
validateStatus,
|
|
600
|
-
},
|
|
601
|
-
);
|
|
602
|
-
|
|
603
|
-
if (!data.payload) throw new Error('Wrong layout, user credentials, or chart id.');
|
|
604
|
-
|
|
605
|
-
return Object.values(data.payload.sources || {}).map((drawing) => ({
|
|
606
|
-
...drawing, ...drawing.state,
|
|
607
|
-
}));
|
|
608
|
-
},
|
|
609
|
-
};
|
package/src/protocol.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
const JSZip = require('jszip');
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @typedef {Object} TWPacket
|
|
5
|
-
* @prop {string} [m] Packet type
|
|
6
|
-
* @prop {[session: string, {}]} [p] Packet data
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const cleanerRgx = /~h~/g;
|
|
10
|
-
const splitterRgx = /~m~[0-9]{1,}~m~/g;
|
|
11
|
-
|
|
12
|
-
module.exports = {
|
|
13
|
-
/**
|
|
14
|
-
* Parse websocket packet
|
|
15
|
-
* @function parseWSPacket
|
|
16
|
-
* @param {string} str Websocket raw data
|
|
17
|
-
* @returns {TWPacket[]} TradingView packets
|
|
18
|
-
*/
|
|
19
|
-
parseWSPacket(str) {
|
|
20
|
-
return str.replace(cleanerRgx, '').split(splitterRgx)
|
|
21
|
-
.map((p) => {
|
|
22
|
-
if (!p) return false;
|
|
23
|
-
try {
|
|
24
|
-
return JSON.parse(p);
|
|
25
|
-
} catch (error) {
|
|
26
|
-
console.warn('Cant parse', p);
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
})
|
|
30
|
-
.filter((p) => p);
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Format websocket packet
|
|
35
|
-
* @function formatWSPacket
|
|
36
|
-
* @param {TWPacket} packet TradingView packet
|
|
37
|
-
* @returns {string} Websocket raw data
|
|
38
|
-
*/
|
|
39
|
-
formatWSPacket(packet) {
|
|
40
|
-
const msg = typeof packet === 'object'
|
|
41
|
-
? JSON.stringify(packet)
|
|
42
|
-
: packet;
|
|
43
|
-
return `~m~${msg.length}~m~${msg}`;
|
|
44
|
-
},
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Parse compressed data
|
|
48
|
-
* @function parseCompressed
|
|
49
|
-
* @param {string} data Compressed data
|
|
50
|
-
* @returns {Promise<{}>} Parsed data
|
|
51
|
-
*/
|
|
52
|
-
async parseCompressed(data) {
|
|
53
|
-
const zip = new JSZip();
|
|
54
|
-
return JSON.parse(
|
|
55
|
-
await (
|
|
56
|
-
await zip.loadAsync(data, { base64: true })
|
|
57
|
-
).file('').async('text'),
|
|
58
|
-
);
|
|
59
|
-
},
|
|
60
|
-
};
|