fsp_scripts/make_reservations.py

204 lines
8.0 KiB
Python

from selenium import webdriver
from selenium.webdriver.remote import webelement
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.service import Service as FirefoxService
from selenium.webdriver.firefox.firefox_profile import FirefoxProfile
from webdriver_manager.firefox import GeckoDriverManager
import configparser
import os
import sys
import datetime
import argparse
# TODO factor out common code (defaults, login, etc) from progress_reports script
GECKO_DRIVER = 'gecko'
CHROME_DRIVER = 'chrome'
DEFAULT_CONFIG_PATH = './config.ini'
DEFAULT_WORKING_DIRECTORY = './tmp'
DEFAULT_APP_URL = 'https://app.flightschedulepro.com'
DEFAULT_TIMEOUT = 10 # seconds
DEFAULT_DAY_LOOKAHEAD = 2
parser = argparse.ArgumentParser(
prog='FSP Make Reservations',
description='Reserves flights in advance',
epilog='Kill urself')
parser.add_argument('-c', '--config', default=DEFAULT_CONFIG_PATH)
parser.add_argument('-s', '--show_browser', help='Show browser window', action='store_true')
parser.add_argument('-d', '--days', help='Number of days in advance to place reservations for', default=DEFAULT_DAY_LOOKAHEAD)
parser.add_argument('-D', '--dry_run', help='Disables saving of comments', action='store_true')
args = parser.parse_args()
config = configparser.ConfigParser()
config.read(args.config)
working_dir = config['main'].get('working_directory', DEFAULT_WORKING_DIRECTORY)
app_url = config['main'].get('app_url', DEFAULT_APP_URL)
timeout = config['main'].get('timeout', DEFAULT_TIMEOUT)
tenant_id = config['main'].get('tenant_id')
if tenant_id is None or tenant_id == "":
print('No Tenant ID configured!')
sys.exit(1)
# HACK maybe Ubuntu specific
if config['main'].getboolean('use_working_dir_for_tmp', False):
os.environ["TMPDIR"] = working_dir
driver_type = config['main']['driver']
# TODO only firefox for now
if driver_type == GECKO_DRIVER:
opt = webdriver.FirefoxOptions()
if config['firefox'].get('binary_path') is not None:
opt.binary_location = config['firefox'].get('binary_path')
if not args.show_browser:
opt.add_argument('-headless')
# Force window size to avoid issues with responsive design
opt.add_argument("--width=1920")
opt.add_argument("--height=1080")
# Disable print dialog. Causes race conditions with window switching
firefox_profile = FirefoxProfile()
firefox_profile.set_preference("print.enabled", False)
opt.profile = firefox_profile
driver = webdriver.Firefox(options=opt, service=FirefoxService(GeckoDriverManager().install()))
else:
print(f'unsupported driver type "{driver_type}"')
sys.exit(1)
# optionally prints a message and cleans up before exiting
def die(message=None):
if message is not None:
print(message)
driver.quit()
sys.exit(0)
login_url = app_url + '/Account/Login?company='+ tenant_id
driver.get(login_url)
# wait for stupid cookie modal to load and accept all ¯\_(ツ)_/¯
try:
WebDriverWait(driver, timeout).until(
EC.presence_of_all_elements_located((By.ID, 'onetrust-accept-btn-handler'))
)
cookie_btn = driver.find_element(By.ID, 'onetrust-accept-btn-handler')
cookie_btn.click()
except:
pass
# Login
form = driver.find_element(By.CLASS_NAME, 'account-form')
username = driver.find_element(By.ID, 'username')
username.send_keys(config['main']['username'])
username.submit()
try:
WebDriverWait(driver, timeout).until(
EC.presence_of_all_elements_located((By.ID, 'password'))
)
except TimeoutException:
if len(driver.find_elements(By.XPATH, '//div[@id="alerts"]')) > 0:
die('Bad username')
die('unknown authentication error')
password = driver.find_element(By.ID, 'password')
password.send_keys(config['main']['password'])
password.submit()
# Wait for app to load and naviagte to Reservations "page"
try:
WebDriverWait(driver, timeout).until(
EC.presence_of_all_elements_located((By.CLASS_NAME, 'fsp-main-drawer-content'))
)
except TimeoutException:
if len(driver.find_elements(By.XPATH, '//div[@id="alerts"]')) > 0:
die('Bad password')
die('unknown authentication error')
today = datetime.datetime.today()
def get_reservation_rows():
driver.get(app_url + '/App/Reservations')
# Wait for "Future" button and click
WebDriverWait(driver, timeout).until(
EC.presence_of_all_elements_located((By.XPATH, '//a[@id="mat-tab-link-1"]'))
)
driver.find_element(By.XPATH, '//a[@id="mat-tab-link-1"]').click()
#Wait for rows to load
WebDriverWait(driver, timeout).until(
EC.presence_of_all_elements_located((By.XPATH, '//tr[contains(@class, "cdk-row")]'))
)
reservation_rows = driver.find_elements(By.XPATH, '//tr[contains(@class, "cdk-row")]')
reservation_rows = list(filter(filter_row_by_date, reservation_rows))
return reservation_rows
def filter_row_by_date(reservation_row: webelement.WebElement) -> bool:
instructor = reservation_row.find_element(By.XPATH, './td[contains(@class, "cdk-column-instructor")]').get_attribute("innerText")
# Double check we're only commenting on your reservations. Out of caution abort if we see any other instructors
if "Jason Vitale" not in instructor:
die("WARNING: retrieved reservation for another instructor. Aborting...")
return False
date_str = reservation_row.find_element(By.XPATH, './td[contains(@class, "cdk-column-date")]').get_attribute("innerText")
date = datetime.datetime.strptime(date_str, '%a, %b %d, %Y')
if date.date() == (today + datetime.timedelta(days=args.days)).date():
return True
return False
rows = get_reservation_rows()
print('found ' + str(len(rows)) + ' reservations to add reservation comment to')
for i in range(0, len(rows)):
row = rows[i]
student_name = row.find_element(By.XPATH, './td[contains(@class, "cdk-column-Customer")]').get_attribute("innerText")
reservation_date = row.find_element(By.XPATH, './td[contains(@class, "cdk-column-date")]').get_attribute("innerText")
print('adding reservation comment for student ' + student_name + ' on ' + reservation_date)
row.find_element(By.XPATH, './/button[contains(@class, "fsp-button")]').click()
WebDriverWait(driver, timeout).until(
EC.presence_of_all_elements_located((By.ID, 'ReservationModal-StandardViewCtrl')),
)
modal = driver.find_element(By.ID, 'ReservationModal-StandardViewCtrl')
#Wait for modal/comments sections to load
WebDriverWait(driver, timeout).until(
EC.presence_of_all_elements_located((By.XPATH, '//div[contains(@ng-model, "reservationDetails.comments")]')),
)
WebDriverWait(driver, timeout).until(
EC.presence_of_all_elements_located((By.XPATH, '//a[contains(@ng-click, "editComment()")]')),
)
comments = modal.find_element(By.XPATH, './/div[contains(@ng-model, "reservationDetails.comments")]')
edit_btn = comments.find_element(By.XPATH, './/a[contains(@ng-click, "editComment()")]')
WebDriverWait(driver, timeout).until(
EC.element_to_be_clickable(edit_btn)
)
edit_btn.click()
#Wait for modal/comments sections to load
WebDriverWait(driver, timeout).until(
EC.presence_of_all_elements_located((By.ID, 'comments'))
)
textarea = comments.find_element(By.ID, 'comments')
textarea.clear()
comment_text = "c172 " + today.strftime('%d %b, %H:%M')
print('comment text: ' + comment_text)
textarea.send_keys(comment_text)
if args.dry_run is False:
save_btn = comments.find_element(By.XPATH, './/button[contains(@ng-click, "save()")]')
WebDriverWait(driver, timeout).until(
EC.element_to_be_clickable(save_btn)
)
print("saving reservation comment")
save_btn.click()
driver.find_element(By.XPATH, '//button[contains(@ng-click, "dismissModal()")]').click()
rows = get_reservation_rows()
die()