-
-
Save dat-vikash/2280dd53f37a6406fb73b719dc6f8cb2 to your computer and use it in GitHub Desktop.
""" | |
Helps make dad life easier by reserving times before pre-k. | |
Features: | |
- allow for filtering by time, day of week, number of player and course | |
- handles reservations and ensures no overbooking | |
- keeps track of default and state | |
Requires python3 and selenium: | |
+ https://sites.google.com/chromium.org/driver/ | |
+ https://selenium-python.readthedocs.io/ | |
""" | |
try: | |
from urllib.request import Request, urlopen # Python 3 | |
except ImportError: | |
from urllib2 import Request, urlopen # Python 2 | |
import json | |
import datetime | |
import time | |
import os | |
from selenium import webdriver | |
import pytz | |
COURSEIDS_MAP = { | |
"B": {"name":"Francis A. Byrne Golf Course", "id":"54f14d8a0c8ad60378b03e95"}, | |
"H": {"name":"Hendricks Field Golf Course", "id":"54f14d8a0c8ad60378b03e98"}, | |
"W": {"name":"Weequahic Park Golf Course", "id":"54f14d8b0c8ad60378b03e9c"} | |
} | |
local_tz = pytz.timezone('US/Eastern') | |
AUTO_RESERVATION=False | |
def login(driver): | |
""" | |
login to site | |
""" | |
driver.get(f"https://essex-group.book.teeitup.golf/login") | |
time.sleep(5) | |
username = driver.find_element("id","txtUsername") | |
password = driver.find_element("id","txtPassword") | |
# FILL in your county log in here | |
username.send_keys("") | |
password.send_keys("") | |
# Login and return driver with auth context | |
driver.find_element("xpath","//button[@data-testid='login-button']").click() | |
return driver | |
def reserve(tees): | |
""" | |
does the actual reservation | |
""" | |
reservations = load_reservations() | |
reservations_weekly_max = tees[0]['rounds_per_week'] | |
reservations_daily_max = tees[0]['rounds_per_day'] | |
# todo: get existing reservations | |
# existing_reservations = 0 | |
# | |
# reservations_weekly_max -= existing_reservations | |
# reservations_daily_max -= existing_reservations | |
drivers = [] | |
for tee in tees: | |
local_time = tee['teetime'].astimezone(local_tz) | |
course_name = list( | |
filter(None, | |
[COURSEIDS_MAP[c]['name'] if COURSEIDS_MAP[c]['id'] == tee['course'] else '' for c in COURSEIDS_MAP] | |
) | |
)[0] | |
if int(reservations['daily'].get(tee['date'], reservations_daily_max)) > 0 \ | |
and int(reservations['weekly'].get(local_time.strftime("%V"), reservations_weekly_max)) > 0 : # weekly check | |
print(f"Reserving Tee time for Date: {tee['date'] } Time: {local_time.strftime('%H:%M %p')} Course: {course_name} Available: {tee['playersAvail']} Fee: ${tee['fee']/100} Booking: {tee['num_player_booking']}") | |
# start driver and get auth context | |
driver = webdriver.Chrome() | |
driver = login(driver) | |
time.sleep(5) | |
drivers.append(driver) | |
# time to book it | |
driver.get(f"https://essex-group.book.teeitup.golf/?course={tee['course']}&date={tee['date']}") | |
t = tee['teetime'].strftime('%-H:%M %p') | |
time.sleep(5) | |
# find the tee time | |
button = driver.find_element(f"xpath",f"//p[@data-testid='teetimes-tile-time'][contains(., '{t}')]").find_element("xpath","./../..").find_element("xpath","//button[@data-testid='teetimes_book_now_button']") | |
button.click() | |
time.sleep(3) | |
# checkout | |
driver.find_element("xpath",f"//button[@data-testid='button-value-{tee['num_player_booking']}']").click() | |
driver.find_element("xpath","//span[contains(.,'Proceed to Checkout')]").click() | |
time.sleep(5) | |
# no turning back | |
driver.find_element("name","chb-nm").click() | |
# todo: actually checkout | |
# driver.find_element("xpath","//button[@data-testid='make-your-reservation-btn']").click() | |
# update reservations | |
reservations['daily'].update({tee['date']: reservations['daily'].get(tee['date'],reservations_daily_max) - 1 }) | |
reservations['weekly'].update({local_time.strftime("%V"): reservations['weekly'].get(local_time.strftime("%V"),reservations_weekly_max) - 1 }) | |
else: | |
print(f"Daily/Weekly Max Filled for Reserving Tee time for Date: {tee['date'] } Time: {local_time.strftime('%H:%M %p')} Course: {course_name} Available: {tee['playersAvail']} Fee: ${tee['fee']/100} Booking: {tee['num_player_booking']}") | |
time.sleep(5) | |
for d in drivers: | |
d.quit() | |
write_reservations(reservations) | |
def banner(): | |
banner = """ | |
'\ . . |>18>> | |
\ . ' . | | |
O>> . 'o | | |
\ . | | |
/\ . | | |
/ / .' | | |
jgs^^^^^^^`^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
""" | |
print(banner) | |
def ranger(teetimes): | |
for t in teetimes: | |
print("Date: " + t['date'] + " Time: " + t['teetime'].strftime('%H:%M')) | |
count = 0 | |
while count < 300: | |
if count % 30 : | |
os.system('say -v "Victoria" "TEE TIMES FOUND, HURRY UP"') | |
time.sleep(1) | |
count += 1 | |
def load_reservations(): | |
data = [] | |
if os.path.exists('reservations.json'): | |
f = open('reservations.json') | |
data = json.load(f) | |
f.close() | |
else: | |
data = {'weekly':{}, 'daily':{}} | |
return data | |
def write_reservations(payload): | |
if os.path.exists('reservations.json'): | |
pass | |
else: | |
print("reservations File created.") | |
with open('reservations.json', "w") as outfile: | |
outfile.write(json.dumps(payload)) | |
return | |
def load_defaults(): | |
data = None | |
# Opening JSON file | |
if os.path.exists('config.json'): | |
f = open('config.json') | |
data = json.load(f) | |
f.close() | |
return data | |
def write_defaults(payload): | |
if os.path.exists('config.json'): | |
pass | |
else: | |
print("Default File created.") | |
with open('config.json', "w") as outfile: | |
outfile.write(json.dumps(payload)) | |
return | |
def get_filters(): | |
# load defaults | |
defaults = load_defaults() | |
if defaults is None: | |
print("""" | |
##### Inital Setup: Fill out scorecard #### | |
""") | |
# get round details | |
starting_tee_time = int(input("Enter the earliest hour you can tee off in 24 hour time (ex: 7 for 7 am, 13 for 1 pm): ")) | |
ending_tee_time = int(input("Enter the latest hour you can tee off in 24 hour time (ex: 7 for 7 am, 13 for 1 pm): ")) | |
num_players = int(input("How many players to reserve?: ")) | |
auto_reserve = input("Should I auto reserve for you (y for yes, n for no)?: ") | |
# print out course | |
print("\n### Course's Available ###") | |
[print(key + " - " + COURSEIDS_MAP[key]['name']) for key in COURSEIDS_MAP.keys()] | |
print("A - ALL") | |
courses = input(f"Which course(s) to search?: ") or "A" | |
dow = input("""Which day(s) of the week: | |
1 = Monday | |
2 = Tuesday | |
3 = Wednesday | |
4 = Thursday | |
5 = Friday | |
6 = Saturday | |
7 = Sunday | |
A = ALL | |
Ex: 1,2 for mon/tues or 1 for mon only or A for all): """) or "A" | |
rounds_per_week = int(input("How many rounds to reserve per week? : ") or 1) | |
rounds_per_day = int(input("How many rounds to reserve per day? : ") or 1) | |
if dow == "A" or dow == "a": | |
dow = None | |
else: | |
dow = dow.split(",") | |
if courses == 'A': | |
courses = ",".join(list([f"{COURSEIDS_MAP[key]['id']}" for key in COURSEIDS_MAP.keys()])) | |
else: | |
courses = COURSEIDS_MAP[courses]['id'] | |
else: | |
# use defaults | |
print("Loading defaults") | |
starting_tee_time = defaults['starting_tee_time'] | |
ending_tee_time = defaults['ending_tee_time'] | |
courses = defaults['courses'] | |
dow = defaults['dow'] | |
num_players = defaults['num_players'] | |
rounds_per_week = defaults['rounds_per_week'] | |
rounds_per_day = defaults['rounds_per_day'] | |
auto_reserve = defaults['auto_reserve'] | |
if auto_reserve == 'y': | |
AUTO_RESERVATION = True | |
# Adjust for utc | |
starting_tee_time += 4 | |
ending_tee_time += 4 | |
# get date range for next couple days | |
base = datetime.datetime.today() | |
numdays=10 | |
date_list = [base + datetime.timedelta(days=x) for x in range(numdays)] | |
# figure out tee times | |
all_times = {} | |
for date in date_list: | |
#check filters | |
if dow is None or str(date.isoweekday()) in dow: | |
# get tee times | |
date_str = date.strftime('%Y-%m-%d') | |
print("Working on tee times for day: " + date_str) | |
req = Request(f"https://phx-api-be-east-1b.kenna.io/tee-times?date={date_str}&courseIds={courses}") | |
req.add_header('x-be-alias', 'essex-group') | |
content = urlopen(req).read() | |
info = json.loads(content) | |
tee_times = [] | |
for result in info: | |
tee_times += result['teetimes'] | |
newlist = sorted(tee_times, key=lambda d: datetime.datetime.strptime(d['teetime'], '%Y-%m-%dT%H:%M:%S.%fZ')) | |
all_times[date_str]=newlist | |
time.sleep(1) | |
# filter for acceptable times | |
final_tees = [] | |
for dayslot in all_times: | |
for timeslot in all_times[dayslot]: | |
hour = datetime.datetime.strptime(timeslot['teetime'], '%Y-%m-%dT%H:%M:%S.%fZ').hour | |
playerBookedCount = timeslot['bookedPlayers'] | |
playerAvailCount = timeslot['maxPlayers'] | |
if (hour >= starting_tee_time) \ | |
and (hour < ending_tee_time) \ | |
and playerAvailCount >= num_players: | |
final_tees.append({"date":dayslot, | |
"teetime": datetime.datetime.strptime(timeslot['teetime'], '%Y-%m-%dT%H:%M:%S.%fZ') - datetime.timedelta(hours=4), | |
"playersBooked": playerBookedCount, | |
"playersAvail": playerAvailCount, | |
"course":timeslot['courseId'], | |
"fee": timeslot['rates'][0]['greenFeeWalking'], | |
"num_player_booking":num_players, | |
"rounds_per_day":rounds_per_day, | |
"rounds_per_week":rounds_per_week}) | |
write_defaults({'starting_tee_time':starting_tee_time-4, 'ending_tee_time':ending_tee_time-4, 'courses':courses, 'dow':dow, 'num_players':num_players,"rounds_per_day":rounds_per_day, | |
"rounds_per_week":rounds_per_week, 'auto_reserve':auto_reserve}) | |
return final_tees | |
def runner(): | |
banner() | |
final_tees = get_filters() | |
if len(final_tees) > 0: | |
print("Times Available") | |
for idx,t in enumerate(final_tees): | |
local_time = t['teetime'].astimezone(local_tz) | |
course_name = list( | |
filter(None, | |
[COURSEIDS_MAP[c]['name'] if COURSEIDS_MAP[c]['id'] == t['course'] else '' for c in COURSEIDS_MAP] | |
) | |
)[0] | |
print(f"[{idx}] Date: {t['date'] } Time: {local_time.strftime('%H:%M %p')} Course: {course_name} Available: {t['playersAvail']} Fee: ${t['fee']/100}") | |
do_reserve = 'n' | |
if AUTO_RESERVATION is False: | |
do_reserve = input("Proceed with reservation? , y for yes, n for no: ", "n") | |
if AUTO_RESERVATION or do_reserve == "y": | |
reserve(final_tees) | |
if __name__ == "__main__": | |
runner() |
When plumbing disasters strike, you need a team you can trust to respond fast. AYS Plumbing & Rooter specializes in Upland Emergency Plumbing, providing 24/7 rapid assistance for burst pipes, overflowing drains, water heater failures, and more. With skilled technicians, cutting-edge equipment, and a commitment to excellence, we ensure quick, effective solutions to keep your home or business running smoothly. Don’t let plumbing issues cause unnecessary stress—call AYS Plumbing & Rooter now for immediate, reliable emergency service!
If you're searching for expert contractors in Plymouth MA, look no further than Level 20 Construction. We specialize in high-quality residential and commercial construction, delivering exceptional craftsmanship, innovative solutions, and seamless project management. Whether you're renovating your home, expanding your business, or starting a custom build, our skilled team ensures precision and efficiency at every step. With a strong reputation for reliability and excellence, we bring your vision to life with superior results. Trust Level 20 Construction for all your building needs—visit our website to learn more!
When it comes to expert construction services, Level 20 Construction is a top choice among Plymouth MA general contractors. With a reputation for quality craftsmanship and attention to detail, they specialize in residential and commercial projects, delivering outstanding results tailored to your needs. Whether you're planning a home renovation, a new build, or a commercial upgrade, their skilled team ensures a seamless process from start to finish. Dedicated to excellence and customer satisfaction, Level 20 Construction transforms visions into reality with precision and care. Visit their website today to start your next project with trusted professionals!
When a plumbing emergency strikes, you need a fast and dependable solution. AYS Plumbing & Rooter is your trusted expert for Upland emergency plumbing, providing 24/7 service to handle burst pipes, clogged drains, leaks, and more. Our skilled plumbers respond quickly to minimize damage and restore your plumbing with precision and care. With a reputation for reliability and top-quality workmanship, we ensure your home or business is back to normal in no time. Don’t wait—call us now for immediate assistance!