Skip to content

Commit 7aecf88

Browse files
authored
Merge pull request #197 from gasparesganga/patch-sessionid-signature
Optionally use 'sessionid_sign' cookie
2 parents f5336dd + 4597a22 commit 7aecf88

File tree

5 files changed

+79
-20
lines changed

5 files changed

+79
-20
lines changed

.eslintrc.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,7 @@ module.exports = {
2020
devDependencies: ['./test.js', './tests/**'],
2121
},
2222
],
23+
'no-restricted-syntax': 'off',
24+
'no-await-in-loop': 'off',
2325
},
2426
};

src/client.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ module.exports = class Client {
214214
/**
215215
* @typedef {Object} ClientOptions
216216
* @prop {string} [token] User auth token (in 'sessionid' cookie)
217+
* @prop {string} [signature] User auth token signature (in 'sessionid_sign' cookie)
217218
* @prop {boolean} [DEBUG] Enable debug mode
218219
* @prop {'data' | 'prodata' | 'widgetdata'} [server] Server type
219220
*/
@@ -230,7 +231,10 @@ module.exports = class Client {
230231
});
231232

232233
if (clientOptions.token) {
233-
misc.getUser(clientOptions.token).then((user) => {
234+
misc.getUser(
235+
clientOptions.token,
236+
clientOptions.signature ? clientOptions.signature : '',
237+
).then((user) => {
234238
this.#sendQueue.unshift(protocol.formatWSPacket({
235239
m: 'set_auth_token',
236240
p: [user.authToken],

src/miscRequests.js

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -375,8 +375,11 @@ module.exports = {
375375

376376
if (data.error) throw new Error(data.error);
377377

378-
const cookie = cookies.find((c) => c.includes('sessionid='));
379-
const session = (cookie.match(/sessionid=(.*?);/) ?? [])[1];
378+
const sessionCookie = cookies.find((c) => c.includes('sessionid='));
379+
const session = (sessionCookie.match(/sessionid=(.*?);/) ?? [])[1];
380+
381+
const signCookie = cookies.find((c) => c.includes('sessionid_sign='));
382+
const signature = (signCookie.match(/sessionid_sign=(.*?);/) ?? [])[1];
380383

381384
return {
382385
id: data.user.id,
@@ -388,6 +391,7 @@ module.exports = {
388391
followers: data.user.followers,
389392
notifications: data.user.notification_count,
390393
session,
394+
signature,
391395
sessionHash: data.user.session_hash,
392396
privateChannel: data.user.private_channel,
393397
authToken: data.user.auth_token,
@@ -399,19 +403,20 @@ module.exports = {
399403
* Get user from 'sessionid' cookie
400404
* @function getUser
401405
* @param {string} session User 'sessionid' cookie
406+
* @param {string} [signature] User 'sessionid_sign' cookie
402407
* @param {string} [location] Auth page location (For france: https://fr.tradingview.com/)
403408
* @returns {Promise<User>} Token
404409
*/
405-
async getUser(session, location = 'https://www.tradingview.com/') {
410+
async getUser(session, signature = '', location = 'https://www.tradingview.com/') {
406411
return new Promise((cb, err) => {
407412
https.get(location, {
408-
headers: { cookie: `sessionid=${session}` },
413+
headers: { cookie: `sessionid=${session}${signature ? `;sessionid_sign=${signature};` : ''}` },
409414
}, (res) => {
410415
let rs = '';
411416
res.on('data', (d) => { rs += d; });
412417
res.on('end', async () => {
413418
if (res.headers.location && location !== res.headers.location) {
414-
cb(await module.exports.getUser(session, res.headers.location));
419+
cb(await module.exports.getUser(session, signature, res.headers.location));
415420
return;
416421
}
417422
if (rs.includes('auth_token')) {
@@ -428,12 +433,13 @@ module.exports = {
428433
user: parseFloat(/"notification_count":\{"following":[0-9]*,"user":([0-9]*)/.exec(rs)[1] || 0),
429434
},
430435
session,
436+
signature,
431437
sessionHash: /"session_hash":"(.*?)"/.exec(rs)[1],
432438
privateChannel: /"private_channel":"(.*?)"/.exec(rs)[1],
433439
authToken: /"auth_token":"(.*?)"/.exec(rs)[1],
434440
joinDate: new Date(/"date_joined":"(.*?)"/.exec(rs)[1] || 0),
435441
});
436-
} else err(new Error('Wrong or expired sessionid'));
442+
} else err(new Error('Wrong or expired sessionid/signature'));
437443
});
438444

439445
res.on('error', err);
@@ -445,12 +451,13 @@ module.exports = {
445451
* Get user's private indicators from a 'sessionid' cookie
446452
* @function getPrivateIndicators
447453
* @param {string} session User 'sessionid' cookie
454+
* @param {string} [signature] User 'sessionid_sign' cookie
448455
* @returns {Promise<SearchIndicatorResult[]>} Search results
449456
*/
450-
async getPrivateIndicators(session) {
457+
async getPrivateIndicators(session, signature = '') {
451458
return new Promise((cb, err) => {
452459
https.get('https://pine-facade.tradingview.com/pine-facade/list?filter=saved', {
453-
headers: { cookie: `sessionid=${session}` },
460+
headers: { cookie: `sessionid=${session}${signature ? `;sessionid_sign=${signature};` : ''}` },
454461
}, (res) => {
455462
let rs = '';
456463
res.on('data', (d) => { rs += d; });
@@ -499,18 +506,19 @@ module.exports = {
499506
* Get a chart token from a layout ID and the user credentials if the layout is not public
500507
* @function getChartToken
501508
* @param {string} layout The layout ID found in the layout URL (Like: 'XXXXXXXX')
502-
* @param {UserCredentials} [credentials] User credentials (id + session)
509+
* @param {UserCredentials} [credentials] User credentials (id + session + [signature])
503510
* @returns {Promise<string>} Token
504511
*/
505512
async getChartToken(layout, credentials = {}) {
506513
const creds = credentials.id && credentials.session;
507514
const userID = creds ? credentials.id : -1;
508515
const session = creds ? credentials.session : null;
516+
const signature = creds ? credentials.signature : null;
509517

510518
const { data } = await request({
511519
host: 'www.tradingview.com',
512520
path: `/chart-token/?image_url=${layout}&user_id=${userID}`,
513-
headers: { cookie: session ? `sessionid=${session}` : '' },
521+
headers: { cookie: session ? `sessionid=${session}${signature ? `;sessionid_sign=${signature};` : ''}` : '' },
514522
});
515523

516524
if (!data.token) throw new Error('Wrong layout or credentials');
@@ -545,20 +553,21 @@ module.exports = {
545553
* @function getDrawings
546554
* @param {string} layout The layout ID found in the layout URL (Like: 'XXXXXXXX')
547555
* @param {string | ''} [symbol] Market filter (Like: 'BINANCE:BTCEUR')
548-
* @param {UserCredentials} [credentials] User credentials (id + session)
556+
* @param {UserCredentials} [credentials] User credentials (id + session + [signature])
549557
* @param {number} [chartID] Chart ID
550558
* @returns {Promise<Drawing[]>} Drawings
551559
*/
552560
async getDrawings(layout, symbol = '', credentials = {}, chartID = 1) {
553561
const chartToken = await module.exports.getChartToken(layout, credentials);
554562
const creds = credentials.id && credentials.session;
555563
const session = creds ? credentials.session : null;
564+
const signature = creds ? credentials.signature : null;
556565

557566
const { data } = await request({
558567
host: 'charts-storage.tradingview.com',
559568
path: `/charts-storage/layout/${layout}/sources?chart_id=${chartID
560569
}&jwt=${chartToken}${symbol ? `&symbol=${symbol}` : ''}`,
561-
headers: { cookie: session ? `sessionid=${session}` : '' },
570+
headers: { cookie: session ? `sessionid=${session}${signature ? `;sessionid_sign=${signature};` : ''}` : '' },
562571
});
563572

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

tests/09. AllErrors.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,48 @@ module.exports = async (log, success, warn, err, cb) => {
135135
next();
136136
});
137137
},
138+
139+
async (next) => { /* Testing "Wrong or expired sessionid/signature" using getUser method */
140+
log('Testing "Wrong or expired sessionid/signature" error using getUser method:');
141+
142+
if (!process.env.SESSION) {
143+
warn('=> Skipping test because no sessionid/signature was provided');
144+
next();
145+
return;
146+
}
147+
148+
try {
149+
await TradingView.getUser(process.env.SESSION);
150+
err('=> User found !');
151+
} catch (error) {
152+
success('=> User not found:', error);
153+
next();
154+
}
155+
},
156+
157+
async (next) => { /* Testing "Wrong or expired sessionid/signature" using client */
158+
log('Testing "Wrong or expired sessionid/signature" error using client:');
159+
160+
if (!process.env.SESSION) {
161+
warn('=> Skipping test because no sessionid/signature was provided');
162+
next();
163+
return;
164+
}
165+
166+
log('Creating a new client...');
167+
const client2 = new TradingView.Client({
168+
token: process.env.SESSION,
169+
});
170+
171+
client2.onError((...error) => {
172+
success('=> Client error:', error);
173+
client2.end();
174+
next();
175+
});
176+
},
138177
];
139178

140179
(async () => {
141-
// eslint-disable-next-line no-restricted-syntax, no-await-in-loop
142180
for (const t of tests) await new Promise(t);
143181
success(`Crashtests ${tests.length}/${tests.length} done !`);
144182
cb();

tests/10. Authenticated.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1-
/* eslint-disable no-restricted-syntax */
2-
/* eslint-disable no-await-in-loop */
31
const TradingView = require('../main');
42

5-
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
3+
const wait = (ms) => new Promise((cb) => { setTimeout(cb, ms); });
64

75
module.exports = async (log, success, warn, err, cb) => {
6+
if (!process.env.SESSION || !process.env.SIGNATURE) {
7+
warn('No sessionid/signature was provided');
8+
cb();
9+
return;
10+
}
11+
812
log('Getting user info');
913

10-
const userInfos = await TradingView.getUser(process.env.SESSION);
14+
const userInfos = await TradingView.getUser(process.env.SESSION, process.env.SIGNATURE);
1115
if (userInfos && userInfos.id) {
1216
success('User info:', {
1317
id: userInfos.id,
@@ -24,8 +28,8 @@ module.exports = async (log, success, warn, err, cb) => {
2428
await wait(1000);
2529

2630
log('Getting user indicators');
27-
2831
const userIndicators = await TradingView.getPrivateIndicators(process.env.SESSION);
32+
2933
if (userIndicators) {
3034
if (userIndicators.length === 0) warn('No private indicator found');
3135
else success('User indicators:', userIndicators.map((i) => i.name));
@@ -36,7 +40,9 @@ module.exports = async (log, success, warn, err, cb) => {
3640
log('Creating logged client');
3741
const client = new TradingView.Client({
3842
token: process.env.SESSION,
43+
signature: process.env.SIGNATURE,
3944
});
45+
4046
client.onError((...error) => {
4147
err('Client error', error);
4248
});
@@ -76,7 +82,6 @@ module.exports = async (log, success, warn, err, cb) => {
7682
await wait(1000);
7783

7884
log('Loading indicators...');
79-
8085
for (const indic of userIndicators) {
8186
const privateIndic = await indic.get();
8287
log(`[${indic.name}] Loading indicator...`);
@@ -92,4 +97,5 @@ module.exports = async (log, success, warn, err, cb) => {
9297
await check(indic.id);
9398
});
9499
}
100+
log('Indicators loaded !');
95101
};

0 commit comments

Comments
 (0)