Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@ module.exports = {
devDependencies: ['./test.js', './tests/**'],
},
],
'no-restricted-syntax': 'off',
'no-await-in-loop': 'off',
},
};
1 change: 1 addition & 0 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ jobs:
run: npm test
env:
SESSION: ${{ secrets.TW_SESSION }}
SIGNATURE: ${{ secrets.TW_SIGNATURE }}
6 changes: 5 additions & 1 deletion src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ module.exports = class Client {
/**
* @typedef {Object} ClientOptions
* @prop {string} [token] User auth token (in 'sessionid' cookie)
* @prop {string} [signature] User auth token signature (in 'sessionid_sign' cookie)
* @prop {boolean} [DEBUG] Enable debug mode
* @prop {'data' | 'prodata' | 'widgetdata'} [server] Server type
*/
Expand All @@ -230,7 +231,10 @@ module.exports = class Client {
});

if (clientOptions.token) {
misc.getUser(clientOptions.token).then((user) => {
misc.getUser(
clientOptions.token,
clientOptions.signature ? clientOptions.signature : '',
).then((user) => {
this.#sendQueue.unshift(protocol.formatWSPacket({
m: 'set_auth_token',
p: [user.authToken],
Expand Down
33 changes: 21 additions & 12 deletions src/miscRequests.js
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,11 @@ module.exports = {

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

const cookie = cookies.find((c) => c.includes('sessionid='));
const session = (cookie.match(/sessionid=(.*?);/) ?? [])[1];
const sessionCookie = cookies.find((c) => c.includes('sessionid='));
const session = (sessionCookie.match(/sessionid=(.*?);/) ?? [])[1];

const signCookie = cookies.find((c) => c.includes('sessionid_sign='));
const signature = (signCookie.match(/sessionid_sign=(.*?);/) ?? [])[1];

return {
id: data.user.id,
Expand All @@ -388,6 +391,7 @@ module.exports = {
followers: data.user.followers,
notifications: data.user.notification_count,
session,
signature,
sessionHash: data.user.session_hash,
privateChannel: data.user.private_channel,
authToken: data.user.auth_token,
Expand All @@ -399,19 +403,20 @@ module.exports = {
* Get user from 'sessionid' cookie
* @function getUser
* @param {string} session User 'sessionid' cookie
* @param {string} [signature] User 'sessionid_sign' cookie
* @param {string} [location] Auth page location (For france: https://fr.tradingview.com/)
* @returns {Promise<User>} Token
*/
async getUser(session, location = 'https://www.tradingview.com/') {
async getUser(session, signature = '', location = 'https://www.tradingview.com/') {
return new Promise((cb, err) => {
https.get(location, {
headers: { cookie: `sessionid=${session}` },
headers: { cookie: `sessionid=${session}${signature ? `;sessionid_sign=${signature};` : ''}` },
}, (res) => {
let rs = '';
res.on('data', (d) => { rs += d; });
res.on('end', async () => {
if (res.headers.location && location !== res.headers.location) {
cb(await module.exports.getUser(session, res.headers.location));
cb(await module.exports.getUser(session, signature, res.headers.location));
return;
}
if (rs.includes('auth_token')) {
Expand All @@ -428,12 +433,13 @@ module.exports = {
user: parseFloat(/"notification_count":\{"following":[0-9]*,"user":([0-9]*)/.exec(rs)[1] || 0),
},
session,
signature,
sessionHash: /"session_hash":"(.*?)"/.exec(rs)[1],
privateChannel: /"private_channel":"(.*?)"/.exec(rs)[1],
authToken: /"auth_token":"(.*?)"/.exec(rs)[1],
joinDate: new Date(/"date_joined":"(.*?)"/.exec(rs)[1] || 0),
});
} else err(new Error('Wrong or expired sessionid'));
} else err(new Error('Wrong or expired sessionid/signature'));
});

res.on('error', err);
Expand All @@ -445,12 +451,13 @@ module.exports = {
* Get user's private indicators from a 'sessionid' cookie
* @function getPrivateIndicators
* @param {string} session User 'sessionid' cookie
* @param {string} [signature] User 'sessionid_sign' cookie
* @returns {Promise<SearchIndicatorResult[]>} Search results
*/
async getPrivateIndicators(session) {
async getPrivateIndicators(session, signature = '') {
return new Promise((cb, err) => {
https.get('https://pine-facade.tradingview.com/pine-facade/list?filter=saved', {
headers: { cookie: `sessionid=${session}` },
headers: { cookie: `sessionid=${session}${signature ? `;sessionid_sign=${signature};` : ''}` },
}, (res) => {
let rs = '';
res.on('data', (d) => { rs += d; });
Expand Down Expand Up @@ -499,18 +506,19 @@ module.exports = {
* Get a chart token from a layout ID and the user credentials if the layout is not public
* @function getChartToken
* @param {string} layout The layout ID found in the layout URL (Like: 'XXXXXXXX')
* @param {UserCredentials} [credentials] User credentials (id + session)
* @param {UserCredentials} [credentials] User credentials (id + session + [signature])
* @returns {Promise<string>} Token
*/
async getChartToken(layout, credentials = {}) {
const creds = credentials.id && credentials.session;
const userID = creds ? credentials.id : -1;
const session = creds ? credentials.session : null;
const signature = creds ? credentials.signature : null;

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

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

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

if (!data.payload) throw new Error('Wrong layout, user credentials, or chart id.');
Expand Down
40 changes: 39 additions & 1 deletion tests/09. AllErrors.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,48 @@ module.exports = async (log, success, warn, err, cb) => {
next();
});
},

async (next) => { /* Testing "Wrong or expired sessionid/signature" using getUser method */
log('Testing "Wrong or expired sessionid/signature" error using getUser method:');

if (!process.env.SESSION) {
warn('=> Skipping test because no sessionid/signature was provided');
next();
return;
}

try {
await TradingView.getUser(process.env.SESSION);
err('=> User found !');
} catch (error) {
success('=> User not found:', error);
next();
}
},

async (next) => { /* Testing "Wrong or expired sessionid/signature" using client */
log('Testing "Wrong or expired sessionid/signature" error using client:');

if (!process.env.SESSION) {
warn('=> Skipping test because no sessionid/signature was provided');
next();
return;
}

log('Creating a new client...');
const client2 = new TradingView.Client({
token: process.env.SESSION,
});

client2.onError((...error) => {
success('=> Client error:', error);
client2.end();
next();
});
},
];

(async () => {
// eslint-disable-next-line no-restricted-syntax, no-await-in-loop
for (const t of tests) await new Promise(t);
success(`Crashtests ${tests.length}/${tests.length} done !`);
cb();
Expand Down
18 changes: 12 additions & 6 deletions tests/10. Authenticated.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
/* eslint-disable no-restricted-syntax */
/* eslint-disable no-await-in-loop */
const TradingView = require('../main');

const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const wait = (ms) => new Promise((cb) => { setTimeout(cb, ms); });

module.exports = async (log, success, warn, err, cb) => {
if (!process.env.SESSION || !process.env.SIGNATURE) {
warn('No sessionid/signature was provided');
cb();
return;
}

log('Getting user info');

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

log('Getting user indicators');

const userIndicators = await TradingView.getPrivateIndicators(process.env.SESSION);

if (userIndicators) {
if (userIndicators.length === 0) warn('No private indicator found');
else success('User indicators:', userIndicators.map((i) => i.name));
Expand All @@ -36,7 +40,9 @@ module.exports = async (log, success, warn, err, cb) => {
log('Creating logged client');
const client = new TradingView.Client({
token: process.env.SESSION,
signature: process.env.SIGNATURE,
});

client.onError((...error) => {
err('Client error', error);
});
Expand Down Expand Up @@ -76,7 +82,6 @@ module.exports = async (log, success, warn, err, cb) => {
await wait(1000);

log('Loading indicators...');

for (const indic of userIndicators) {
const privateIndic = await indic.get();
log(`[${indic.name}] Loading indicator...`);
Expand All @@ -92,4 +97,5 @@ module.exports = async (log, success, warn, err, cb) => {
await check(indic.id);
});
}
log('Indicators loaded !');
};