commit 83cfa157232ca2bcb7bd016fd57fa8a51c535093 Author: gregory Date: Wed Mar 12 16:22:45 2025 -0400 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fe66201 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.venv +tmp +output \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/config.ini b/config.ini new file mode 100644 index 0000000..06c5b26 --- /dev/null +++ b/config.ini @@ -0,0 +1,6 @@ +[main] +username = +password = +driver = gecko +output_directory = ./output +tenant_id = \ No newline at end of file diff --git a/save_progress_reports.py b/save_progress_reports.py new file mode 100644 index 0000000..1dac0a1 --- /dev/null +++ b/save_progress_reports.py @@ -0,0 +1,163 @@ +from selenium import webdriver +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.common.by import By +from selenium.webdriver.common.print_page_options import PrintOptions +from selenium.webdriver.firefox.service import Service as FirefoxService +from selenium.webdriver.firefox.firefox_profile import FirefoxProfile +from webdriver_manager.firefox import GeckoDriverManager +from pypdf import PdfWriter +import configparser +import os +import base64 +import errno +import glob + +GECKO_DRIVER = 'gecko' +CHROME_DRIVER = 'chrome' + + +# HACK +os.environ["TMPDIR"] = "./tmp" + +config = configparser.ConfigParser() + +config.read('./config.ini') +output_dir = config['main']['output_directory'] +try: + os.makedirs(output_dir) +except OSError as exception: + files = glob.glob(os.path.join(output_dir, "*.pdf")) + for f in files: + os.remove(f) + if exception.errno != errno.EEXIST: + raise + +driver_type = config['main']['driver'] +# TODO only firefox for now +if driver_type == GECKO_DRIVER: + opt = webdriver.FirefoxOptions() + opt.binary_location = "/usr/bin/firefox" + opt.add_argument("-headless") # Here + 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(format('unsupported driver type "%s"', driver_type)) + os.exit(1) + +url = 'https://app.flightschedulepro.com/Account/Login?company='+ config['main']['tenant_id'] +driver.get(url) + +# wait for stupid cookie modal to load and accept all ¯\_(ツ)_/¯ +try: + WebDriverWait(driver, 10).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() + +WebDriverWait(driver, 10).until( + EC.presence_of_all_elements_located((By.ID, 'password')) +) +password = driver.find_element(By.ID, 'password') +password.send_keys(config['main']['password']) + +password.submit() + +# Wait for app to load and naviagte to students "page" +WebDriverWait(driver, 10).until( + EC.presence_of_all_elements_located((By.CLASS_NAME, 'fsp-main-drawer-content')) +) +driver.get('https://app.flightschedulepro.com/App/Students') +WebDriverWait(driver, 10).until( + EC.presence_of_all_elements_located((By.CLASS_NAME, 'clickable-course')) +) + +# Get all the student rows and iterate +students = driver.find_elements(By.CLASS_NAME, 'clickable-course') +student_count = len(students) + +print(f'processing {student_count} students') + +original_window = driver.current_window_handle +for i in range(0, student_count): + # Need to reload student elements after returning to page + students = driver.find_elements(By.CLASS_NAME, 'clickable-course') + if len(students) is not student_count: + raise 'student count changed. aborting...' + student = students[i] + student_name = student.find_element(By.XPATH, '//div[@class="student"]/div[@class="bold"]').get_attribute("innerText") + student_name_no_space = student_name.replace(' ', '_') + course_td = student.find_element(By.CLASS_NAME, 'course-td') + course_td.click() + WebDriverWait(driver, 10).until( + EC.presence_of_all_elements_located((By.XPATH, '//span[text()[contains(., "Sessions")]]')) + ) + sessions_tab = driver.find_element(By.XPATH, '//span[text()[contains(., "Sessions")]]') + sessions_tab.click() + WebDriverWait(driver, 10).until( + EC.presence_of_all_elements_located((By.XPATH, '//fsp-ui-button[@Text="View"]')) + ) + # Ugh... Sessions Table reloads on modal opening after the first modal is opened. So we have to record + # the count of sessions initially and iterate while reloading the elements each time a button is clicked + view_btns = driver.find_elements(By.XPATH, '//fsp-ui-button[@Text="View"]') + btn_count = len(view_btns) + pdf_count = 0 + print(f'Downloading {btn_count} sessions for student "{student_name}"') + for j in range(0, btn_count): + view_btns = driver.find_elements(By.XPATH, '//fsp-ui-button[@Text="View"]') + if len(view_btns) is not btn_count: + raise 'session count changed. aborting...' + view_btns[j].click() + WebDriverWait(driver, 10).until( + EC.presence_of_all_elements_located((By.XPATH, '//fsp-ui-button[@Text="Print"]')) + ) + print_btn = driver.find_element(By.XPATH, '//fsp-ui-button[@Text="Print"]') + print_btn.click() + + # Wait for the new window or tab + WebDriverWait(driver, 10).until(EC.number_of_windows_to_be(2)) + + # Loop through until we find a new window handle + for window_handle in driver.window_handles: + if window_handle != original_window: + driver.switch_to.window(window_handle) + break + # Wait for the new tab to finish loading content + WebDriverWait(driver, 10).until(EC.title_is('Print Training Session')) + print_options = PrintOptions() + print_options.orientation = "portrait" + pdf = driver.print_page(print_options) + with open(os.path.join(output_dir, student_name_no_space + "{:03d}".format(pdf_count) + '.pdf'), 'wb') as file: + file.write(base64.decodebytes(pdf.encode('utf-8'))) + driver.close() + + driver.switch_to.window(original_window) + WebDriverWait(driver, 10).until(EC.title_is('Flight Schedule Pro')) + close_btn = driver.find_element(By.XPATH, '//fsp-ui-button[@Text="Close"]') + close_btn.click() + pdf_count = pdf_count + 1 + # Merge all Session PDFs for the student + print(f'Merging PDFs into "{student_name_no_space}.pdf"') + driver.close() + writer = PdfWriter() + + pdfs = [a for a in os.listdir(output_dir) if a.endswith(".pdf") and a.startswith(student_name_no_space)] + pdfs.sort() + + for pdf in pdfs: + writer.append(os.path.join(output_dir, pdf)) + + writer.write(student_name_no_space + '.pdf') + writer.close() +