# # swa.py # Functions for interacting with the Southwest API # import codecs from urllib.parse import urlencode import pendulum import requests import exceptions USER_AGENT = "SouthwestAndroid/7.2.1 android/10" # This is not a secret, but obfuscate it to prevent detection API_KEY = codecs.decode("y7kk8389n5on9ro24nr68onq068oq1860osp", "rot13") def _make_request(method, page, data='', check_status_code=True): url = f"https://mobile.southwest.com/api/{page}" headers = { "User-Agent": USER_AGENT, "X-API-Key": API_KEY, "X-Channel-ID": "MWEB", "Accept": "application/json" } method = method.lower() if method == 'get': response = requests.get(url, headers=headers, params=urlencode(data)) elif method == 'post': headers['Content-Type'] = 'application/json' response = requests.post(url, headers=headers, json=data) else: raise NotImplementedError() if check_status_code and not response.ok: try: msg = response.json()["message"] except: msg = response.reason if response.status_code == 404: raise exceptions.ReservationNotFoundError() raise exceptions.SouthwestAPIError("status_code={} msg=\"{}\"".format( response.status_code, msg)) return response class Reservation(): def __init__(self, first_name, last_name, confirmation_number, response): self.first_name = first_name self.last_name = last_name self.confirmation_number = confirmation_number self.response = response # Second of the minute to use for check in times self.check_in_seconds = 5 def __repr__(self): return "<Reservation {}>".format(self.confirmation_number) @classmethod def from_passenger_info(cls, first_name, last_name, confirmation_number): params = {'first-name': first_name, 'last-name': last_name} response = _make_request( "get", "mobile-air-booking/v1/mobile-air-booking/page/view-reservation/" + confirmation_number, params ) return cls(first_name, last_name, confirmation_number, response.json()) def _get_check_in_time(self, departure_time): """ Receives a departure time in RFC3339 format: 2017-02-09T07:50:00.000-06:00 And returns the check in time (24 hours prior) as a pendulum time object. `self.check_in_seconds` seconds (Default 5) are added to the checkin time to allow for some clock skew buffer. """ return pendulum.parse(departure_time)\ .subtract(days=1)\ .add(seconds=self.check_in_seconds) def get_check_in_times(self, expired=False): """ Return a sorted and reversed list of check-in times for a reservation as RFC3339 timestamps. By default, only future checkin times are returned. Set `expired` to True to return all checkin times. Times are sorted and reversed so that the soonest check-in time may be popped from the end of the list. """ flights = self.response['viewReservationViewPage']['shareDetails']['flightInfo'] times = [ self._get_check_in_time(flight['departureDateTime']) for flight in flights ] # Remove expired checkins from results if not expired: times = [t for t in times if t > pendulum.now()] return list(map(str, reversed(sorted(times)))) @property def check_in_times(self): return self.get_check_in_times() def check_in(first_name, last_name, confirmation_number): # first we get a session token with a GET request, then issue a POST to check in page = "mobile-air-operations/v1/mobile-air-operations/page/check-in" params = {'first-name': first_name, 'last-name': last_name} session = _make_request("get", page + "/" + confirmation_number, params) sessionj = session.json() try: # the whole POST body (including the session token) is provided here body = sessionj['checkInViewReservationPage']['_links']['checkIn']['body'] except KeyError: print(sessionj) raise exceptions.SouthwestAPIError("Error getting check-in session") response = _make_request("post", page, body) if not response.ok: raise exceptions.SouthwestAPIError("Error checking in! response={}".format(response)) responsej = response.json() if responsej['checkInConfirmationPage']['title']['key'] != 'CHECKIN__YOURE_CHECKEDIN': raise exceptions.SouthwestAPIError("Check in failed. response={}".format(responsej)) return responsej