From 8625622002ec342acea9908c62a8c72b510bda58 Mon Sep 17 00:00:00 2001 From: augustin64 Date: Fri, 1 Dec 2023 19:07:59 +0100 Subject: [PATCH] Initial commit --- .gitignore | 4 ++ requirements.txt | 1 + src/classes.py | 45 ++++++++++++++++++++++ src/example_config.py | 12 ++++++ src/main.py | 88 +++++++++++++++++++++++++++++++++++++++++++ src/utils.py | 61 ++++++++++++++++++++++++++++++ 6 files changed, 211 insertions(+) create mode 100644 .gitignore create mode 100644 requirements.txt create mode 100644 src/classes.py create mode 100644 src/example_config.py create mode 100644 src/main.py create mode 100644 src/utils.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5bbd428 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +archive +har +src/config.py +**/__pycache__ \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..663bd1f --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +requests \ No newline at end of file diff --git a/src/classes.py b/src/classes.py new file mode 100644 index 0000000..52a1a80 --- /dev/null +++ b/src/classes.py @@ -0,0 +1,45 @@ +from utils import raiseDiscord + +class Travel(): + def __init__(self, data): + self.orderId = data["orderId"] + self.serviceItemId = data["serviceItemId"] + self.dvNumber = data["dvNumber"] + self.origin = data["origin"] + self.destination = data["destination"] + self.departureDateTime = data["departureDateTime"] + self.arrivalDateTime = data["arrivalDateTime"] + + self.travelClass = data["travelClass"] + self.trainNumber = data["trainNumber"] + self.coachNumber = data["coachNumber"] + self.seatNumber = data["seatNumber"] + + self.reservationDate = data["reservationDate"] + self.travelConfirmed = data["travelConfirmed"] # CONFIRMED, TOO_EARLY_TO_CONFIRM + self.travelStatus = data["travelStatus"] + + def confirm(self, session, headers): + """ + https://www.maxjeune-tgvinoui.sncf/api/public/reservation/travel-confirm + {"marketingCarrierRef":"{Référence}","trainNumber":"{trainNumber}","departureDateTime":"2023-01-01T01:01:00.000"} + """ + r = session.post( + "https://www.maxjeune-tgvinoui.sncf/api/public/reservation/travel-confirm", + headers=headers, + json={ + "marketingCarrierRef": self.dvNumber, + "trainNumber": self.trainNumber, + "departureDateTime": self.departureDateTime + } + ) + if (r.status_code >= 300 or r.status_code < 200): + raiseDiscord( + "An error occurred during travel auto confirmation", + travel=self, + req=r + ) + + return r + + diff --git a/src/example_config.py b/src/example_config.py new file mode 100644 index 0000000..4e737f9 --- /dev/null +++ b/src/example_config.py @@ -0,0 +1,12 @@ +# Numéro de carte TGVMax, 17 chiffres +card_number="..." + +# Change très fréquemment (1 fois par heure ?) +authorization = "Bearer ..." + +cookies = "didomi_token=...; euconsent-v2=...; sticky__authentication=...; _dd_s=..." + + +# Discord Webhooks +error_url = "https://discord.com/api/webhooks/..." +success_url = "https://discord.com/api/webhooks/..." \ No newline at end of file diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..426f14c --- /dev/null +++ b/src/main.py @@ -0,0 +1,88 @@ +import json +import requests +from datetime import datetime + +import utils +import config +from classes import Travel + +""" +https://www.maxjeune-tgvinoui.sncf/api/public/subscription/summary +{"cardNumber": {CARDNUMBER}} + +https://www.maxjeune-tgvinoui.sncf/api/public/reservation/travel-consultation +{"cardNumber": {CARDNUMBER},"startDate":"2023-01-01T01:01:01.001Z"} + +https://www.maxjeune-tgvinoui.sncf/api/public/reservation/travel-confirm +{"marketingCarrierRef":"{Référence}","trainNumber":"{trainNumber}","departureDateTime":"2023-01-01T01:01:00.000"} + +https://www.maxjeune-tgvinoui.sncf/api/public/reservation/cancel-reservation +{"travelsInfo":[{"marketingCarrierRef":"{Référence}","orderId":"{orderId}","customerName":"{NomAssocié}","trainNumber":"{trainNumber}","departureDateTime":"2023-01-01T01:01:00.000"}]} +""" + +CARDNUMBER=config.card_number + +headers = { + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0", + "Accept": "application/json", + "Accept-Language": "fr,fr-FR;q=0.8,en;q=0.5,en-US;q=0.3", # MANDATORY + "Content-Type": "application/json", + "Authorization": config.authorization, + "Cookie": config.cookies, +} + +def r_to_data(r): + assert r.status_code >= 200 and r.status_code < 300 + return json.loads(r.content.decode("utf-8")) + +def summary(s, headers): + return s.post( + "https://www.maxjeune-tgvinoui.sncf/api/public/subscription/summary", + headers=headers, + json={"cardNumber": CARDNUMBER} + ) + + +def travel_consultation(s, headers, date_time=None): + if date_time is None: + date_time = datetime.utcnow() + + return s.post( + "https://www.maxjeune-tgvinoui.sncf/api/public/reservation/travel-consultation", + headers=headers, + json={ + "cardNumber": CARDNUMBER, + "startDate": date_time.strftime("%Y-%m-%dT%H:%M:%S.%fZ") + } + ) + + +def check_confirmed(travel, s, headers): + travels = [Travel(i) for i in r_to_data(travel_consultation(s, headers))] + for t in travels: + if t.dvNumber == travel.dvNumber and t.departureDateTime == travel.departureDateTime and t.trainNumber == travel.trainNumber and t.travelConfirmed != "CONFIRMED": + utils.raiseDiscord( + "The tentative of confirmation did not succeed", + travel=t, + ) + return False + + return True + +def confirm_all(s, headers): + travels = [Travel(i) for i in r_to_data(travel_consultation(s, headers))] + + now = datetime.utcnow() + for travel in travels: + if datetime.fromisoformat(travel.departureDateTime) >= now: + print(travel) + print(travel.travelConfirmed) + if (travel.travelConfirmed != "CONFIRMED" and travel.travelConfirmed != "TOO_EARLY_TO_CONFIRM"): + travel.confirm(s, headers) + if check_confirmed(travel, s, headers): + utils.successDiscord(travel) + + + + +s = requests.Session() diff --git a/src/utils.py b/src/utils.py new file mode 100644 index 0000000..257c0f0 --- /dev/null +++ b/src/utils.py @@ -0,0 +1,61 @@ +import requests +from datetime import datetime + +from config import error_url, success_url + +color_red = 16711680 +color_green = 65280 + + + +def raiseDiscord(message, travel=None, req=None): + data = { + "username": "TGVMax AutoConfirm", + "avatar_url": "https://cdn.discordapp.com/attachments/894238990633427034/1180197752911696082/tgvmax.png", + "embeds": + [{ + "title": "Erreur lors de la confirmation automatique", + "description": message, + "color": color_red, + "fields": [] + }] + } + + if travel is not None: + departureDateTime = datetime.fromisoformat(travel.departureDateTime) + data["embeds"][0]["fields"].append({ + "name": f"Travel {travel.dvNumber}", + "value": departureDateTime.strftime("%d %b. %Hh%M") + }) + if req is not None: + try: + data["embeds"][0]["fields"].append({ + "name": f"Erreur {req.status_code}", + "value": req.content[:200].decode("utf-8") + }) + except TypeError: + data["embeds"][0]["fields"].append({ + "name": f"Erreur {req.status_code}", + "value": "dunno" + }) + + requests.post(error_url, json=data) + + +def successDiscord(travel): + departureDateTime = datetime.fromisoformat(travel.departureDateTime) + data = { + "username": "TGVMax AutoConfirm", + "avatar_url": "https://cdn.discordapp.com/attachments/894238990633427034/1180197752911696082/tgvmax.png", + "embeds": + [{ + "title": "Trajet validé automatiquement", + "color": color_green, + "fields": [{ + "name": f"Travel {travel.dvNumber}", + "value": departureDateTime.strftime("%d %b. %Hh%M") + }] + }] + } + + requests.post(error_url, json=data) \ No newline at end of file