1
0
mirror of https://github.com/pyro2927/SouthwestCheckin.git synced 2025-12-06 01:13:19 +00:00

Refactoring Southwest class to be OOP

This commit is contained in:
pyro2927
2019-02-16 15:25:43 -08:00
parent 170519d316
commit d7722fa1ce
5 changed files with 83 additions and 71 deletions

View File

@@ -5,4 +5,4 @@ python:
- "3.7.1" - "3.7.1"
script: script:
- pytest - pytest
- pycodestyle checkin.py --show-source --ignore=E501 - pycodestyle *.py --show-source --ignore=E501

View File

@@ -20,17 +20,17 @@ from dateutil.parser import parse
from docopt import docopt from docopt import docopt
from math import trunc from math import trunc
from pytz import utc from pytz import utc
from southwest import Reservation
from threading import Thread from threading import Thread
from tzlocal import get_localzone from tzlocal import get_localzone
import openflights import openflights
import southwest
import sys import sys
import time import time
CHECKIN_EARLY_SECONDS = 5 CHECKIN_EARLY_SECONDS = 5
def schedule_checkin(flight_time, number, first, last, notify=[]): def schedule_checkin(flight_time, reservation):
checkin_time = flight_time - timedelta(days=1) checkin_time = flight_time - timedelta(days=1)
current_time = datetime.now(utc).astimezone(get_localzone()) current_time = datetime.now(utc).astimezone(get_localzone())
# check to see if we need to sleep until 24 hours before flight # check to see if we need to sleep until 24 hours before flight
@@ -42,16 +42,15 @@ def schedule_checkin(flight_time, number, first, last, notify=[]):
h, m = divmod(m, 60) h, m = divmod(m, 60)
print("Too early to check in. Waiting {} hours, {} minutes, {} seconds".format(trunc(h), trunc(m), s)) print("Too early to check in. Waiting {} hours, {} minutes, {} seconds".format(trunc(h), trunc(m), s))
time.sleep(delta) time.sleep(delta)
data = southwest.checkin(number, first, last) data = reservation.checkin()
for flight in data['flights']: for flight in data['flights']:
for doc in flight['passengers']: for doc in flight['passengers']:
print("{} got {}{}!".format(doc['name'], doc['boardingGroup'], doc['boardingPosition'])) print("{} got {}{}!".format(doc['name'], doc['boardingGroup'], doc['boardingPosition']))
if len(notify) > 0:
southwest.send_notification(data, notify)
def auto_checkin(reservation_number, first_name, last_name, notify=[]): def auto_checkin(reservation_number, first_name, last_name, notify=[]):
body = southwest.lookup_existing_reservation(reservation_number, first_name, last_name) r = Reservation(reservation_number, first_name, last_name, notify)
body = r.lookup_existing_reservation()
# Get our local current time # Get our local current time
now = datetime.now(utc).astimezone(get_localzone()) now = datetime.now(utc).astimezone(get_localzone())
@@ -70,7 +69,7 @@ def auto_checkin(reservation_number, first_name, last_name, notify=[]):
# found a flight for checkin! # found a flight for checkin!
print("Flight information found, departing {} at {}".format(airport, date.strftime('%b %d %I:%M%p'))) print("Flight information found, departing {} at {}".format(airport, date.strftime('%b %d %I:%M%p')))
# Checkin with a thread # Checkin with a thread
t = Thread(target=schedule_checkin, args=(date, reservation_number, first_name, last_name, notify)) t = Thread(target=schedule_checkin, args=(date, r))
t.daemon = True t.daemon = True
t.start() t.start()
threads.append(t) threads.append(t)

View File

@@ -8,6 +8,7 @@ from datetime import datetime, timedelta
from pytz import timezone, utc from pytz import timezone, utc
from tzlocal import get_localzone from tzlocal import get_localzone
def template_time(file_name): def template_time(file_name):
in_string = open(file_name, 'r').read() in_string = open(file_name, 'r').read()
t = datetime.now() + timedelta(seconds=5) t = datetime.now() + timedelta(seconds=5)
@@ -15,6 +16,7 @@ def template_time(file_name):
in_string = in_string.replace('TIME_HERE', t.strftime('%H:%M')) in_string = in_string.replace('TIME_HERE', t.strftime('%H:%M'))
return in_string return in_string
def test_checkin(requests_mock): def test_checkin(requests_mock):
requests_mock.register_uri('GET', '/api/mobile-misc/v1/mobile-misc/page/view-reservation/XXXX?first-name=John&last-name=Smith', text=template_time('fixtures/view-reservation.json')) requests_mock.register_uri('GET', '/api/mobile-misc/v1/mobile-misc/page/view-reservation/XXXX?first-name=John&last-name=Smith', text=template_time('fixtures/view-reservation.json'))
requests_mock.register_uri('GET', '/api/mobile-air-operations/v1/mobile-air-operations/page/check-in/XXXX?first-name=John&last-name=Smith', text=template_time('fixtures/checkin-get.json')) requests_mock.register_uri('GET', '/api/mobile-air-operations/v1/mobile-air-operations/page/check-in/XXXX?first-name=John&last-name=Smith', text=template_time('fixtures/checkin-get.json'))
@@ -22,14 +24,16 @@ def test_checkin(requests_mock):
requests_mock.register_uri('POST', '/php/apsearch.php', text=template_time('fixtures/openflights.json')) requests_mock.register_uri('POST', '/php/apsearch.php', text=template_time('fixtures/openflights.json'))
try: try:
checkin.auto_checkin('XXXX', 'John', 'Smith') checkin.auto_checkin('XXXX', 'John', 'Smith')
except: except Exception:
pytest.fail("Error checking in") pytest.fail("Error checking in")
def test_timezone_localization(): def test_timezone_localization():
tz = timezone('America/Los_Angeles') tz = timezone('America/Los_Angeles')
date = tz.localize(datetime.strptime('2018-01-01 13:00', '%Y-%m-%d %H:%M')) date = tz.localize(datetime.strptime('2018-01-01 13:00', '%Y-%m-%d %H:%M'))
assert date.strftime('%z') == '-0800' assert date.strftime('%z') == '-0800'
@vcr.use_cassette() @vcr.use_cassette()
def test_openflights_api(): def test_openflights_api():
tzrequest = {'iata': 'LAX', tzrequest = {'iata': 'LAX',
@@ -42,24 +46,22 @@ def test_openflights_api():
airport_tz = timezone(json.loads(tzresult.text)['airports'][0]['tz_id']) airport_tz = timezone(json.loads(tzresult.text)['airports'][0]['tz_id'])
assert airport_tz.zone == "America/Los_Angeles" assert airport_tz.zone == "America/Los_Angeles"
def test_notifications(requests_mock, mocker): def test_notifications(requests_mock, mocker):
requests_mock.register_uri('GET', '/api/mobile-air-operations/v1/mobile-air-operations/page/check-in/XXXX?first-name=John&last-name=Smith', text=template_time('fixtures/checkin-get.json')) requests_mock.register_uri('GET', '/api/mobile-air-operations/v1/mobile-air-operations/page/check-in/XXXX?first-name=John&last-name=Smith', text=template_time('fixtures/checkin-get.json'))
data = template_time('fixtures/checkin-post.json') data = template_time('fixtures/checkin-post.json')
requests_mock.register_uri('POST', '/api/mobile-air-operations/v1/mobile-air-operations/page/check-in', text=data) requests_mock.register_uri('POST', '/api/mobile-air-operations/v1/mobile-air-operations/page/check-in', text=data)
data = json.loads(data) data = json.loads(data)
requests_mock.register_uri('POST', '/php/apsearch.php', text=template_time('fixtures/openflights.json')) requests_mock.register_uri('POST', '/php/apsearch.php', text=template_time('fixtures/openflights.json'))
mocked_checkin = mocker.patch('southwest.send_notification') mocked_checkin = mocker.patch('southwest.Reservation.send_notification')
t = datetime.now(utc).astimezone(get_localzone()) + timedelta(minutes=5) t = datetime.now(utc).astimezone(get_localzone()) + timedelta(minutes=5)
try: try:
checkin.schedule_checkin(t, 'XXXX', 'John', 'Smith') r = southwest.Reservation('XXXX', 'John', 'Smith')
southwest.send_notification.assert_not_called() checkin.schedule_checkin(t, r)
email = [{'mediaType': 'EMAIL', 'emailAddress': 'test@example.com'}] mocked_checkin.assert_not_called()
checkin.schedule_checkin(t, 'XXXX', 'John', 'Smith', email) r.notifications = [{'mediaType': 'EMAIL', 'emailAddress': 'test@example.com'}]
southwest.send_notification.assert_called_once_with(data['checkInConfirmationPage'], email) checkin.schedule_checkin(t, r)
southwest.send_notification.reset_mock() mocked_checkin.assert_called_once()
phone = [{'mediaType': 'SMS', 'phoneNumber': '1234567890'}] except Exception:
checkin.schedule_checkin(t, 'XXXX', 'John', 'Smith', phone)
southwest.send_notification.assert_called_once_with(data['checkInConfirmationPage'], phone)
except:
pytest.fail("Error checking in") pytest.fail("Error checking in")

View File

@@ -2,6 +2,7 @@ import requests
import json import json
import pytz import pytz
def timezone_for_airport(airport_code): def timezone_for_airport(airport_code):
tzrequest = {'iata': airport_code, tzrequest = {'iata': airport_code,
'country': 'ALL', 'country': 'ALL',

View File

@@ -10,58 +10,68 @@ MAX_ATTEMPTS = 40
# Pulled from proxying the Southwest iOS App # Pulled from proxying the Southwest iOS App
headers = {'Host': 'mobile.southwest.com', 'Content-Type': 'application/json', 'X-API-Key': API_KEY, 'X-User-Experience-Id': USER_EXPERIENCE_KEY, 'Accept': '*/*'} headers = {'Host': 'mobile.southwest.com', 'Content-Type': 'application/json', 'X-API-Key': API_KEY, 'X-User-Experience-Id': USER_EXPERIENCE_KEY, 'Accept': '*/*'}
# You might ask yourself, "Why the hell does this exist?"
# Basically, there sometimes appears a "hiccup" in Southwest where things
# aren't exactly available 24-hours before, so we try a few times
def safe_request(url, body=None):
try:
attempts = 0
while True:
if body is not None:
r = requests.post(url, headers=headers, json=body)
else:
r = requests.get(url, headers=headers)
data = r.json()
if 'httpStatusCode' in data and data['httpStatusCode'] in ['NOT_FOUND', 'BAD_REQUEST', 'FORBIDDEN']:
attempts += 1
print(data['message'])
if attempts > MAX_ATTEMPTS:
sys.exit("Unable to get data, killing self")
time.sleep(CHECKIN_INTERVAL_SECONDS)
continue
return data
except ValueError:
# Ignore responses with no json data in body
pass
def lookup_existing_reservation(number, first, last): class Reservation():
# Find our existing record
url = "{}mobile-misc/v1/mobile-misc/page/view-reservation/{}?first-name={}&last-name={}".format(BASE_URL, number, first, last)
data = safe_request(url)
return data['viewReservationViewPage']
def get_checkin_data(number, first, last): def __init__(self, number, first, last, notifications=[]):
url = "{}mobile-air-operations/v1/mobile-air-operations/page/check-in/{}?first-name={}&last-name={}".format(BASE_URL, number, first, last) self.number = number
data = safe_request(url) self.first = first
return data['checkInViewReservationPage'] self.last = last
self.notifications = []
def checkin(number, first, last): # You might ask yourself, "Why the hell does this exist?"
data = get_checkin_data(number, first, last) # Basically, there sometimes appears a "hiccup" in Southwest where things
info_needed = data['_links']['checkIn'] # aren't exactly available 24-hours before, so we try a few times
url = "{}mobile-air-operations{}".format(BASE_URL, info_needed['href']) def safe_request(self, url, body=None):
print("Attempting check-in...") try:
return safe_request(url, info_needed['body'])['checkInConfirmationPage'] attempts = 0
while True:
if body is not None:
r = requests.post(url, headers=headers, json=body)
else:
r = requests.get(url, headers=headers)
data = r.json()
if 'httpStatusCode' in data and data['httpStatusCode'] in ['NOT_FOUND', 'BAD_REQUEST', 'FORBIDDEN']:
attempts += 1
print(data['message'])
if attempts > MAX_ATTEMPTS:
sys.exit("Unable to get data, killing self")
time.sleep(CHECKIN_INTERVAL_SECONDS)
continue
return data
except ValueError:
# Ignore responses with no json data in body
pass
def send_notification(checkindata, notify): def lookup_existing_reservation(self):
if len(notify) < 1: # Find our existing record
return url = "{}mobile-misc/v1/mobile-misc/page/view-reservation/{}?first-name={}&last-name={}".format(BASE_URL, self.number, self.first, self.last)
info_needed = checkindata['_links']['boardingPasses'] data = self.safe_request(url)
url = "{}mobile-air-operations{}".format(BASE_URL, info_needed['href']) return data['viewReservationViewPage']
mbpdata = safe_request(url, info_needed['body'])
info_needed = mbpdata['checkInViewBoardingPassPage']['_links'] def get_checkin_data(self):
url = "{}mobile-air-operations{}".format(BASE_URL, info_needed['href']) url = "{}mobile-air-operations/v1/mobile-air-operations/page/check-in/{}?first-name={}&last-name={}".format(BASE_URL, self.number, self.first, self.last)
print("Attempting to send boarding pass...") data = self.safe_request(url)
body = info_needed['body'] return data['checkInViewReservationPage']
for n in notify:
body.update(n) def checkin(self):
safe_request(url, body) data = self.get_checkin_data()
info_needed = data['_links']['checkIn']
url = "{}mobile-air-operations{}".format(BASE_URL, info_needed['href'])
print("Attempting check-in...")
confirmation = self.safe_request(url, info_needed['body'])['checkInConfirmationPage']
if len(self.notifications) > 0:
self.send_notification(confirmation)
return confirmation
def send_notification(self, checkindata):
info_needed = checkindata['_links']['boardingPasses']
url = "{}mobile-air-operations{}".format(BASE_URL, info_needed['href'])
mbpdata = self.safe_request(url, info_needed['body'])
info_needed = mbpdata['checkInViewBoardingPassPage']['_links']
url = "{}mobile-air-operations{}".format(BASE_URL, info_needed['href'])
print("Attempting to send boarding pass...")
body = info_needed['body']
for n in self.notifications:
body.update(n)
self.safe_request(url, body)