Skip to content

Commit e8a07f9

Browse files
authored
Merge pull request #252 from Mathieu2301/hotfix/searchmarket-wrong-results
fix(searchmarket): use of the new /v3/ endpoint
2 parents 5544817 + ab14827 commit e8a07f9

File tree

5 files changed

+162
-101
lines changed

5 files changed

+162
-101
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,6 @@ module.exports = {
2323
'no-restricted-syntax': 'off',
2424
'no-await-in-loop': 'off',
2525
'no-continue': 'off',
26+
'guard-for-in': 'off',
2627
},
2728
};

src/classes/PinePermManager.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const axios = require('axios');
2+
const { genAuthCookies } = require('../utils');
23

34
/**
45
* @typedef {Object} AuthorizationUser
@@ -50,7 +51,7 @@ class PinePermManager {
5051
headers: {
5152
origin: 'https://www.tradingview.com',
5253
'Content-Type': 'application/x-www-form-urlencoded',
53-
cookie: `sessionid=${this.sessionId};sessionid_sign=${this.signature};`,
54+
cookie: genAuthCookies(this.sessionId, this.signature),
5455
},
5556
},
5657
);
@@ -84,7 +85,7 @@ class PinePermManager {
8485
headers: {
8586
origin: 'https://www.tradingview.com',
8687
'Content-Type': 'application/x-www-form-urlencoded',
87-
cookie: `sessionid=${this.sessionId};sessionid_sign=${this.signature};`,
88+
cookie: genAuthCookies(this.sessionId, this.signature),
8889
},
8990
},
9091
);
@@ -118,7 +119,7 @@ class PinePermManager {
118119
headers: {
119120
origin: 'https://www.tradingview.com',
120121
'Content-Type': 'application/x-www-form-urlencoded',
121-
cookie: `sessionid=${this.sessionId};sessionid_sign=${this.signature};`,
122+
cookie: genAuthCookies(this.sessionId, this.signature),
122123
},
123124
},
124125
);
@@ -143,7 +144,7 @@ class PinePermManager {
143144
headers: {
144145
origin: 'https://www.tradingview.com',
145146
'Content-Type': 'application/x-www-form-urlencoded',
146-
cookie: `sessionid=${this.sessionId};sessionid_sign=${this.signature};`,
147+
cookie: genAuthCookies(this.sessionId, this.signature),
147148
},
148149
},
149150
);

src/miscRequests.js

Lines changed: 115 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,22 @@ const os = require('os');
22
const axios = require('axios');
33

44
const PineIndicator = require('./classes/PineIndicator');
5+
const { genAuthCookies } = require('./utils');
56

67
const validateStatus = (status) => status < 500;
78

89
const indicators = ['Recommend.Other', 'Recommend.All', 'Recommend.MA'];
910
const builtInIndicList = [];
1011

11-
async function fetchScanData(tickers = [], type = '', columns = []) {
12-
const { data } = await axios.post(`https://scanner.tradingview.com/${type}/scan`, {
13-
symbols: { tickers },
14-
columns,
15-
}, { validateStatus });
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+
);
1621

1722
return data;
1823
}
@@ -40,56 +45,21 @@ async function fetchScanData(tickers = [], type = '', columns = []) {
4045
* }} Periods
4146
*/
4247

43-
// /**
44-
// * @typedef {string | 'forex' | 'crypto'
45-
// * | 'america' | 'australia' | 'canada' | 'egypt'
46-
// * | 'germany' | 'india' | 'israel' | 'italy'
47-
// * | 'luxembourg' | 'poland' | 'sweden' | 'turkey'
48-
// * | 'uk' | 'vietnam'} Screener
49-
// * You can use `getScreener(exchange)` function for non-forex and non-crypto markets.
50-
// */
51-
5248
module.exports = {
53-
// /**
54-
// * Get a screener from an exchange
55-
// * @function getScreener
56-
// * @param {string} exchange Example: BINANCE, EURONEXT, NASDAQ
57-
// * @returns {Screener}
58-
// */
59-
// getScreener(exchange) {
60-
// const e = exchange.toUpperCase();
61-
// if (['NASDAQ', 'NYSE', 'NYSE ARCA', 'OTC'].includes(e)) return 'america';
62-
// if (['ASX'].includes(e)) return 'australia';
63-
// if (['TSX', 'TSXV', 'CSE', 'NEO'].includes(e)) return 'canada';
64-
// if (['EGX'].includes(e)) return 'egypt';
65-
// if (['FWB', 'SWB', 'XETR'].includes(e)) return 'germany';
66-
// if (['BSE', 'NSE'].includes(e)) return 'india';
67-
// if (['TASE'].includes(e)) return 'israel';
68-
// if (['MIL', 'MILSEDEX'].includes(e)) return 'italy';
69-
// if (['LUXSE'].includes(e)) return 'luxembourg';
70-
// if (['NEWCONNECT'].includes(e)) return 'poland';
71-
// if (['NGM'].includes(e)) return 'sweden';
72-
// if (['BIST'].includes(e)) return 'turkey';
73-
// if (['LSE', 'LSIN'].includes(e)) return 'uk';
74-
// if (['HNX'].includes(e)) return 'vietnam';
75-
// return 'global';
76-
// },
77-
7849
/**
7950
* Get technical analysis
8051
* @function getTA
81-
* @param {Screener} screener Market screener
8252
* @param {string} id Full market id (Example: COINBASE:BTCEUR)
8353
* @returns {Promise<Periods>} results
8454
*/
85-
async getTA(screener, id) {
55+
async getTA(id) {
8656
const advice = {};
8757

8858
const cols = ['1', '5', '15', '60', '240', '1D', '1W', '1M']
8959
.map((t) => indicators.map((i) => (t !== '1D' ? `${i}|${t}` : i)))
9060
.flat();
9161

92-
const rs = await fetchScanData([id], screener, cols);
62+
const rs = await fetchScanData([id], cols);
9363
if (!rs.data || !rs.data[0]) return false;
9464

9565
rs.data[0].d.forEach((val, i) => {
@@ -107,53 +77,99 @@ module.exports = {
10777
* @prop {string} id Market full symbol
10878
* @prop {string} exchange Market exchange name
10979
* @prop {string} fullExchange Market exchange full name
110-
* @prop {Screener | 'forex' | 'crypto'} screener Market screener
11180
* @prop {string} symbol Market symbol
11281
* @prop {string} description Market name
11382
* @prop {string} type Market type
11483
* @prop {() => Promise<Periods>} getTA Get market technical analysis
11584
*/
11685

11786
/**
118-
* Find a symbol
87+
* Find a symbol (deprecated)
11988
* @function searchMarket
12089
* @param {string} search Keywords
12190
* @param {'stock'
12291
* | 'futures' | 'forex' | 'cfd'
12392
* | 'crypto' | 'index' | 'economic'
12493
* } [filter] Caterogy filter
12594
* @returns {Promise<SearchMarketResult[]>} Search results
95+
* @deprecated Use searchMarketV3 instead
12696
*/
12797
async searchMarket(search, filter = '') {
12898
const { data } = await axios.get(
129-
`https://symbol-search.tradingview.com/symbol_search/?text=${search.replace(/ /g, '%20')}&type=${filter}`,
99+
'https://symbol-search.tradingview.com/symbol_search',
130100
{
131-
validateStatus,
101+
params: {
102+
text: search.replace(/ /g, '%20'),
103+
type: filter,
104+
},
132105
headers: {
133106
origin: 'https://www.tradingview.com',
134107
},
108+
validateStatus,
135109
},
136110
);
137111

138112
return data.map((s) => {
139113
const exchange = s.exchange.split(' ')[0];
140114
const id = `${exchange}:${s.symbol}`;
141115

142-
// const screener = (['forex', 'crypto'].includes(s.type)
143-
// ? s.type
144-
// : this.getScreener(exchange)
145-
// );
146-
const screener = 'global';
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+
* @returns {Promise<SearchMarketResult[]>} Search results
137+
*/
138+
async searchMarketV3(search, filter = '') {
139+
const splittedSearch = search.toUpperCase().replace(/ /g, '+').split(':');
140+
141+
const request = await axios.get(
142+
'https://symbol-search.tradingview.com/symbol_search/v3',
143+
{
144+
params: {
145+
exchange: (splittedSearch.length === 2
146+
? splittedSearch[0]
147+
: undefined
148+
),
149+
text: splittedSearch.pop(),
150+
search_type: filter,
151+
},
152+
headers: {
153+
origin: 'https://www.tradingview.com',
154+
},
155+
validateStatus,
156+
},
157+
);
158+
159+
const { data } = request;
160+
161+
return data.symbols.map((s) => {
162+
const exchange = s.exchange.split(' ')[0];
163+
const id = `${exchange.toUpperCase()}:${s.symbol}`;
147164

148165
return {
149166
id,
150167
exchange,
151168
fullExchange: s.exchange,
152-
screener,
153169
symbol: s.symbol,
154170
description: s.description,
155171
type: s.type,
156-
getTA: () => this.getTA(screener, id),
172+
getTA: () => this.getTA(id),
157173
};
158174
});
159175
},
@@ -182,16 +198,26 @@ module.exports = {
182198
if (!builtInIndicList.length) {
183199
await Promise.all(['standard', 'candlestick', 'fundamental'].map(async (type) => {
184200
const { data } = await axios.get(
185-
`https://pine-facade.tradingview.com/pine-facade/list/?filter=${type}`,
186-
{ validateStatus },
201+
'https://pine-facade.tradingview.com/pine-facade/list',
202+
{
203+
params: {
204+
filter: type,
205+
},
206+
validateStatus,
207+
},
187208
);
188209
builtInIndicList.push(...data);
189210
}));
190211
}
191212

192213
const { data } = await axios.get(
193-
`https://www.tradingview.com/pubscripts-suggest-json/?search=${search.replace(/ /g, '%20')}`,
194-
{ validateStatus },
214+
'https://www.tradingview.com/pubscripts-suggest-json',
215+
{
216+
params: {
217+
search: search.replace(/ /g, '%20'),
218+
},
219+
validateStatus,
220+
},
195221
);
196222

197223
function norm(str = '') {
@@ -253,10 +279,10 @@ module.exports = {
253279
const { data } = await axios.get(
254280
`https://pine-facade.tradingview.com/pine-facade/translate/${indicID}/${version}`,
255281
{
256-
validateStatus,
257282
headers: {
258-
cookie: `${session ? `sessionid=${session};` : ''}${signature ? `sessionid_sign=${signature};` : ''}`,
283+
cookie: genAuthCookies(session, signature),
259284
},
285+
validateStatus,
260286
},
261287
);
262288

@@ -356,12 +382,12 @@ module.exports = {
356382
'https://www.tradingview.com/accounts/signin/',
357383
`username=${username}&password=${password}${remember ? '&remember=on' : ''}`,
358384
{
359-
validateStatus,
360385
headers: {
361386
referer: 'https://www.tradingview.com',
362387
'Content-Type': 'application/x-www-form-urlencoded',
363388
'User-agent': `${UA} (${os.version()}; ${os.platform()}; ${os.arch()})`,
364389
},
390+
validateStatus,
365391
},
366392
);
367393

@@ -403,10 +429,10 @@ module.exports = {
403429
*/
404430
async getUser(session, signature = '', location = 'https://www.tradingview.com/') {
405431
const { data } = await axios.get(location, {
406-
validateStatus,
407432
headers: {
408-
cookie: `sessionid=${session}${signature ? `;sessionid_sign=${signature};` : ''}`,
433+
cookie: genAuthCookies(session, signature),
409434
},
435+
validateStatus,
410436
});
411437

412438
if (data.includes('auth_token')) {
@@ -442,12 +468,18 @@ module.exports = {
442468
* @returns {Promise<SearchIndicatorResult[]>} Search results
443469
*/
444470
async getPrivateIndicators(session, signature = '') {
445-
const { data } = await axios.get('https://pine-facade.tradingview.com/pine-facade/list?filter=saved', {
446-
validateStatus,
447-
headers: {
448-
cookie: `sessionid=${session}${signature ? `;sessionid_sign=${signature};` : ''}`,
471+
const { data } = await axios.get(
472+
'https://pine-facade.tradingview.com/pine-facade/list',
473+
{
474+
headers: {
475+
cookie: genAuthCookies(session, signature),
476+
},
477+
params: {
478+
filter: 'saved',
479+
},
480+
validateStatus,
449481
},
450-
});
482+
);
451483

452484
return data.map((ind) => ({
453485
id: ind.scriptIdPart,
@@ -495,14 +527,16 @@ module.exports = {
495527
);
496528

497529
const { data } = await axios.get(
498-
`https://www.tradingview.com/chart-token/?image_url=${layout}&user_id=${id}`,
530+
'https://www.tradingview.com/chart-token',
499531
{
500-
validateStatus,
501532
headers: {
502-
cookie: session
503-
? `sessionid=${session}${signature ? `;sessionid_sign=${signature};` : ''}`
504-
: '',
533+
cookie: genAuthCookies(session, signature),
505534
},
535+
params: {
536+
image_url: layout,
537+
user_id: id,
538+
},
539+
validateStatus,
506540
},
507541
);
508542

@@ -548,14 +582,15 @@ module.exports = {
548582
const { data } = await axios.get(
549583
`https://charts-storage.tradingview.com/charts-storage/get/layout/${
550584
layout
551-
}/sources?chart_id=${
552-
chartID
553-
}&jwt=${
554-
chartToken
555-
}${
556-
(symbol ? `&symbol=${symbol}` : '')
557-
}`,
558-
{ validateStatus },
585+
}/sources`,
586+
{
587+
params: {
588+
chart_id: chartID,
589+
jwt: chartToken,
590+
symbol,
591+
},
592+
validateStatus,
593+
},
559594
);
560595

561596
if (!data.payload) throw new Error('Wrong layout, user credentials, or chart id.');

src/utils.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,10 @@ module.exports = {
1111
for (let i = 0; i < 12; i += 1) r += c.charAt(Math.floor(Math.random() * c.length));
1212
return `${type}_${r}`;
1313
},
14+
15+
genAuthCookies(sessionId = '', signature = '') {
16+
if (!sessionId) return '';
17+
if (!signature) return `sessionid=${sessionId}`;
18+
return `sessionid=${sessionId};sessionid_sign=${signature}`;
19+
},
1420
};

0 commit comments

Comments
 (0)