From cd2ab2a87fe8e4f9e0bdcd9d7b2a0d55c02d0359 Mon Sep 17 00:00:00 2001 From: Joe Trader Date: Tue, 26 Sep 2023 17:11:00 +1000 Subject: [PATCH 1/4] Added basic 2FA access --- tvDatafeed/main.py | 60 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/tvDatafeed/main.py b/tvDatafeed/main.py index de77ebc..b08d8fe 100644 --- a/tvDatafeed/main.py +++ b/tvDatafeed/main.py @@ -9,10 +9,15 @@ from websocket import create_connection import requests import json +from pathlib import Path +from appdirs import user_data_dir logger = logging.getLogger(__name__) +tokendata = Path(user_data_dir(appname="tvdata", appauthor=""), "token.2fa") +tokendata.parent.mkdir(parents=True, exist_ok=True) + class Interval(enum.Enum): in_1_minute = "1" in_3_minute = "3" @@ -31,6 +36,7 @@ class Interval(enum.Enum): class TvDatafeed: __sign_in_url = 'https://www.tradingview.com/accounts/signin/' + __sign_in_totp = 'https://www.tradingview.com/accounts/two-factor/signin/totp/' __search_url = 'https://symbol-search.tradingview.com/symbol_search/?text={}&hl=1&exchange={}&lang=en&type=&domain=production' __ws_headers = json.dumps({"Origin": "https://data.tradingview.com"}) __signin_headers = {'Referer': 'https://www.tradingview.com'} @@ -63,24 +69,48 @@ def __init__( self.chart_session = self.__generate_chart_session() def __auth(self, username, password): - - if (username is None or password is None): - token = None - - else: - data = {"username": username, - "password": password, - "remember": "on"} - try: - response = requests.post( - url=self.__sign_in_url, data=data, headers=self.__signin_headers) - token = response.json()['user']['auth_token'] - except Exception as e: - logger.error('error while signin') + + try: + with open(tokendata, 'r') as f: + token = f.read() + except IOError: + if (username is None or password is None): token = None + else: + data = {"username": username, + "password": password, + "remember": "on"} + try: + with requests.Session() as s: + response = s.post(url=self.__sign_in_url, data=data, headers=self.__signin_headers) + # '{"error":"2FA_required","code":"2FA_required","message":"Second authentication factor is needed","two_factor_types":[{"name":"totp"}]}' + if "2FA_required" in response.text: + response = s.post(url=self.__sign_in_totp, data={"code": self.__getcode()}, headers=self.__signin_headers) + token = response.json()['user']['auth_token'] + with open(tokendata, 'w') as f: + f.write(token) + else: + token = response.json()['user']['auth_token'] + + except Exception as e: + logger.error('error while signin') + token = None + return token + @staticmethod + def __getcode(): + print("Asking user for 2FA code") + code = input("Enter 2FA code: ") + return int(code) + + @staticmethod + def __delete_token(): + self.token = None + tokendata.unlink() + raise Exception("error with token - exiting") + def __create_connection(self): logging.debug("creating websocket connection") self.ws = create_connection( @@ -281,6 +311,7 @@ def get_hist( result = self.ws.recv() raw_data = raw_data + result + "\n" except Exception as e: + self.__delete_token() logger.error(e) break @@ -299,6 +330,7 @@ def search_symbol(self, text: str, exchange: str = ''): symbols_list = json.loads(resp.text.replace( '', '').replace('', '')) except Exception as e: + self.__delete_token() logger.error(e) return symbols_list From 4f65711094dc3fc7e5bad6125bf38e5fa4e3366d Mon Sep 17 00:00:00 2001 From: Joe Trader Date: Tue, 26 Sep 2023 21:16:01 +1000 Subject: [PATCH 2/4] Added Pro Data Download --- tvDatafeed/main.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tvDatafeed/main.py b/tvDatafeed/main.py index b08d8fe..7469b45 100644 --- a/tvDatafeed/main.py +++ b/tvDatafeed/main.py @@ -39,13 +39,15 @@ class TvDatafeed: __sign_in_totp = 'https://www.tradingview.com/accounts/two-factor/signin/totp/' __search_url = 'https://symbol-search.tradingview.com/symbol_search/?text={}&hl=1&exchange={}&lang=en&type=&domain=production' __ws_headers = json.dumps({"Origin": "https://data.tradingview.com"}) + __ws_proheaders = json.dumps({"Origin": "https://prodata.tradingview.com"}) __signin_headers = {'Referer': 'https://www.tradingview.com'} - __ws_timeout = 5 + __ws_timeout = 10 def __init__( self, username: str = None, password: str = None, + pro: bool = False, ) -> None: """Create TvDatafeed object @@ -56,6 +58,8 @@ def __init__( self.ws_debug = False + self.pro = pro + self.token = self.__auth(username, password) if self.token is None: @@ -113,9 +117,10 @@ def __delete_token(): def __create_connection(self): logging.debug("creating websocket connection") - self.ws = create_connection( - "wss://data.tradingview.com/socket.io/websocket", headers=self.__ws_headers, timeout=self.__ws_timeout - ) + if self.pro: + self.ws = create_connection("wss://prodata.tradingview.com/socket.io/websocket", headers=self.__ws_proheaders, timeout=self.__ws_timeout) + else: + self.ws = create_connection("wss://data.tradingview.com/socket.io/websocket", headers=self.__ws_headers, timeout=self.__ws_timeout) @staticmethod def __filter_raw_message(text): From 21e12f38bf9565e924b2428278a03b760e51dfc9 Mon Sep 17 00:00:00 2001 From: Joe Trader Date: Wed, 27 Sep 2023 21:53:42 +1000 Subject: [PATCH 3/4] Added websocket timeout handing --- tvDatafeed/main.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tvDatafeed/main.py b/tvDatafeed/main.py index 7469b45..02f35a4 100644 --- a/tvDatafeed/main.py +++ b/tvDatafeed/main.py @@ -6,7 +6,7 @@ import re import string import pandas as pd -from websocket import create_connection +from websocket import create_connection, WebSocketTimeoutException import requests import json from pathlib import Path @@ -47,7 +47,7 @@ def __init__( self, username: str = None, password: str = None, - pro: bool = False, + pro: bool =False ) -> None: """Create TvDatafeed object @@ -111,8 +111,8 @@ def __getcode(): @staticmethod def __delete_token(): - self.token = None tokendata.unlink() + self.token = None raise Exception("error with token - exiting") def __create_connection(self): @@ -315,6 +315,9 @@ def get_hist( try: result = self.ws.recv() raw_data = raw_data + result + "\n" + except WebSocketTimeoutException as e: + logger.error(e) + break except Exception as e: self.__delete_token() logger.error(e) From 49a7eed1f405346fada73de687aa0df3a8580689 Mon Sep 17 00:00:00 2001 From: Joe Trader Date: Wed, 27 Sep 2023 22:27:12 +1000 Subject: [PATCH 4/4] Fixed erronous delete token --- tvDatafeed/main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tvDatafeed/main.py b/tvDatafeed/main.py index 02f35a4..c3d5986 100644 --- a/tvDatafeed/main.py +++ b/tvDatafeed/main.py @@ -338,7 +338,6 @@ def search_symbol(self, text: str, exchange: str = ''): symbols_list = json.loads(resp.text.replace( '', '').replace('', '')) except Exception as e: - self.__delete_token() logger.error(e) return symbols_list