Last active
March 15, 2024 04:00
-
-
Save adrienshen/d281e160fec36a3f53b7ff080d052e88 to your computer and use it in GitHub Desktop.
Beating the Austin DPS Appointment Booking System
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# -*- coding: utf-8 -*- | |
import time | |
import pprint | |
pp = pprint.PrettyPrinter(indent=4) | |
import requests | |
import json | |
from datetime import datetime | |
DEV_FORCE_BOOKING = False | |
DATE_DELAY = 0 # 0 would be the first available date,, but it could also be the same day as today | |
TIME_SLOT = -1 # last slot one in the day | |
BASE_URL = 'https://publicapi.txdpsscheduler.com/api/' | |
# change these details to your personal information to book | |
DOB = "09/08/1990" | |
FIRST_NAME = 'Fred' | |
LAST_NAME = 'Johnson' | |
EMAIL = 'fredjohnson@gmail.com' | |
LAST_4 = '2233' | |
SERVICE_TYPE = 71 | |
DATE_PATTERN = '%m/%d/%Y' | |
# these are the needed headers to communicate with the scheduler apis | |
HEADERS = { | |
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36', | |
'Referer': 'https://public.txdpsscheduler.com/', | |
'Origin': 'https://public.txdpsscheduler.com', | |
'Host': 'publicapi.txdpsscheduler.com', | |
'Accept': 'application/json, text/plain, */*', | |
'Accept-Encoding': 'gzip, deflate, br', | |
'Content-Type': 'application/json', | |
'Cookie': 'ARRAffinity=bbe667b6ac7363554ad0781245985818906290e4fd0c62146e63586c6747a1f4; ARRAffinitySameSite=bbe667b6ac7363554ad0781245985818906290e4fd0c62146e63586c6747a1f4' | |
} | |
def get_soonest_date_from_locations(results): | |
da_soonest = None | |
for location in results: | |
print('{} with Id={}, Soonest={}, Distance={}'.format(location.get('Name'), location.get('Id'), location.get('NextAvailableDate'), location.get('Distance'))) | |
compare_date = datetime.strptime(location.get('NextAvailableDate'), DATE_PATTERN) | |
if da_soonest and (da_soonest.get('next_date') < compare_date): | |
continue | |
if da_soonest and (da_soonest.get('next_date') == compare_date) and \ | |
da_soonest.get('distance') <= location.get('Distance'): | |
# print('date is equal and distance is less or equal') | |
continue | |
da_soonest = { | |
'id': location.get('Id'), | |
'next_date': datetime.strptime(location.get('NextAvailableDate'), DATE_PATTERN), | |
'distance': location.get('Distance') | |
} | |
return da_soonest | |
def get_response_id(): | |
# make sure we are eligible: | |
ELIGIBILITY_URL = BASE_URL + 'Eligibility' | |
PAYLOAD = json.dumps({ | |
"CardNumber": "", | |
"DateOfBirth": DOB, | |
"FirstName": FIRST_NAME, | |
"LastFourDigitsSsn": LAST_4, | |
"LastName": LAST_NAME | |
}) | |
response = requests.request('POST', ELIGIBILITY_URL, headers=HEADERS, data=PAYLOAD) | |
results = response.json() | |
return results[0].get('ResponseId') | |
def get_bookings(): | |
PAYLOAD = json.dumps({ | |
"DateOfBirth": DOB, | |
"FirstName": FIRST_NAME, | |
"LastFourDigitsSsn": LAST_4, | |
"LastName": LAST_NAME | |
}) | |
BOOKING_URL = BASE_URL + 'Booking' | |
response = requests.request('POST', BOOKING_URL, headers=HEADERS, data=PAYLOAD) | |
return response.json() | |
def make_booking_request(last_slot_of_earliest_day, response_id, da_soonest): | |
MAKE_BOOKING_URL = BASE_URL + 'RescheduleBooking' | |
MAKE_BOOKING_BODY = json.dumps({ | |
'BookingDateTime': last_slot_of_earliest_day.get('StartDateTime'), | |
'BookingDuration': last_slot_of_earliest_day.get('Duration'), | |
'CardNumber': "", | |
'CellPhone': "", | |
'DateOfBirth': DOB, | |
'Email': EMAIL, | |
'FirstName': FIRST_NAME, | |
'HomePhone': "", | |
'Last4Ssn': LAST_4, | |
'LastName': LAST_NAME, | |
# where is this from? | |
'ResponseId': response_id, | |
'SendSms': False, | |
'ServiceTypeId': SERVICE_TYPE, | |
'SiteId': da_soonest.get('id'), | |
'SpanishLanguage': "N" | |
}) | |
print(MAKE_BOOKING_BODY) | |
response = requests.request('POST', MAKE_BOOKING_URL, headers=HEADERS, data=MAKE_BOOKING_BODY) | |
return response.json() | |
# return None | |
def get_available_locations(): | |
PAYLOAD = json.dumps({ | |
"CityName": "", | |
"PreferredDay": 0, | |
"TypeId": 71, | |
"ZipCode": "78723" | |
}) | |
URL = BASE_URL + 'AvailableLocation' | |
response = requests.request('POST', URL, headers=HEADERS, data=PAYLOAD) | |
return response.json() | |
def hold_slot_request(slot_id): | |
HOLD_SLOT_URL = BASE_URL + 'HoldSlot' | |
PAYLOAD = json.dumps({ | |
"DateOfBirth": DOB, | |
"FirstName": FIRST_NAME, | |
"Last4Ssn": LAST_4, | |
"LastName": LAST_NAME, | |
"SlotId": slot_id | |
}) | |
print('\nHOLD_SLOT payload: ', PAYLOAD, '\n') | |
response = requests.request('POST', HOLD_SLOT_URL, headers=HEADERS, data=PAYLOAD) | |
return response.json() | |
def __main__(): | |
results = get_available_locations() | |
da_soonest = get_soonest_date_from_locations(results) | |
print('>>> Soonest Date: ', da_soonest, '\n\n') | |
print('>>> Check Current Booking Against Next Availability to See if We Improved Our Time:') | |
response_id = get_response_id() | |
print('ResponseId: ', response_id) | |
results = get_bookings() | |
# the order is most recent bookings to older bookings | |
print('>>> Print current best booking time: {}'.format(results[0].get('BookingDateTime'))) | |
print('>>> Confirmation: ', results[0].get('ConfirmationNumber')) | |
print(results, '\n\n') | |
current_booking_date = datetime.strptime(results[0]['BookingDateTime'], '%Y-%m-%dT%H:%M:%S') | |
print('>>> Compare current booking with new availability (Is the New Date Earlier Than Current Booking?):') | |
print('>>>> ', da_soonest.get('next_date') < current_booking_date) | |
# print("(da_soonest.get('next_date') < current_booking_date)", (da_soonest.get('next_date') > current_booking_date)) | |
if DEV_FORCE_BOOKING or (da_soonest.get('next_date') < current_booking_date): | |
print('>>> We improved our time slot, rescedule for the better position') | |
print('>>> Get available timeslots for soonest location:') | |
TIME_SLOTS_URL = BASE_URL + 'AvailableLocationDates' | |
PAYLOAD = json.dumps({ | |
'LocationId': da_soonest.get('id'), | |
'PreferredDay': 0, | |
'SameDay': False, | |
'TypeId': SERVICE_TYPE, | |
}) | |
response = requests.request('POST', TIME_SLOTS_URL, headers=HEADERS, data=PAYLOAD) | |
results = response.json() | |
last_slot_of_earliest_day = results.get('LocationAvailabilityDates')[DATE_DELAY].get('AvailableTimeSlots')[TIME_SLOT] | |
pp.pprint(last_slot_of_earliest_day) | |
results = hold_slot_request(last_slot_of_earliest_day.get('SlotId')) | |
print('>>> Hold Slot Successful: ', results.get('SlotHeldSuccessfully')) | |
print('>>> Make the Booking After 2 Seconds Hold:') | |
time.sleep(2) | |
response = make_booking_request(last_slot_of_earliest_day, response_id, da_soonest) | |
print('>>> Final Booking Response:') | |
booking = response.get('Booking') | |
if booking.get('ConfirmationNumber'): | |
print('\n\nNew Booking on: {}, Confirmation # is {}, Site Address is {}'.format(booking.get('BookingDateTime'), booking.get('ConfirmationNumber'), booking.get('SiteAddress'))) | |
print('>>> You Should Be Getting a Email for This Booking From DPS') | |
else: | |
print('>>> New booking was not successful, response details: ', response) | |
else: | |
print('\n\nNo date to optimize, check again in 5 minutes...\n\n') | |
for i in range(60 * 24): # should run for 1 day | |
print('Script has been running for: {} minutes'.format(i)) | |
__main__() | |
time.sleep(60) | |
# How to Use: | |
# set this script on a a cronjob running every X minutes | |
# python3 makebooking.py |
good job, you saved half of my day
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For context: https://www.kiiitv.com/article/money/business/right-on-the-money/right-on-the-money-dps-license-changed/287-37475df9-3a21-42ec-aa46-de762269298a