-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathclock_manager.py
339 lines (271 loc) · 12.1 KB
/
clock_manager.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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
import sys
import time
import os
import argparse
import getpass
# This is just to prevent annoying debugging messages so that you can clearly see what the program is doing in the console.
import logging
logging.getLogger("urllib3").setLevel(logging.ERROR)
from selenium import webdriver
import chromedriver_autoinstaller
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import ElementNotInteractableException
#=================================================================================================#
# This block sets up everything that needs user input
DEFAULT_HOURS_TO_CLOCK = 0.15
parser = argparse.ArgumentParser(description='OneUSGAutomaticClock')
parser.add_argument('-u', '--username', help="GT Username", required=True)
parser.add_argument('-hrs', '--hours', type=float, help="Hours to clock", required=False, default=DEFAULT_HOURS_TO_CLOCK)
args = vars(parser.parse_args())
USERNAME = args['username']
PASSWORD = getpass.getpass(prompt='GT Password: ', stream=None)
HOURS_TO_CLOCK = args['hours']
if HOURS_TO_CLOCK < DEFAULT_HOURS_TO_CLOCK:
print(f"[NOTE] Hours less than {DEFAULT_HOURS_TO_CLOCK} unsupported")
print(f"[NOTE] See issue: https://github.com/Shaun-Regenbaum/OneUSGAutomaticClock/issues/6")
print(f"[NOTE] Using {DEFAULT_HOURS_TO_CLOCK} hours")
if PASSWORD == "":
print("Be sure to set your password.\n")
sys.exit(1)
# (By the way for newcomers, variables with CAPITAL LETTERS imply they are a global variable.)
#=================================================================================================#
#=================================================================================================#
# You shouldn't need to change anything past this point (besides duo 2fa), but you do you
#=================================================================================================#
#=================================================================================================#
# Global Variables:
chromedriver_autoinstaller.install()
MINUTES = HOURS_TO_CLOCK * 60
TIME_BLOCKS = round(MINUTES / 15)
BLOCKS_DONE = 0
DRIVER = webdriver.Chrome()
WAIT = WebDriverWait(DRIVER, 25)
MINI_WAIT = WebDriverWait(DRIVER, 5)
DEBUG_MODE = False
#=================================================================================================#
# Functions, each step gets its own function:
# This is a function to try go straight to the GT clock's iframe (IN ACTIVE USE):
def goToGTClock():
DRIVER.get("https://selfservice.hprod.onehcm.usg.edu/psc/hprodsssso_newwin/HCMSS/HRMS/c/TL_EMPLOYEE_FL.TL_RPT_TIME_FLU.GBL?EMPDASHBD=Y&tW=1&tH=1&ICDoModeless=1&ICGrouplet=3&bReload=y&nWidth=236&nHeight=163&TL_JOB_CHAR=0")
return True
# Selecting GT:
def selectGT():
# I was encountering an error occasionally, so I made a hotfix:
gt_option = WAIT.until(EC.element_to_be_clickable((By.XPATH,
"//*[@id='https_idp_gatech_edu_idp_shibboleth']/div/div/a/img")))
gt_option.click()
return checkExistence(element_to_find="username", method_to_find="name", purpose="Selecting GT")
# This function logs us in once we are at the GT login Page:
def loginGT():
gatech_login_username = DRIVER.find_element_by_name("username")
gatech_login_password = DRIVER.find_element_by_name("password")
gatech_login_username.send_keys(USERNAME)
gatech_login_password.send_keys(PASSWORD)
submit_button = DRIVER.find_element_by_name("submit")
submit_button.click()
print("...")
print("...")
print("Script will wait for you to authenticate on duo")
print("If you run out of time, just run the script again")
print("...")
# In order to wait for Duo, we will run small while loop:
waiting = True
while waiting:
time.sleep(5)
waiting = checkExistence(
element_to_find="duo_form", purpose="Checking Duo Auth", passOnError=True)
print("Waiting for Duo")
time.sleep(1)
DRIVER.refresh()
time.sleep(1)
checkExistence(element_to_find="TL_RPTD_SFF_WK_GROUPBOX$PIMG",
purpose="Logging In")
# This function clocks us in:
def clockHoursIn():
# We open the menu and clock clock in
openMenu()
clock_in_button = DRIVER.find_element_by_id("TL_RPTD_SFF_WK_TL_ACT_PUNCH1")
clock_in_button.send_keys(Keys.RETURN)
double_clock_handler()
print("You Have Clocked In, Be Careful That Your Computer Does Not Turn Off.")
print("...")
return checkExistence(element_to_find="TL_RPTD_SFF_WK_GROUPBOX$PIMG", purpose="Clocking In")
# This function clocks us out:
def clockHoursOut():
openMenu()
WAIT.until(lambda DRIVER: DRIVER.find_element_by_id(
"TL_RPTD_SFF_WK_TL_ACT_PUNCH3"))
clock_out_button = DRIVER.find_element_by_id(
"TL_RPTD_SFF_WK_TL_ACT_PUNCH3")
clock_out_button.send_keys(Keys.RETURN)
last_action_text = DRIVER.find_element_by_id(
"TL_WEB_CLOCK_WK_DESCR50_1").get_attribute("innerHTML")
try:
WAIT.until(
lambda d: "Out" in d.find_element_by_id("TL_WEB_CLOCK_WK_DESCR50_1").get_attribute("innerHTML")
)
print("You Have Clocked Out")
DRIVER.quit()
except:
print("Failed, Unable to Clock Out.")
print("NOTICE: Please Manually Clock Out To Avoid Issues")
print("If this error continues, please raise an issue on Github")
print("...")
# This function checks to make sure you did duo correctly:
def checkLogin():
checkExistence("TL_RPTD_SFF_WK_GROUPBOX$PIMG")
# This function opens the clocing menu:
def openMenu():
checkExistence("TL_RPTD_SFF_WK_GROUPBOX$PIMG")
clock_menu = DRIVER.find_element_by_id("TL_RPTD_SFF_WK_GROUPBOX$PIMG")
clock_menu.send_keys(Keys.RETURN)
return checkExistence(element_to_find="TL_RPTD_SFF_WK_TL_ACT_PUNCH1", purpose="Clocking In")
# This function prevents timeouts:
def prevent_timeout():
DRIVER.refresh()
# If the timeout box does appear, click the prevent timeout button
try:
timeout_button = DRIVER.find_element_by_id("BOR_INSTALL_VW$0_row_0")
timeout_button.send_keys(Keys.RETURN)
print("Timeout Prevented")
print("...")
return True
except (NoSuchElementException, TimeoutException):
return True
except Exception as error:
if not DEBUG_MODE:
print("...")
print("Something Unknown Happened, Please Manually Clock out!")
print(
"Please Raise an Issue on Github and say the error is in the Timeout Prevention")
DRIVER.quit()
else:
print("Debug Mode:")
print(error)
return False
# This function checks to see if the popup for double-clocking comes up
def double_clock_handler():
try:
MINI_WAIT.until(lambda DRIVER: DRIVER.find_element_by_id("#ICOK"))
popup_button = DRIVER.find_element_by_id("#ICOK")
popup_button.send_keys(Keys.RETURN)
MINI_WAIT.until(lambda DRIVER: DRIVER.find_element_by_id(
"PT_WORK_PT_BUTTON_BACK"))
back_button = DRIVER.find_element_by_id("PT_WORK_PT_BUTTON_BACK")
back_button.send_keys(Keys.RETURN)
print("You were about to double clock, we prevented that.")
return True
except (NoSuchElementException, TimeoutException):
return False
except Exception as error:
if not DEBUG_MODE:
print("...")
print("Something Unknown Happened, Please Manually Clock out!")
print(
"Please Raise an Issue on Github and say the error is in the Double Clock Handler")
DRIVER.quit()
else:
print("Debug Mode:")
print(error)
return False
# This function handles errors and returns either true or false to indicate success or failure:
# There are probably a lot more cases to handle, but its fine for now.
def checkExistence(element_to_find, method_to_find="id", purpose="Default, Please Specify when Invoking checkExistence", passOnError=False):
try:
if method_to_find == "xpath":
MINI_WAIT.until(
lambda DRIVER: DRIVER.find_element_by_xpath(element_to_find))
return True
elif method_to_find == "id":
MINI_WAIT.until(
lambda DRIVER: DRIVER.find_element_by_id(element_to_find))
return True
elif method_to_find == "name":
MINI_WAIT.until(
lambda DRIVER: DRIVER.find_element_by_name(element_to_find))
return True
else:
print("method_to_find not right")
if not DEBUG_MODE and not passOnError:
DRIVER.quit()
else:
print("Debug Mode:")
return False
except (NoSuchElementException, TimeoutException, ElementNotInteractableException) as error:
if not DEBUG_MODE and not passOnError:
print("This Element: " + element_to_find + " ")
print("was not found, this means OneUsg made some changes.")
print("This element is associated with " + purpose + ". ")
print("If this error continues, please raise an issue on Github.")
print("...")
DRIVER.quit()
else:
print("Element: " + element_to_find)
print("Purpose: " + purpose)
print("Debug Mode:")
print(error)
return False
except (RuntimeError, TypeError, NameError) as error:
if not DEBUG_MODE and not passOnError:
DRIVER.quit()
else:
print("Debug Mode:")
print(error)
return False
except Exception as error:
print("...")
print("Please Raise an Issue on Github!")
print("Failure with: " + purpose)
if not DEBUG_MODE and not passOnError:
DRIVER.quit()
else:
print("Debug Mode:")
print(error)
return False
#=================================================================================================#
# The script running:
print('\nClocking {0} hours...\n'.format(HOURS_TO_CLOCK))
goToGTClock()
selectGT()
loginGT()
clockHoursIn()
# This is a little loop to make sure we prevent timeouts and to keep track of how long its been
# It just refreshes the page every fifteen minutes and keeps track of how much time has passed.
while BLOCKS_DONE < TIME_BLOCKS:
print(str(BLOCKS_DONE*15) + " minutes done, roughly " +
str(MINUTES - BLOCKS_DONE*15) + " minutes left to go.")
print("...")
prevent_timeout()
for i in range(15):
# This should be 60 for a full minute, but Im accounting for slow downs in other places.
time.sleep(59)
print(BLOCKS_DONE*15 + i)
BLOCKS_DONE = BLOCKS_DONE + 1
if BLOCKS_DONE == TIME_BLOCKS:
clockHoursOut()
break
# This is just another safety check to make sure we don't ever leave without clocking out first.
else:
try:
clockHoursOut()
except:
print("Make sure you were clocked out please.")
#=================================================================================================#
# DEPRECATED STUFF (may want to use in the future:)
# Go To One USG (DEPRECATED):
# def goToOneUSG():
# DRIVER.get("https://hcm-sso.onehcm.usg.edu/")
# return checkExistence(element_to_find="//*[@id='https_idp_gatech_edu_idp_shibboleth']/div/div/a/img", method_to_find="xpath", purpose="Going to One Usg")
# # This function goes through the menus to click the Time and Absence button (DEPRECATED):
# def goToClock():
# WAIT.until(lambda DRIVER: DRIVER.find_element_by_id(
# "win0divPTNUI_LAND_REC_GROUPLET$7"))
# time_and_absence_button = DRIVER.find_element_by_id(
# "win0divPTNUI_LAND_REC_GROUPLET$7")
# time_and_absence_button.send_keys(Keys.RETURN)
# return checkExistence(element_to_find="win0groupletPTNUI_LAND_REC_GROUPLET$3_iframe", purpose="Going to the Clock")