kidsnote-backup/get_report.py
Jeena 0ada2d4858 Fix bugs and improve robustness of report fetching
- Fix missing 'import sys' in get_report.py that caused a NameError
  on the credential-check error path
- Fix indentation of the per-report print statement in
  report_json_down.py so it prints for every report, not just the
  last one per date
- Replace hardcoded X-ENROLLMENT header value with dynamic extraction
  from selenium-wire intercepted requests
- Replace fragile time.sleep() calls with WebDriverWait so the script
  waits for actual page/login readiness rather than fixed delays
- Remove album_json_down.py, a Windows-only legacy script inherited
  from upstream that was never used in this fork
2026-03-18 00:37:35 +00:00

103 lines
3.5 KiB
Python
Executable file

#!/usr/bin/env python3
import os
import sys
import re
import requests
from dotenv import load_dotenv
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from seleniumwire import webdriver
load_dotenv()
USERNAME = os.getenv("KIDSNOTE_USERNAME")
PASSWORD = os.getenv("KIDSNOTE_PASSWORD")
if not USERNAME or not PASSWORD:
print("Error: KIDSNOTE_USERNAME and/or KIDSNOTE_PASSWORD is not set.")
sys.exit(1)
login_url = "https://www.kidsnote.com/en/login"
def extract_child_id_from_requests(requests) -> str | None:
"""Extract the first child_id found in request URLs."""
for request in requests:
if match := re.search(r'/children/(\d+)/reports', request.url):
return match.group(1)
return None
def extract_enrollment_from_requests(requests) -> str | None:
"""Extract the X-ENROLLMENT header value from intercepted API requests."""
for request in requests:
if '/api/' in request.url and request.headers.get('X-ENROLLMENT'):
return request.headers['X-ENROLLMENT']
return None
# Use headless browser if you don't need to see it
chrome_options = Options()
chrome_options.add_argument("--headless=new")
chrome_options.add_argument("--no-sandbox")
driver = webdriver.Chrome(options=chrome_options)
driver.get(login_url)
# Wait for login form to be ready
wait = WebDriverWait(driver, 30)
wait.until(EC.element_to_be_clickable((By.NAME, "username")))
# Fill in login form
driver.find_element(By.NAME, "username").send_keys(USERNAME)
driver.find_element(By.NAME, "password").send_keys(PASSWORD)
driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
# Wait until the browser makes an authenticated API call (child reports endpoint)
wait.until(lambda d: extract_child_id_from_requests(d.requests) is not None)
child_id = extract_child_id_from_requests(driver.requests)
enrollment = extract_enrollment_from_requests(driver.requests)
report_url = f"https://www.kidsnote.com/api/v1_2/children/{child_id}/reports/?page_size=5000"
# Extract cookies
cookies = driver.get_cookies()
driver.quit()
if not child_id:
print("Error: could not determine child ID from intercepted requests.")
sys.exit(1)
if not enrollment:
print("Error: could not extract X-ENROLLMENT header from intercepted requests.")
sys.exit(1)
with requests.Session() as session:
# Convert cookies for requests
for cookie in cookies:
session.cookies.set(cookie['name'], cookie['value'])
headers = {
"Accept": "application/json, text/plain, */*",
"Accept-Encoding": "gzip, deflate, br, zstd",
"Accept-Language": "en-US,en;q=0.5",
"Cache-Control": "no-cache, no-store",
"Connection": "keep-alive",
"DNT": "1",
"Host": "www.kidsnote.com",
"Pragma": "no-cache",
"Referer": "https://www.kidsnote.com/service/report",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0",
"X-ENROLLMENT": enrollment,
}
report_response = session.get(report_url, headers=headers)
if report_response.ok:
with open("report.json", "w", encoding="utf-8") as f:
f.write(report_response.text)
else:
print("Failed to get report:", report_response.status_code)