diff --git a/.gitignore b/.gitignore index 0dbf2f2..69f6bcc 100644 --- a/.gitignore +++ b/.gitignore @@ -168,3 +168,5 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +pdfs/ +export/ \ No newline at end of file diff --git a/README.md b/README.md index 5d9de44..f929eb3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,39 @@ # moodle_quiz_pdf_export -Kleines Script, um Moddle Quizzes/Tests von Studierenden automatisiert als PDF zu exportieren. \ No newline at end of file +Kleines Script, um Moddle Quizzes/Tests von Studierenden automatisiert als PDF zu exportieren. + +# Installation +Requirements installieren: +``` +pip install -r requirements.txt +``` + +# Konfiguration +Eine Textdatei `.env` im Hauptverzeichnis anlegen mit folgenden Inhalten und entsprechend anpassen: +``` +MOODLE_URL="https://elearning.ohmportal.de" +QUIZ_ATTEMPTS_URL="URL der Ergebnisübersicht des Tests" +USERNAME="Moodle Username" +PASSWORD="Moodle Passwort" +PDF_OUTPUT_DIR="Gewünschtes Ausgabeverzeichnis" +``` + +**Hinweis:** `QUIZ_ATTEMPTS_URL` sollte die vollständige URL der Ergebnisübersicht des Moodle Tests sein, ggf. inklusive der gewünschten Filter (z.B. Gruppe) sowie einer Seitengröße, die alle Versuche anzeigt. Das Skript extrahiert die Versuche der Studierenden aus dieser Seite, d.h. es werden genau diejenigen Versuche exportiert, die bei Aufruf der URL in der Tabelle sichtbar sind. + +# Ausführen +Das Skript ausführen. +``` +python moodle_quiz_pdf_export.py +``` + +Es sollte eine Ausgabe ähnlich wie unten erscheinen und die PDF-Dateien im gewünschten Ordner gespeichert werden. + +``` +Found 53 attempt URLs. +[1/53] Downloading: https://elearning.ohmportal.de/mod/quiz/review.php?attempt=241863 +[2/53] Downloading: https://elearning.ohmportal.de/mod/quiz/review.php?attempt=241865 +[3/53] Downloading: https://elearning.ohmportal.de/mod/quiz/review.php?attempt=241866 +... +[53/53] Downloading: https://elearning.ohmportal.de/mod/quiz/review.php?attempt=241948 +✅ All PDFs downloaded. +``` \ No newline at end of file diff --git a/moodle_quiz_pdf_export.py b/moodle_quiz_pdf_export.py new file mode 100644 index 0000000..4292de0 --- /dev/null +++ b/moodle_quiz_pdf_export.py @@ -0,0 +1,93 @@ +from selenium import webdriver +from selenium.common.exceptions import NoSuchElementException +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.common.by import By +import time +import os +import base64 +import dotenv + +# Load configuration variables from .env File +dotenv.load_dotenv() +MOODLE_URL = os.getenv('MOODLE_URL') +QUIZ_ATTEMPTS_URL = os.getenv('QUIZ_ATTEMPTS_URL') +USERNAME = os.getenv('USERNAME') +PASSWORD = os.getenv('PASSWORD') +PDF_OUTPUT_DIR = os.getenv('PDF_OUTPUT_DIR') + +# Ensure output directory exists +os.makedirs(PDF_OUTPUT_DIR, exist_ok=True) + +# Chrome options for headless PDF printing +chrome_options = Options() +chrome_options.add_argument('--headless=new') # use --headless=new for Chrome v112+ +chrome_options.add_argument('--disable-gpu') +chrome_options.add_argument('--kiosk-printing') +chrome_prefs = { + "printing.print_preview_sticky_settings.appState": '{"recentDestinations":[{"id":"Save as PDF","origin":"local"}],"selectedDestinationId":"Save as PDF","version":2}', + "savefile.default_directory": PDF_OUTPUT_DIR +} +chrome_options.add_experimental_option("prefs", chrome_prefs) +chrome_options.add_argument('--disable-popup-blocking') +chrome_options.add_argument('--no-sandbox') +chrome_options.add_argument('--print-to-pdf-no-header') + +# Start driver +driver = webdriver.Chrome(options=chrome_options) + +# --- LOGIN --- +driver.get(MOODLE_URL) +driver.find_element(By.ID, "username").send_keys(USERNAME) +driver.find_element(By.ID, "password").send_keys(PASSWORD) +driver.find_element(By.ID, "loginbtn").click() +time.sleep(2) + +# --- GO TO QUIZ RESULTS PAGE --- +driver.get(QUIZ_ATTEMPTS_URL) +time.sleep(3) + +# --- SCRAPE ATTEMPT LINKS --- +attempt_links = [] +elements = driver.find_elements(By.CSS_SELECTOR, "a[href*='review.php?attempt=']") +for elem in elements: + url = elem.get_attribute("href") + if url and url not in attempt_links: + attempt_links.append(url) + +print(f"Found {len(attempt_links)} attempt URLs.") + +# --- HELPER: Save PDF from current page --- +def save_pdf_from_current_page(filename): + # Call DevTools Protocol directly + result = driver.execute_cdp_cmd("Page.printToPDF", { + "format": "A4", + "printBackground": True + }) + pdf_data = base64.b64decode(result['data']) + with open(filename, "wb") as f: + f.write(pdf_data) + +# --- DOWNLOAD EACH AS PDF --- +for idx, url in enumerate(attempt_links, start=1): + print(f"[{idx}/{len(attempt_links)}] Downloading: {url}") + driver.get(url) + time.sleep(2) + + try: + # Try to get the student's name near the profile picture + student_name_elem = driver.find_element(By.CSS_SELECTOR, "td.cell a[href*='/user/view.php']") + student_name = student_name_elem.text.strip() + except NoSuchElementException: + # Fallback if name is not found + student_name = f"student_{idx}" + + # Clean the name for filename use + safe_name = "".join(c for c in student_name if c.isalnum() or c in ('_', '-')) + + filename = os.path.join(PDF_OUTPUT_DIR, f"{safe_name}.pdf") + + save_pdf_from_current_page(filename) + +print("✅ All PDFs downloaded.") + +driver.quit() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2288f3e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +selenium +dotenv \ No newline at end of file