Last active
March 12, 2025 03:53
-
-
Save hcho3/e173a69cb144a0132c8384abe8b9a6c6 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
# Check for open appointments in the current month at Las Vegas DMV | |
# Currently, the script looks for appointments of type "Multiple Services - DL/Vehicle" | |
import datetime | |
import time | |
import boto3 | |
import requests | |
# List of Nevada DMV locations in the Las Vegas metro area | |
las_dmv = { # site_id, servicetype_id | |
"Flamingo": (402410, 1260), | |
"Sahara": (402419, 870), | |
"Decatur": (402418, 1143), | |
"Henderson": (402408, 1026), | |
} | |
request_headers = { | |
"Accept": "application/json", | |
"User-Agent": ( | |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " | |
"Chrome/133.0.0.0 Safari/537.36" | |
), # Need to spoof the user agent to avoid 429 error | |
} | |
email_to = "[email protected]" | |
email_from = "[email protected]" | |
def format_url_date_list(*, site_id: str, servicetype_id: str) -> str: | |
"""List all dates where appointment slots are available""" | |
return ( | |
f"https://api.waitwell.us/api/{site_id}/client/appointments/available?" | |
f"ServiceType_id={servicetype_id}&CheckDates=1" | |
) | |
def format_url_slot_list(*, site_id: str, servicetype_id: str, date: str) -> str: | |
"""List all available time slots in a given date""" | |
start_date = datetime.datetime.strptime(date, "%Y-%m-%d").date() | |
end_date = start_date + datetime.timedelta(days=1) | |
return ( | |
f"https://api.waitwell.us/api/{site_id}/client/appointments/available?" | |
f"ServiceType_id={servicetype_id}&StartDate={start_date}&EndDate={end_date}" | |
f"&limit=80&AM=1&PM=1" | |
) | |
def filter_by_month(date: str, *, month: int) -> bool: | |
return datetime.datetime.strptime(date, "%Y-%m-%d").date().month == month | |
def check_date_for_slots(*, site_id: str, servicetype_id: str, date: str) -> str: | |
url = format_url_slot_list( | |
site_id=site_id, servicetype_id=servicetype_id, date=date | |
) | |
r = requests.get(url, headers=request_headers) | |
r.raise_for_status() | |
obj = r.json() | |
if obj["rows"]: | |
return True | |
return False | |
def alert_via_email(message: str): | |
""" | |
Send an alert via e-mail, using Simple Email Service from AWS. | |
Make sure to configure AWS credentials. | |
""" | |
client = boto3.client("ses", region_name="us-west-2") | |
response = client.send_email( | |
Destination={ | |
"ToAddresses": [ | |
email_to, | |
], | |
}, | |
Message={ | |
"Body": { | |
"Text": { | |
"Charset": "UTF-8", | |
"Data": message, | |
}, | |
}, | |
"Subject": { | |
"Charset": "UTF-8", | |
"Data": "Alert from Philip's monitor for NV DMV appt", | |
}, | |
}, | |
Source=email_from, | |
) | |
def check_spot() -> bool: | |
this_month = datetime.datetime.today().month | |
for location, (site_id, servicetype_id) in las_dmv.items(): | |
url = format_url_date_list(site_id=site_id, servicetype_id=servicetype_id) | |
r = requests.get(url, headers=request_headers) | |
r.raise_for_status() | |
obj = r.json() | |
# Filter available dates this month | |
dates = [e for e in obj["dates"] if filter_by_month(e, month=this_month)] | |
# Check individual dates by making a second request to view available | |
# time slots in each date. | |
# Sometimes a date shows up as available, but in fact all slots are taken | |
dates = [ | |
date | |
for date in dates[:5] | |
if check_date_for_slots( | |
site_id=site_id, servicetype_id=servicetype_id, date=date | |
) | |
] | |
if dates: | |
t = ", ".join(dates) | |
msg = f"DMV {location} has free slots in {t}" | |
print(msg) | |
alert_via_email(msg) | |
return True | |
else: | |
print(f"DMV {location} has no free slot this month") | |
return False | |
if __name__ == "__main__": | |
while True: | |
r = check_spot() | |
if r: | |
break | |
print("Checking again in 60 sec...") | |
time.sleep(60) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment