From 83e8e76f5f011a4e11746edacff7724526c9989f Mon Sep 17 00:00:00 2001 From: Martin Scharrer Date: Thu, 14 Mar 2024 14:23:56 +0100 Subject: [PATCH 1/8] Added check if given phone number is identical to the one in credential file and start a new websession if so. Added command line argument to manage saveing of the credential file without asking. --- pytr/account.py | 38 +++++++++++++++++++++++++++----------- pytr/main.py | 11 ++++++----- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/pytr/account.py b/pytr/account.py index 28d6863..400b1ab 100644 --- a/pytr/account.py +++ b/pytr/account.py @@ -16,7 +16,7 @@ def get_settings(tr): return formatted_json -def login(phone_no=None, pin=None, web=True): +def login(phone_no=None, pin=None, web=True, save_credentials=None): ''' If web is true, use web login method as else simulate app login. Check if credentials file exists else create it. @@ -24,37 +24,53 @@ def login(phone_no=None, pin=None, web=True): ''' log = get_logger(__name__) save_cookies = True + save_credentials = (save_credentials == 'y') - if phone_no is None and CREDENTIALS_FILE.is_file(): + if CREDENTIALS_FILE.is_file(): log.info('Found credentials file') with open(CREDENTIALS_FILE) as f: lines = f.readlines() - phone_no = lines[0].strip() - pin = lines[1].strip() - phone_no_masked = phone_no[:-8] + '********' - pin_masked = len(pin) * '*' + phone_no_cf = lines[0].strip() + pin_cf = lines[1].strip() + phone_no_masked = phone_no_cf[:-8] + '********' + pin_masked = len(pin_cf) * '*' log.info(f'Phone: {phone_no_masked}, PIN: {pin_masked}') else: + phone_no_cf = None + pin_cf = None + + ask_for_save = False + different_account = False + if phone_no is not None and phone_no_cf is not None and phone_no != phone_no_cf: + log.info('Phone number different from credential files. Assuming different account.') + different_account = True + ask_for_save = True + + if phone_no is None and phone_no_cf is None: CREDENTIALS_FILE.parent.mkdir(parents=True, exist_ok=True) if phone_no is None: log.info('Credentials file not found') print('Please enter your TradeRepublic phone number in the format +4912345678:') phone_no = input() + ask_for_save = True else: log.info('Phone number provided as argument') if pin is None: print('Please enter your TradeRepublic pin:') pin = input() + ask_for_save = True - print('Save credentials? Type "y" to save credentials:') - save = input() - if save == 'y': + if save_credentials or ask_for_save: + save = save_credentials + if save_credentials is None: + print('Save credentials? Type "y" to save credentials:') + save = input() == 'y' + if save: with open(CREDENTIALS_FILE, 'w') as f: f.writelines([phone_no + '\n', pin + '\n']) log.info(f'Saved credentials in {CREDENTIALS_FILE}') - else: save_cookies = False log.info('Credentials not saved') @@ -63,7 +79,7 @@ def login(phone_no=None, pin=None, web=True): if web: # Use same login as app.traderepublic.com - if tr.resume_websession(): + if not different_account and tr.resume_websession(): log.info('Web session resumed') else: try: diff --git a/pytr/main.py b/pytr/main.py index 43b6f03..6c63db9 100644 --- a/pytr/main.py +++ b/pytr/main.py @@ -50,6 +50,7 @@ def formatter(prog): parser_login_args.add_argument('--applogin', help='Use app login instead of web login', action='store_true') parser_login_args.add_argument('-n', '--phone_no', help='TradeRepublic phone number (international format)') parser_login_args.add_argument('-p', '--pin', help='TradeRepublic pin') + parser_login_args.add_argument('-s', '--save_credentials', choices=['y', 'n'], help='Save credentials') # login info = ( @@ -189,7 +190,7 @@ def main(): log.debug('logging is set to debug') if args.command == 'login': - login(phone_no=args.phone_no, pin=args.pin, web=not args.applogin) + login(phone_no=args.phone_no, pin=args.pin, web=not args.applogin, save_credentials=args.save_credentials) elif args.command == 'dl_docs': if args.last_days == 0: @@ -198,7 +199,7 @@ def main(): since_timestamp = (time.time() - (24 * 3600 * args.last_days)) * 1000 dl = DL( - login(phone_no=args.phone_no, pin=args.pin, web=not args.applogin), + login(phone_no=args.phone_no, pin=args.pin, web=not args.applogin, save_credentials=args.save_credentials), args.output, args.format, since_timestamp=since_timestamp, @@ -210,11 +211,11 @@ def main(): # TODO print('Not implemented yet') elif args.command == 'get_price_alarms': - Alarms(login(phone_no=args.phone_no, pin=args.pin, web=not args.applogin)).get() + Alarms(login(phone_no=args.phone_no, pin=args.pin, web=not args.applogin, save_credentials=args.save_credentials)).get() elif args.command == 'details': - Details(login(phone_no=args.phone_no, pin=args.pin, web=not args.applogin), args.isin).get() + Details(login(phone_no=args.phone_no, pin=args.pin, web=not args.applogin, save_credentials=args.save_credentials), args.isin).get() elif args.command == 'portfolio': - Portfolio(login(phone_no=args.phone_no, pin=args.pin, web=not args.applogin)).get() + Portfolio(login(phone_no=args.phone_no, pin=args.pin, web=not args.applogin, save_credentials=args.save_credentials)).get() elif args.command == 'export_transactions': export_transactions(args.input, args.output, args.lang) elif args.version: From a25f887b384df649b316c28dc035db45b48e5f33 Mon Sep 17 00:00:00 2001 From: Martin Scharrer Date: Thu, 14 Mar 2024 14:32:38 +0100 Subject: [PATCH 2/8] Added handling of invalid cookie file. --- pytr/api.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pytr/api.py b/pytr/api.py index bbd9da1..3172377 100644 --- a/pytr/api.py +++ b/pytr/api.py @@ -226,7 +226,12 @@ def resume_websession(self): # Only attempt to load if the cookie file exists. if COOKIES_FILE.exists(): # Loads session cookies too (expirydate=0). - self._websession.cookies.load(ignore_discard=True, ignore_expires=True) + try: + self._websession.cookies.load(ignore_discard=True, ignore_expires=True) + except: + # cookie file invalid. Delete it. + COOKIES_FILE.unlink() + return False self._weblogin = True try: self.settings() From 893c9922838bd6e51ef7530a2886984014c82ee7 Mon Sep 17 00:00:00 2001 From: Martin Scharrer Date: Thu, 4 Apr 2024 10:21:45 +0200 Subject: [PATCH 3/8] Changed save_credentials to bool. --- pytr/account.py | 1 - pytr/main.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pytr/account.py b/pytr/account.py index 400b1ab..9eb05e6 100644 --- a/pytr/account.py +++ b/pytr/account.py @@ -24,7 +24,6 @@ def login(phone_no=None, pin=None, web=True, save_credentials=None): ''' log = get_logger(__name__) save_cookies = True - save_credentials = (save_credentials == 'y') if CREDENTIALS_FILE.is_file(): log.info('Found credentials file') diff --git a/pytr/main.py b/pytr/main.py index 4f3ac09..ced8e2b 100644 --- a/pytr/main.py +++ b/pytr/main.py @@ -50,7 +50,7 @@ def formatter(prog): parser_login_args.add_argument('--applogin', help='Use app login instead of web login', action='store_true') parser_login_args.add_argument('-n', '--phone_no', help='TradeRepublic phone number (international format)') parser_login_args.add_argument('-p', '--pin', help='TradeRepublic pin') - parser_login_args.add_argument('-s', '--save_credentials', choices=['y', 'n'], help='Save credentials') + parser_login_args.add_argument('-s', '--save_credentials', action='store_true', help='Save credentials') # login info = ( From da395d9083af942db55b759775233f449eb8d48d Mon Sep 17 00:00:00 2001 From: Martin Scharrer Date: Thu, 4 Apr 2024 10:28:37 +0200 Subject: [PATCH 4/8] Removed ask_for_save. Credentials are only saved now if save_credentials is used as command line argument. --- pytr/account.py | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/pytr/account.py b/pytr/account.py index 9eb05e6..5f903d2 100644 --- a/pytr/account.py +++ b/pytr/account.py @@ -38,12 +38,10 @@ def login(phone_no=None, pin=None, web=True, save_credentials=None): phone_no_cf = None pin_cf = None - ask_for_save = False different_account = False if phone_no is not None and phone_no_cf is not None and phone_no != phone_no_cf: log.info('Phone number different from credential files. Assuming different account.') different_account = True - ask_for_save = True if phone_no is None and phone_no_cf is None: CREDENTIALS_FILE.parent.mkdir(parents=True, exist_ok=True) @@ -51,28 +49,20 @@ def login(phone_no=None, pin=None, web=True, save_credentials=None): log.info('Credentials file not found') print('Please enter your TradeRepublic phone number in the format +4912345678:') phone_no = input() - ask_for_save = True else: log.info('Phone number provided as argument') if pin is None: print('Please enter your TradeRepublic pin:') pin = input() - ask_for_save = True - if save_credentials or ask_for_save: - save = save_credentials - if save_credentials is None: - print('Save credentials? Type "y" to save credentials:') - save = input() == 'y' - if save: - with open(CREDENTIALS_FILE, 'w') as f: - f.writelines([phone_no + '\n', pin + '\n']) - - log.info(f'Saved credentials in {CREDENTIALS_FILE}') - else: - save_cookies = False - log.info('Credentials not saved') + if save_credentials: + with open(CREDENTIALS_FILE, 'w') as f: + f.writelines([phone_no + '\n', pin + '\n']) + log.info(f'Saved credentials in {CREDENTIALS_FILE}') + else: + save_cookies = False + log.info('Credentials not saved') tr = TradeRepublicApi(phone_no=phone_no, pin=pin, save_cookies=save_cookies) From d662147e37e7c70b05212f8442fbe96e4ce91f1e Mon Sep 17 00:00:00 2001 From: Martin Scharrer Date: Thu, 4 Apr 2024 10:35:15 +0200 Subject: [PATCH 5/8] Added credentials_file and cookies_file as optional arguments to login(). They are then also passed to TradeRepublicApi(). --- pytr/account.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pytr/account.py b/pytr/account.py index 5f903d2..92c03f5 100644 --- a/pytr/account.py +++ b/pytr/account.py @@ -3,7 +3,7 @@ from pygments import highlight, lexers, formatters import time -from pytr.api import TradeRepublicApi, CREDENTIALS_FILE +from pytr.api import TradeRepublicApi, CREDENTIALS_FILE, COOKIES_FILE from pytr.utils import get_logger @@ -16,7 +16,8 @@ def get_settings(tr): return formatted_json -def login(phone_no=None, pin=None, web=True, save_credentials=None): +def login(phone_no=None, pin=None, web=True, save_credentials=None, + credentials_file=CREDENTIALS_FILE, cookies_file=COOKIES_FILE): ''' If web is true, use web login method as else simulate app login. Check if credentials file exists else create it. @@ -25,9 +26,9 @@ def login(phone_no=None, pin=None, web=True, save_credentials=None): log = get_logger(__name__) save_cookies = True - if CREDENTIALS_FILE.is_file(): + if credentials_file.is_file(): log.info('Found credentials file') - with open(CREDENTIALS_FILE) as f: + with open(credentials_file) as f: lines = f.readlines() phone_no_cf = lines[0].strip() pin_cf = lines[1].strip() @@ -44,7 +45,7 @@ def login(phone_no=None, pin=None, web=True, save_credentials=None): different_account = True if phone_no is None and phone_no_cf is None: - CREDENTIALS_FILE.parent.mkdir(parents=True, exist_ok=True) + credentials_file.parent.mkdir(parents=True, exist_ok=True) if phone_no is None: log.info('Credentials file not found') print('Please enter your TradeRepublic phone number in the format +4912345678:') @@ -57,14 +58,15 @@ def login(phone_no=None, pin=None, web=True, save_credentials=None): pin = input() if save_credentials: - with open(CREDENTIALS_FILE, 'w') as f: + with open(credentials_file, 'w') as f: f.writelines([phone_no + '\n', pin + '\n']) - log.info(f'Saved credentials in {CREDENTIALS_FILE}') + log.info(f'Saved credentials in {credentials_file}') else: save_cookies = False log.info('Credentials not saved') - tr = TradeRepublicApi(phone_no=phone_no, pin=pin, save_cookies=save_cookies) + tr = TradeRepublicApi(phone_no=phone_no, pin=pin, save_cookies=save_cookies, + credentials_file=credentials_file, cookies_file=cookies_file) if web: # Use same login as app.traderepublic.com From 9632aea4956297889ca89576be4598b707335c37 Mon Sep 17 00:00:00 2001 From: Martin Scharrer Date: Thu, 4 Apr 2024 10:48:02 +0200 Subject: [PATCH 6/8] Added command line arguments for credential and cookie files. --- pytr/account.py | 5 +++-- pytr/main.py | 15 ++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/pytr/account.py b/pytr/account.py index 92c03f5..9b3aba5 100644 --- a/pytr/account.py +++ b/pytr/account.py @@ -16,8 +16,7 @@ def get_settings(tr): return formatted_json -def login(phone_no=None, pin=None, web=True, save_credentials=None, - credentials_file=CREDENTIALS_FILE, cookies_file=COOKIES_FILE): +def login(phone_no=None, pin=None, web=True, save_credentials=False, credentials_file=None, cookies_file=None): ''' If web is true, use web login method as else simulate app login. Check if credentials file exists else create it. @@ -25,6 +24,8 @@ def login(phone_no=None, pin=None, web=True, save_credentials=None, ''' log = get_logger(__name__) save_cookies = True + if credentials_file is None: + credentials_file = CREDENTIALS_FILE if credentials_file.is_file(): log.info('Found credentials file') diff --git a/pytr/main.py b/pytr/main.py index ced8e2b..7c2a5b2 100644 --- a/pytr/main.py +++ b/pytr/main.py @@ -51,6 +51,8 @@ def formatter(prog): parser_login_args.add_argument('-n', '--phone_no', help='TradeRepublic phone number (international format)') parser_login_args.add_argument('-p', '--pin', help='TradeRepublic pin') parser_login_args.add_argument('-s', '--save_credentials', action='store_true', help='Save credentials') + parser_login_args.add_argument('-c', '--credentials', help='Storage file for credentials') + parser_login_args.add_argument('-k', '--cookies', help='Storage file for cookies') # login info = ( @@ -191,9 +193,12 @@ def main(): log = get_logger(__name__, args.verbosity) log.setLevel(args.verbosity.upper()) log.debug('logging is set to debug') + login_kwargs = dict(phone_no=args.phone_no, pin=args.pin, web=not args.applogin, + save_credentials=args.save_credentials, credentials_file=args.credentials, + cookies_file=args.cookies) if args.command == 'login': - login(phone_no=args.phone_no, pin=args.pin, web=not args.applogin, save_credentials=args.save_credentials) + login(**login_kwargs) elif args.command == 'dl_docs': if args.last_days == 0: @@ -202,7 +207,7 @@ def main(): since_timestamp = (time.time() - (24 * 3600 * args.last_days)) * 1000 dl = DL( - login(phone_no=args.phone_no, pin=args.pin, web=not args.applogin, save_credentials=args.save_credentials), + login(**login_kwargs), args.output, args.format, since_timestamp=since_timestamp, @@ -214,11 +219,11 @@ def main(): # TODO print('Not implemented yet') elif args.command == 'get_price_alarms': - Alarms(login(phone_no=args.phone_no, pin=args.pin, web=not args.applogin, save_credentials=args.save_credentials)).get() + Alarms(login(login(**login_kwargs))).get() elif args.command == 'details': - Details(login(phone_no=args.phone_no, pin=args.pin, web=not args.applogin, save_credentials=args.save_credentials), args.isin).get() + Details(login(login(**login_kwargs)), args.isin).get() elif args.command == 'portfolio': - p = Portfolio(login(phone_no=args.phone_no, pin=args.pin, web=not args.applogin, save_credentials=args.save_credentials)) + p = Portfolio(login(login(**login_kwargs))) p.get() if args.output is not None: p.portfolio_to_csv(args.output) From 06864b90f841962d9fa13f5a134e3a830f2c2f63 Mon Sep 17 00:00:00 2001 From: Martin Scharrer Date: Thu, 4 Apr 2024 11:21:20 +0200 Subject: [PATCH 7/8] Changed credentials_file argument to Path object if given. --- pytr/account.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pytr/account.py b/pytr/account.py index 9b3aba5..1bd5829 100644 --- a/pytr/account.py +++ b/pytr/account.py @@ -2,6 +2,7 @@ import sys from pygments import highlight, lexers, formatters import time +import pathlib from pytr.api import TradeRepublicApi, CREDENTIALS_FILE, COOKIES_FILE from pytr.utils import get_logger @@ -23,9 +24,7 @@ def login(phone_no=None, pin=None, web=True, save_credentials=False, credentials If no parameters are set but are needed then ask for input ''' log = get_logger(__name__) - save_cookies = True - if credentials_file is None: - credentials_file = CREDENTIALS_FILE + credentials_file = pathlib.Path(credentials_file) if credentials_file else CREDENTIALS_FILE if credentials_file.is_file(): log.info('Found credentials file') From b6fff94edff03775eaf9fd324d0f55cb61ee1b48 Mon Sep 17 00:00:00 2001 From: Martin Scharrer Date: Thu, 4 Apr 2024 11:22:04 +0200 Subject: [PATCH 8/8] Corrected save_cookies handling on different account. --- pytr/account.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pytr/account.py b/pytr/account.py index 1bd5829..f7c75d6 100644 --- a/pytr/account.py +++ b/pytr/account.py @@ -17,7 +17,7 @@ def get_settings(tr): return formatted_json -def login(phone_no=None, pin=None, web=True, save_credentials=False, credentials_file=None, cookies_file=None): +def login(phone_no=None, pin=None, web=True, save_credentials=False, credentials_file=None, save_cookies=True, cookies_file=None): ''' If web is true, use web login method as else simulate app login. Check if credentials file exists else create it. @@ -62,7 +62,8 @@ def login(phone_no=None, pin=None, web=True, save_credentials=False, credentials f.writelines([phone_no + '\n', pin + '\n']) log.info(f'Saved credentials in {credentials_file}') else: - save_cookies = False + if different_account: + save_cookies = False log.info('Credentials not saved') tr = TradeRepublicApi(phone_no=phone_no, pin=pin, save_cookies=save_cookies,