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()