Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
84caa21
feat: getPersonalIndicator
ThisIsRuddy Jan 27, 2025
d90503d
Merge remote-tracking branch 'upstream/main' into feat/personal-indic…
ThisIsRuddy Jan 28, 2025
8accb1f
Merge pull request #1 from danielgroen/feat/personal-indicators
danielgroen Jan 28, 2025
1899cf1
fix: re-add missing getInviteOnlyScripts function
ThisIsRuddy Jan 28, 2025
da0094d
Merge pull request #2 from danielgroen/feat/personal-indicators
ThisIsRuddy Jan 28, 2025
7b934eb
fix: reimplement deeptest since resynce with original repo
ThisIsRuddy Feb 8, 2025
5a85ece
Merge pull request #3 from danielgroen/feat/deepbacktest
ThisIsRuddy Feb 8, 2025
2aa64b9
feat: added clone function to PineIndicator & BuiltInIndicator classes
ThisIsRuddy Feb 17, 2025
3ecc9c9
Merge pull request #4 from danielgroen/feat/clone-indicator
ThisIsRuddy Feb 17, 2025
c12e4ee
dev: created initial types main.d.ts
ThisIsRuddy Feb 17, 2025
2a172f2
dev: enhanced types main.d.ts
ThisIsRuddy Feb 17, 2025
27721d9
dev: enhanced types main.d.ts
ThisIsRuddy Feb 18, 2025
b1fdb8f
feat: layouts
ThisIsRuddy Feb 23, 2025
830fff8
dev: rework + clean-up
ThisIsRuddy Feb 25, 2025
a9ec7c2
dev: deleteLayout
ThisIsRuddy Feb 25, 2025
943e58a
dev: replace env variable refs
ThisIsRuddy Feb 25, 2025
de11a83
Merge pull request #5 from danielgroen/feat/layouts
ThisIsRuddy Feb 25, 2025
3d43bac
dev: re-add console.error in try catches
ThisIsRuddy Feb 25, 2025
5875406
Merge pull request #6 from danielgroen/feat/layouts
ThisIsRuddy Feb 25, 2025
db626ec
dev: change createLayout to only return layoutShortURL
ThisIsRuddy Feb 25, 2025
0842980
Merge pull request #7 from danielgroen/feat/layouts
ThisIsRuddy Feb 25, 2025
8388613
adding 2fa login and changed return object of user
Mar 2, 2025
b643841
fixed type
Mar 2, 2025
2127b1a
adding totp for 2fa
Mar 2, 2025
c2b1835
added conditional qmark to prevent err
Mar 2, 2025
05f7347
optional chaining to prevent errors
Mar 2, 2025
ce1526a
Merge pull request #8 from danielgroen/feat/twofalogin
danielgroen Mar 2, 2025
9d9cae6
Added feature to get the first bar available for deepbacktest
Mar 2, 2025
68d8180
Added feature to get the first bar available for deepbacktest
Mar 2, 2025
2d229b9
fix: update getPersonalIndicator to use latest version
ThisIsRuddy Mar 5, 2025
b04deda
Merge pull request #11 from danielgroen/hotfix/latest-personal
ThisIsRuddy Mar 5, 2025
a008a88
Merge pull request #10 from danielgroen/feat/getFirstBarAvailable
ThisIsRuddy Mar 6, 2025
0def8b9
added more fields for getuser
Mar 6, 2025
41f0ebc
added more fields for getuser
Mar 6, 2025
47510e4
Merge pull request #12 from danielgroen/feat/get-user-expanded
danielgroen Mar 6, 2025
0384184
cleanup
Mar 6, 2025
c902fa0
Added new alert functionalities
Mar 12, 2025
28dd4fe
Merge pull request #13 from danielgroen/feat/alerts
danielgroen Mar 12, 2025
e083887
bugfix authtoken
Mar 17, 2025
93c991c
Merge pull request #14 from danielgroen/feat/fix-authToken
danielgroen Mar 17, 2025
db97878
typo
Apr 7, 2025
6863f8b
test
Apr 8, 2025
af01bc5
test
Apr 8, 2025
a814be5
test
Apr 8, 2025
93c84aa
feat: createAlertForChart
ThisIsRuddy Apr 12, 2025
9be5f4a
Merge pull request #15 from danielgroen/feat/createAlertForChart
ThisIsRuddy Apr 12, 2025
9a9af73
fix: resolves bad currency issue with some symbols
ThisIsRuddy Apr 26, 2025
0487905
Merge pull request #16 from danielgroen/fix/create-layouts
ThisIsRuddy Apr 26, 2025
33eae0c
feat: udpateLayoutStudyInputs
ThisIsRuddy Apr 26, 2025
6e0dda2
Merge pull request #17 from danielgroen/feat/updateLayoutInputs
ThisIsRuddy Apr 26, 2025
e9e0947
rebase from fork
May 6, 2025
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
5 changes: 2 additions & 3 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ module.exports = {
es2021: true,
node: true,
},
extends: [
'airbnb-base',
],
extends: ['airbnb-base'],
parser: '@babel/eslint-parser',
parserOptions: {
ecmaVersion: 12,
requireConfigFile: false,
},
rules: {
quotes: ['error', 'single'],
'no-console': 'off',
'import/no-extraneous-dependencies': [
'error',
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*
16 changes: 16 additions & 0 deletions examples/TwoFa.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const TradingView = require('../main');

/**
* This example tests the user login function
*/

if (!process.argv[2]) throw Error('Please specify your username/email');
if (!process.argv[3]) throw Error('Please specify your password');

TradingView.twoFactorAuth(process.argv[2], process.argv[3], 'sms', process.argv[4])
.then((data) => {
console.log(data);
})
.catch((err) => {
console.error('Login error:', err.message);
});
14 changes: 14 additions & 0 deletions examples/getUser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const TradingView = require('../main');

/**
* This example tests the user login function
*/

if (!process.argv[2]) throw Error('Please specify your session');
if (!process.argv[3]) throw Error('Please specify your signature');

TradingView.getUser(process.argv[2], process.argv[3]).then((data) => {
console.log(data);
}).catch((err) => {
console.error('Login error:', err.message);
});
98 changes: 86 additions & 12 deletions main.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,32 +163,106 @@ declare module '@mathieuc/tradingview' {
signature?: string,
): Promise<PineIndicator>;

export interface TwoFactorInfoMessage {
detail: string;
code: string;
two_factor_types: [
{
name: string,
code_ttl: number
}
]
}
export interface User {
id: number;
username: string;
firstName: string;
lastName: string;
first_name?: string;
last_name?: string;
reputation: number;
following: number;
followers: number;
notifications: {
user: number;
following: number;
notification_count: {
user: number;
following: number;
};
session: string;
sessionHash: string;
signature: string;
privateChannel: string;
authToken: string;
joinDate: Date;
session_hash: string;
private_channel: string;
auth_token: string;
date_joined: string;
has_active_email: boolean;
userpic?: string;
userpic_mid?: string;
userpic_big?: string;
status?: string;
must_change_password: boolean;
must_change_tfa: boolean;
notification_popup: boolean;
notification_sound: boolean;
max_user_language_reputation: number;
profile_data_filled: boolean;
is_corporation_user: boolean;
active_broker?: any;
ignore_list: any[];
is_active_partner: boolean;
is_broker: boolean;
broker_plan?: any;
badges?: any[];
permissions: Record<string, any>;
is_symphony: boolean;
is_staff: boolean;
is_superuser: boolean;
is_moderator: boolean;
last_locale: string;
social_registration: boolean;
has_phone: boolean;
sms_email?: string | null;
is_non_pro_confirmed: boolean;
do_not_track: boolean;
is_pro: boolean;
is_expert: boolean;
is_trial: boolean;
is_lite_plan: boolean;
pro_being_cancelled?: boolean | null;
pro_plan_days_left: number;
pro_plan_original_name?: string;
pro_plan: string;
pro_plan_billing_cycle: string;
trial_days_left?: number | null;
trial_days_left_text?: string;
available_offers: Record<string, any>;
had_pro: boolean;
declared_status: string;
declared_status_timestamp?: string | null;
market_profile_updated_timestamp?: string | null;
force_to_complete_data: boolean;
force_to_upgrade: boolean;
is_support_available: boolean;
disallow_adding_to_private_chats: boolean;
picture_url?: string;
}


export interface LoginResponse {
session: string;
signature: string;
user?: User;
two_factor_info?: TwoFactorInfoMessage;
}

export function loginUser(
username: string,
password: string,
remember?: boolean,
UA?: string,
): Promise<User>;
): Promise<LoginResponse>;

export function twoFactorAuth(
smsCode: string,
session: string,
signature: string,
twoFaType?: 'sms' | 'totp',
UA?: string,
): Promise<LoginResponse>;

export function getUser(session: string, signature?: string, location?: string): Promise<User>;

Expand Down
186 changes: 155 additions & 31 deletions src/miscRequests.js
Original file line number Diff line number Diff line change
Expand Up @@ -441,20 +441,85 @@ module.exports = {
* @typedef {Object} User Instance of User
* @prop {number} id User ID
* @prop {string} username User username
* @prop {string} firstName User first name
* @prop {string} lastName User last name
* @prop {boolean} has_active_email Whether the user has an active email
* @prop {Date} date_joined Account creation date
* @prop {string} userpic User profile picture URL
* @prop {string} userpic_mid Medium-sized profile picture URL
* @prop {string} userpic_big Large-sized profile picture URL
* @prop {string} status User status
* @prop {boolean} must_change_password Whether the user must change their password
* @prop {boolean} must_change_tfa Whether the user must enable two-factor authentication
* @prop {string} private_channel User private channel
* @prop {string} session_hash User session hash
* @prop {boolean} notification_popup Whether notifications pop up
* @prop {boolean} notification_sound Whether notification sounds are enabled
* @prop {Object} notification_count User's notifications
* @prop {number} notification_count.user User notifications
* @prop {number} notification_count.following Notifications from following accounts
* @prop {number} reputation User reputation
* @prop {number} following Number of following accounts
* @prop {number} followers Number of followers
* @prop {Object} notifications User's notifications
* @prop {number} notifications.user User notifications
* @prop {number} notifications.following Notification from following accounts
* @prop {number} max_user_language_reputation Maximum reputation in a language
* @prop {boolean} profile_data_filled Whether the profile is fully filled out
* @prop {boolean} is_corporation_user Whether the user is a corporation
* @prop {any} active_broker Active broker information (nullable)
* @prop {Array<any>} ignore_list List of ignored users
* @prop {boolean} is_active_partner Whether the user is an active partner
* @prop {boolean} is_broker Whether the user is a broker
* @prop {any} broker_plan User's broker plan (nullable)
* @prop {Array<any>} badges User's badges
* @prop {Object} permissions User's permissions
* @prop {boolean} is_symphony Whether the user is a Symphony member
* @prop {boolean} is_staff Whether the user is a staff member
* @prop {boolean} is_superuser Whether the user is a superuser
* @prop {boolean} is_moderator Whether the user is a moderator
* @prop {string} last_locale Last used locale
* @prop {boolean} social_registration Whether the user registered via social login
* @prop {boolean} has_phone Whether the user has a phone number linked
* @prop {string|null} sms_email SMS email (nullable)
* @prop {boolean} is_non_pro_confirmed Whether the user is confirmed as non-pro
* @prop {boolean} do_not_track Whether the user has opted out of tracking
* @prop {boolean} is_pro Whether the user has a Pro plan
* @prop {boolean} is_expert Whether the user is an expert
* @prop {boolean} is_trial Whether the user is on a trial plan
* @prop {boolean} is_lite_plan Whether the user is on a Lite plan
* @prop {boolean|null} pro_being_cancelled Whether the Pro plan is being canceled (nullable)
* @prop {number} pro_plan_days_left Days left on the Pro plan
* @prop {string} pro_plan_original_name Original name of the Pro plan
* @prop {string} pro_plan Current Pro plan type
* @prop {string} pro_plan_billing_cycle Billing cycle of the Pro plan
* @prop {number|null} trial_days_left Days left on the trial (nullable)
* @prop {string} trial_days_left_text Text representation of trial days left
* @prop {Object} available_offers Available offers for the user
* @prop {boolean} had_pro Whether the user had a Pro plan before
* @prop {string} declared_status User's declared status (e.g., "non_pro")
* @prop {string|null} declared_status_timestamp Timestamp of declared status update (nullable)
* @prop {string|null} market_profile_updated_timestamp Timestamp of last market profile update
* @prop {boolean} force_to_complete_data Whether the user is forced to complete their profile
* @prop {boolean} force_to_upgrade Whether the user is forced to upgrade their plan
* @prop {boolean} is_support_available Whether customer support is available for the user
* @prop {boolean} disallow_adding_to_private_chats user is restricted from private chats
* @prop {string} picture_url User's profile picture URL
*/

/**
* @typedef {Object} TwoFactorTypes Instance of User
* @prop {string} name 2fa detail message
* @prop {number} code_ttl 2fa dtaill code
*/

/**
* @typedef {Object} TwoFactorInfoMessage Instance of User
* @prop {string} detail 2fa detail message
* @prop {string} code 2fa dtaill code
* @prop {TwoFactorTypes[]} two_factor_types List of drawing points
*/

/**
* @typedef {Object} LoginResponse Instance of User
* @prop {string} session User session
* @prop {string} sessionHash User session hash
* @prop {string} signature User session signature
* @prop {string} privateChannel User private channel
* @prop {string} authToken User auth token
* @prop {Date} joinDate Account creation date
* @prop {User} [User] User Object
* @prop {TwoFactorInfoMessage} [two_factor_info] User Object
* @prop {TwoFactorTypes[]} two_factor_types 2fa info messages
*/

/**
Expand All @@ -464,7 +529,7 @@ module.exports = {
* @param {string} password User password
* @param {boolean} [remember] Remember the session (default: false)
* @param {string} [UA] Custom UserAgent
* @returns {Promise<User>} Token
* @returns {Promise<LoginResponse>} Login response
*/
async loginUser(username, password, remember = true, UA = 'TWAPI/3.0') {
const {
Expand All @@ -480,29 +545,88 @@ module.exports = {

const cookies = headers['set-cookie'];

if (data.error) throw new Error(data.error);
const sessionCookie = cookies?.find((c) => c.includes('sessionid='));
const session = (sessionCookie?.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];

const signCookie = cookies.find((c) => c.includes('sessionid_sign='));
const signature = (signCookie.match(/sessionid_sign=(.*?);/) ?? [])[1];
if (data.error) {
if (data.error.includes('2FA_required')) {
return {
session,
signature,
two_factor_info: {
...data,
},
};
}

throw new Error(data.error);
}

if (data.code?.includes('2FA_challenge_not_generated')) {
return {
two_factor_info: {
...data,
},
};
}

return {
id: data.user.id,
username: data.user.username,
firstName: data.user.first_name,
lastName: data.user.last_name,
reputation: data.user.reputation,
following: data.user.following,
followers: data.user.followers,
notifications: data.user.notification_count,
...data,
session,
signature,
sessionHash: data.user.session_hash,
privateChannel: data.user.private_channel,
authToken: data.user.auth_token,
joinDate: new Date(data.user.date_joined),
};
},

/**
* Get user and sessionid from username/email and password
* @function twoFactorAuth
* @param {string} code User username/email
* @param {string} session sission id
* @param {boolean} signature session sign
* @param {string} [twoFaType] 2fa type: "sms" | "totp"
* @param {string} [UA] Custom UserAgent
* @returns {Promise<LoginResponse>} Login response
*/
async twoFactorAuth(code, session, signature, twoFaType = 'sms', UA = 'TWAPI/3.0') {
const type = twoFaType === 'sms' ? 'sms' : 'totp';

const { data, headers } = await axios.post(
`https://www.tradingview.com/accounts/two-factor/signin/${type}/`,
`code=${code}`,
{
validateStatus,
headers: {
referer: 'https://www.tradingview.com',
'Content-Type': 'application/x-www-form-urlencoded',
'User-agent': `${UA} (${os.version()}; ${os.platform()}; ${os.arch()})`,
cookie: `sessionid=${session}${
signature ? `;sessionid_sign=${signature};` : ''
}`,
},
},
);

if (data.code) {
return {
two_factor_info: {
...data,
},
};
}

const cookies = headers['set-cookie'];

const sessionCookie = cookies.find((c) => c.includes('sessionid='));

const signCookie = cookies.find((c) => c.includes('sessionid_sign='));

return {
session: (sessionCookie.match(/sessionid=(.*?);/) ?? [])[1],
signature: (signCookie.match(/sessionid_sign=(.*?);/) ?? [])[1],
...data,
};
},

Expand Down Expand Up @@ -790,9 +914,9 @@ module.exports = {
return JSON.parse(match[1]);
} catch (e) {
console.error(e);
throw new Error("Failed to to parse 'initData.content' data.");
throw new Error('Failed to to parse \'initData.content\' data.');
}
} else throw new Error("Failed to find 'content' property on 'initData' object.");
} else throw new Error('Failed to find \'content\' property on \'initData\' object.');
},

/**
Expand Down