forked from chinhtle/reserve-tfl
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathreserve_tfl.py
188 lines (145 loc) · 6.81 KB
/
reserve_tfl.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
import threading
import time
from datetime import datetime
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.chrome.options import Options
# Login not required for Tock. Leave it as false to decrease reservation delay
ENABLE_LOGIN = False
TOCK_USERNAME = "SET_YOUR_USER_NAME_HERE"
TOCK_PASSWORD = "SET_YOUR_PASSWORD_HERE"
# Set your specific reservation month and days
RESERVATION_MONTH = 'November'
RESERVATION_DAYS = ['23', '24', '25']
RESERVATION_YEAR = '2021'
RESERVATION_TIME_FORMAT = "%I:%M %p"
# Set the time range for acceptable reservation times.
# I.e., any available slots between 5:00 PM and 8:30 PM
EARLIEST_TIME = "3:00 PM"
LATEST_TIME = "8:30 PM"
RESERVATION_TIME_MIN = datetime.strptime(EARLIEST_TIME, RESERVATION_TIME_FORMAT)
RESERVATION_TIME_MAX = datetime.strptime(LATEST_TIME, RESERVATION_TIME_FORMAT)
# Set the party size for the reservation
RESERVATION_SIZE = 4
# Multithreading configurations
NUM_THREADS = 1
THREAD_DELAY_SEC = 1
RESERVATION_FOUND = False
# Time between each page refresh in milliseconds. Decrease this time to
# increase the number of reservation attempts
REFRESH_DELAY_MSEC = 500
# Chrome extension configurations that are used with Luminati.io proxy.
# Enable proxy to avoid getting IP potentially banned. This should be enabled only if the REFRESH_DELAY_MSEC
# is extremely low (sub hundred) and NUM_THREADS > 1.
ENABLE_PROXY = False
USER_DATA_DIR = '~/Library/Application Support/Google/Chrome'
PROFILE_DIR = 'Default'
# https://chrome.google.com/webstore/detail/luminati/efohiadmkaogdhibjbmeppjpebenaool
EXTENSION_PATH = USER_DATA_DIR + '/' + PROFILE_DIR + '/Extensions/efohiadmkaogdhibjbmeppjpebenaool/1.149.316_0'
# Delay for how long the browser remains open so that the reservation can be finalized. Tock holds the reservation
# for 10 minutes before releasing.
BROWSER_CLOSE_DELAY_SEC = 600
WEBDRIVER_TIMEOUT_DELAY_MS = 3000
MONTH_NUM = {
'january': '01',
'february': '02',
'march': '03',
'april': '04',
'may': '05',
'june': '06',
'july': '07',
'august': '08',
'september': '09',
'october': '10',
'november': '11',
'december': '12'
}
class ReserveTFL():
def __init__(self):
options = Options()
if ENABLE_PROXY:
options.add_argument('--load-extension={}'.format(EXTENSION_PATH))
options.add_argument('--user-data-dir={}'.format(USER_DATA_DIR))
options.add_argument('--profile-directory=Default')
self.driver = webdriver.Chrome(options=options)
def teardown(self):
self.driver.quit()
def reserve(self):
global RESERVATION_FOUND
print("Looking for availability on month: %s, days: %s, between times: %s and %s" % (RESERVATION_MONTH, RESERVATION_DAYS, EARLIEST_TIME, LATEST_TIME))
if ENABLE_LOGIN:
self.login_tock()
while not RESERVATION_FOUND:
time.sleep(REFRESH_DELAY_MSEC / 1000)
self.driver.get("https://www.exploretock.com/tfl/search?date=%s-%s-02&size=%s&time=%s" % (RESERVATION_YEAR, month_num(RESERVATION_MONTH), RESERVATION_SIZE, "22%3A00"))
WebDriverWait(self.driver, WEBDRIVER_TIMEOUT_DELAY_MS).until(expected_conditions.presence_of_element_located((By.CSS_SELECTOR, "div.ConsumerCalendar-month")))
if not self.search_month():
print("No available days found. Continuing next search iteration")
continue
WebDriverWait(self.driver, WEBDRIVER_TIMEOUT_DELAY_MS).until(expected_conditions.presence_of_element_located((By.CSS_SELECTOR, "button.Consumer-resultsListItem.is-available")))
print("Found availability. Sleeping for 10 minutes to complete reservation...")
RESERVATION_FOUND = True
time.sleep(BROWSER_CLOSE_DELAY_SEC)
def login_tock(self):
self.driver.get("https://www.exploretock.com/tfl/login")
WebDriverWait(self.driver, WEBDRIVER_TIMEOUT_DELAY_MS).until(expected_conditions.presence_of_element_located((By.NAME, "email")))
self.driver.find_element(By.NAME, "email").send_keys(TOCK_USERNAME)
self.driver.find_element(By.NAME, "password").send_keys(TOCK_PASSWORD)
self.driver.find_element(By.CSS_SELECTOR, ".Button").click()
WebDriverWait(self.driver, WEBDRIVER_TIMEOUT_DELAY_MS).until(expected_conditions.visibility_of_element_located((By.CSS_SELECTOR, ".MainHeader-accountName")))
def search_month(self):
month_object = None
for month in self.driver.find_elements(By.CSS_SELECTOR, "div.ConsumerCalendar-month"):
header = month.find_element(By.CSS_SELECTOR, "div.ConsumerCalendar-monthHeading")
span = header.find_element(By.CSS_SELECTOR, "span.H1")
print("Encountered month", span.text)
if RESERVATION_MONTH in span.text:
month_object = month
print("Month", RESERVATION_MONTH, "found")
break
if month_object is None:
print("Month", RESERVATION_MONTH, "not found. Ending search")
return False
for day in month_object.find_elements(By.CSS_SELECTOR, "button.ConsumerCalendar-day.is-in-month.is-available"):
span = day.find_element(By.CSS_SELECTOR, "span.B2")
print("Encountered day: " + span.text)
if span.text in RESERVATION_DAYS:
print("Day %s found. Clicking button" % span.text)
day.click()
if self.search_time():
print("Time found")
return True
return False
def search_time(self):
for item in self.driver.find_elements(By.CSS_SELECTOR, "button.Consumer-resultsListItem.is-available"):
span = item.find_element(By.CSS_SELECTOR, "span.Consumer-resultsListItemTime")
span2 = span.find_element(By.CSS_SELECTOR, "span")
print("Encountered time", span2.text)
available_time = datetime.strptime(span2.text, RESERVATION_TIME_FORMAT)
if RESERVATION_TIME_MIN <= available_time <= RESERVATION_TIME_MAX:
print("Time %s found. Clicking button" % span2.text)
item.click()
return True
return False
def month_num(month):
# TODO error handling
return MONTH_NUM[month.lower()]
def run_reservation():
r = ReserveTFL()
r.reserve()
r.teardown()
def execute_reservations():
threads = []
for _ in range(NUM_THREADS):
t = threading.Thread(target=run_reservation)
threads.append(t)
t.start()
time.sleep(THREAD_DELAY_SEC)
for t in threads:
t.join()
def continuous_reservations():
while True:
execute_reservations()
continuous_reservations()