diff --git a/.env.example b/.env.example index b49327f..5d74ab7 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,7 @@ -OUISNCF_COOKIE= -# Example : "cookie-name=cookie-value; cookie-name2=cookie-value2" TGVMAX_CARD_NUMBER= # Card number with "HC" prefix included. Example : HCXXXXXXXXX BIRTH_DATE= # FR/UK/ES format : YYYY-MM-DD Example : 2000-01-31 -OUISNCF_STATS_COOKIE='not mandatory in classic use' -# Example : "cookie-name=cookie-value; cookie-name2=cookie-value2" +REAUTHENTICATE= +# Exemple : REAUTHENTICATE='__Secure-refresh-account-token= SOMETHING; datadome=SOMETHING' + You need to find the value field in your cookies !! diff --git a/README.md b/README.md index 7a66564..3d72a16 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,11 @@ ## Setup Create and fill a `.env` file with your cookies contents, your birthdate and your TGVMax card number. + +The two important cookies are : + - __Secure-refresh-account-token --> It allows you to authentificate + - datadome=SOMETHING' --> I don't know but seems to be important to avoid captcha +
Tutorial to get your oui.sncf cookies. Reproduce it once connected. diff --git a/config.py b/config.py index dd96303..e4f22c0 100644 --- a/config.py +++ b/config.py @@ -3,8 +3,25 @@ from dotenv import load_dotenv + load_dotenv() +def dict_cookie_from_str(str_cookies): + d_cookies = {} + l_cookies = str_cookies.split("; ") + for key_item in l_cookies: + i = key_item.find("=") + key = key_item[:i] + item = key_item[i+1:] + d_cookies[key] = item + return d_cookies + +def str_cookies_from_dict(d_cookies): + str_cookie = "" + for key, item in d_cookies.items(): + str_cookie += key + "="+ item + "; " + str_cookie = str_cookie[:-2] + return str_cookie class AppConfigError(Exception): """ @@ -21,11 +38,9 @@ class AppConfig: Application configuration. """ - OUISNCF_COOKIE: str TGVMAX_CARD_NUMBER: str BIRTH_DATE: str - OUISNCF_STATS_COOKIE: str - + REAUTHENTICATE: str """ Map environment variables to class fields according to these rules: - Field won't be parsed unless it has a type annotation @@ -56,9 +71,29 @@ def __init__(self, env): except ValueError: raise AppConfigError(f'Unable to cast value of "{env[field]}" ' f'to type "{var_type}" for "{field}" field') from None + self.write_temp_env() def __repr__(self): return str(self.__dict__) + + def update_cookies_from_dict(self, field, dict_cookies): + str_old = self.__dict__[field] + d_new_cookies = dict_cookie_from_str(str_old) + for key, value in dict_cookies.items(): + d_new_cookies[key] = value + self.__dict__[field] = str_cookies_from_dict(d_new_cookies) + self.write_temp_env() + + def write_temp_env(self): + f = open("./.env", "w") + f.close() + f = open("./.env", "a") + for field in AppConfig.__annotations__: + str_write = field + value = self.__getattribute__(field) + str_write += "='" + value + "'" + f.write(str_write + "\n") + f.close() # Expose Config object for app to import diff --git a/proposal.py b/proposal.py index 5db6da7..c6d8a9e 100644 --- a/proposal.py +++ b/proposal.py @@ -47,7 +47,7 @@ def parse_intercites_de_nuit(second_class_offers: any) -> dict: """ remaining = {} for offer in second_class_offers: - if float(offer['priceLabel'].split(' ')[0]) == 0: + if float(offer['priceLabel'].split(' ')[0].replace(",", ".") ) == 0: for message in offer['messages']: physical_space = offer['comfortClass']['physicalSpaceLabel'] if 'Plus que' in message['message']: @@ -70,13 +70,35 @@ def get_next(dpt_station, arr_station, dpt_date, verbosity) -> requests.Response """ headers = { - 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36', - 'x-bff-key': 'ah1MPO-izehIHD-QZZ9y88n-kku876', - 'x-market-locale': 'fr_FR', - 'x-nav-current-path': '/app/en-en/home/shop/results/outward', - 'cookie': Config.OUISNCF_COOKIE, + 'Host': 'www.sncf-connect.com', + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:105.0) Gecko/20100101 Firefox/105.0', + 'Accept': 'application/json, text/plain, */*', 'Accept-Language': 'fr,en-US;q=0.7,en;q=0.3', 'Accept-Encoding': 'gzip, deflate, br', + 'x-bff-key': 'ah1MPO-izehIHD-QZZ9y88n-kku876', 'x-client-channel': 'web', 'x-client-app-id': 'front-web', + 'x-api-env': 'production', 'x-market-locale': 'fr_FR', + 'x-email-hidden': '358C5C06A9357683EAF0119515C81AAE0A942BB5', + 'x-email-strong': '9b0b6129b4c95891f8965bc7ad8d537ae9a1c71f3fa26da516c246afd17b765e', + 'x-email-stronger': '860f0711e955b6bdb08000f7b4cba8c6b4eda78b5eb14e2bafae05510838f055', + 'x-con-s': 'CPfztUAPfztUAAHABBENChCgAAAAAAAAAAAAAAAAAAEDoAMAAQSAIQAYAAgkAUgAwABBIANABgACCQAqADAAEEgBEAGAAIJABIAMAAQSAGQAYAAgkAAA.YAAAAAAAAAAA', + 'x-con-id': 'fbac099fd2cccb74601b3c50ca1f697e78f', 'x-con-ex': 'fbab7d18833206847f893fb8a0d355225a5', 'x-app-version': '20220903.0.0-2022090300-641abe9ff5', + 'x-device-os-version': 'Linux (x86_64)', 'x-device-class': 'desktop', + 'x-attribution-referrer': 'https://www.sncf-connect.com/', + 'x-nav-previous-page': 'Homepage', 'x-nav-current-path': '/app/home/search', + 'x-visitor-type': '1', 'Origin': 'https://www.sncf-connect.com', 'DNT': '1', + 'Connection': 'keep-alive', 'Referer': 'https://www.sncf-connect.com/app/home/search?destinationLabel=Paris&destinationId=CITY_FR_6455259', + 'Sec-Fetch-Dest': 'empty', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Site': 'same-origin', 'Pragma': 'no-cache', 'Cache-Control': 'no-cache', 'Content-Length': '0' } + #### reauthentification ###### + session = requests.Session() + headers["Cookie"] = Config.REAUTHENTICATE + response = session.post("https://www.sncf-connect.com/bff/api/v1/web-refresh/reauthenticate", + headers=headers) + if response.status_code != 200: + print("__Secure-refresh-account-tokem in REAUTHENTICATE in the .env file is misconfigured ! ") + Config.update_cookies_from_dict("REAUTHENTICATE", session.cookies.get_dict()) + + headers["Cookie"] = Config.REAUTHENTICATE + data = { 'schedule': { 'outward': { @@ -104,7 +126,7 @@ def get_next(dpt_station, arr_station, dpt_date, verbosity) -> requests.Response 'discountCards': [ { 'code': 'HAPPY_CARD', - 'number': 'HC700678060', + 'number': Config.TGVMAX_CARD_NUMBER, 'label': 'MAX JEUNE', }, { @@ -130,8 +152,10 @@ def get_next(dpt_station, arr_station, dpt_date, verbosity) -> requests.Response 'strictMode': False, } - response = requests.post('https://www.sncf-connect.com/bff/api/v1/itineraries', + response = session.post('https://www.sncf-connect.com/bff/api/v1/itineraries', headers=headers, json=data) + Config.update_cookies_from_dict("REAUTHENTICATE", session.cookies.get_dict()) + if response.status_code == 404: return False elif response.status_code != 200: @@ -139,8 +163,7 @@ def get_next(dpt_station, arr_station, dpt_date, verbosity) -> requests.Response if verbosity: print(response.text) if response.status_code == 403: - print('Too many requests. Resolve captcha at ' - 'https://oui.sncf/billet-train and recover your new cookies') + print(response.text) sys_exit('Error in the request to get proposal') return response diff --git a/setup.py b/setup.py index d3fddc9..5551c76 100644 --- a/setup.py +++ b/setup.py @@ -7,4 +7,5 @@ description='Python Distribution Utilities', author='Divulgacheur', author_email='github@theopeltier.me', + packages=['distutils', 'requests', 'dotenv'], )