""" ======================================================================= Název: download_all_inbox_eml_v1.0.py Verze: 1.0 Datum: 2026-06-03 Popis: Stáhne zprávy z Outlook Inboxu jako .eml. Virtualizovaný seznam řeší navigací klávesnicí (ArrowDown) — Outlook sám scrolluje a dorenderovává. Postup: 1. vybrat první zprávu 2. stáhnout vybranou (pravý klik → Download → Download as EML) 3. ArrowDown na další 4. opakovat, dokud se výběr (aria-selected) přestane hýbat Používá persistent profil z outlook_login_v1.0.py. Nastavení: LIMIT – max počet zpráv (None = celý Inbox) SKIP_EXISTING – přeskočit zprávy, jejichž EML už v downloads/ existuje ======================================================================= """ import re from pathlib import Path from playwright.sync_api import sync_playwright BASE_DIR = Path(__file__).resolve().parent PROFILE_DIR = BASE_DIR / "outlook_profile" OUT_DIR = BASE_DIR / "downloads" START_URL = "https://outlook.cloud.microsoft/mail/" LIMIT = 30 # max počet zpráv; None = celý Inbox SKIP_EXISTING = False # False = existující stejný soubor přepsat (smazat + uložit nový) def safe_name(name: str) -> str: """Očistí název pro filesystem (Windows).""" name = re.sub(r'[<>:"/\\|?*\r\n\t]', "_", name).strip().strip(".") return name[:150] or "message" def download_selected(page, out_dir: Path) -> Path | None: """Pravý klik na vybranou zprávu → Download as EML. Vrátí cestu nebo None.""" selected = page.locator('[role="option"][aria-selected="true"]').first if selected.count() == 0: return None selected.click(button="right") page.wait_for_timeout(600) # Download (rodič submenu) download_parent = None for name in ("Download", "Stáhnout"): loc = page.get_by_role("menuitem", name=name).first if loc.count() and loc.is_visible(): download_parent = loc break if download_parent is None: page.keyboard.press("Escape") return None download_parent.hover() page.wait_for_timeout(500) # Download as EML (submenu); fallback = klik přímo na Download eml_item = None for name in ("Download as EML", "Stáhnout jako EML", "Stáhnout jako .eml"): loc = page.get_by_role("menuitem", name=name).first if loc.count() and loc.is_visible(): eml_item = loc break try: if eml_item is not None: with page.expect_download(timeout=15_000) as dl: eml_item.click() else: with page.expect_download(timeout=15_000) as dl: download_parent.click() download = dl.value except Exception: page.keyboard.press("Escape") return None fname = safe_name(download.suggested_filename or "message.eml") if not fname.lower().endswith(".eml"): fname += ".eml" target = out_dir / fname if target.exists(): if SKIP_EXISTING: return target # už máme — neukládat znovu target.unlink() # přepsat: smazat starou verzi a uložit novou download.save_as(str(target)) return target def main() -> None: if not PROFILE_DIR.exists(): print(f" Profil nenalezen: {PROFILE_DIR}") print(" Nejprve spusť outlook_login_v1.0.py.") return OUT_DIR.mkdir(exist_ok=True) with sync_playwright() as p: context = p.chromium.launch_persistent_context( user_data_dir=str(PROFILE_DIR), headless=False, no_viewport=True, accept_downloads=True, args=[ "--disable-blink-features=AutomationControlled", "--start-maximized", ], ) page = context.pages[0] if context.pages else context.new_page() # 1) Otevřít Outlook print(" 1/4 Otevírám Outlook...") page.goto(START_URL) page.wait_for_load_state("domcontentloaded") search_selector = ( '[placeholder*="Search"], [aria-label*="Search"], ' '[placeholder*="Hledat"], [aria-label*="Hledat"]' ) page.wait_for_selector(search_selector, timeout=30_000) # 2) Inbox / Doručená pošta print(" 2/4 Otevírám Inbox...") inbox_candidates = [ 'div[role="treeitem"]:has-text("Inbox")', 'div[role="treeitem"]:has-text("Doručená pošta")', 'text=Inbox', 'text=Doručená pošta', ] for sel in inbox_candidates: loc = page.locator(sel).first if loc.count() and loc.is_visible(): loc.click() break page.wait_for_selector('div[role="option"]', timeout=15_000) page.wait_for_timeout(1000) # 3) Vybrat první zprávu print(" 3/4 Vybírám první zprávu...") page.locator('div[role="option"]').first.click() page.wait_for_timeout(800) # 4) Smyčka: stáhni vybranou → ArrowDown → dokud se výběr hýbe # Pozn.: oddělovače sekcí (Today/Yesterday/...) jsou role="button" # aria-expanded — kurzor na nich ZASTAVÍ a žádná zpráva nemá # aria-selected (selected.count()==0). Takový krok jen přeskočíme # (ArrowDown dál), NEpočítáme ho a NEukončujeme smyčku. print(" 4/4 Stahuji zprávy...\n") saved = 0 dividers = 0 failed = 0 prev_label = None no_progress = 0 # kolikrát po sobě se výběr neposunul NO_PROGRESS_MAX = 4 # tolik = konec seznamu / zaseknutí while LIMIT is None or saved < LIMIT: selected = page.locator('[role="option"][aria-selected="true"]').first # (a) stojíme na oddělovači sekce → krok přes něj if selected.count() == 0: dividers += 1 no_progress += 1 if no_progress >= NO_PROGRESS_MAX: print(" Konec seznamu / zaseknutí — končím.") break page.keyboard.press("ArrowDown") page.wait_for_timeout(250) continue label = selected.get_attribute("aria-label") or "" # (b) výběr se neposunul (konec seznamu) if label == prev_label: no_progress += 1 if no_progress >= NO_PROGRESS_MAX: print(" Konec seznamu (výběr se nehýbe).") break page.keyboard.press("ArrowDown") page.wait_for_timeout(250) continue # (c) nová zpráva → stáhni no_progress = 0 prev_label = label target = download_selected(page, OUT_DIR) if target is None: failed += 1 print(f" [!] selhalo: {label[:70]}") else: saved += 1 print(f" [{saved:>4}] {target.name}") # refokus seznamu (klik na zprávu, ne na oddělovač) + posun dál try: selected.click() except Exception: pass page.wait_for_timeout(200) page.keyboard.press("ArrowDown") page.wait_for_timeout(300) print(f"\n Hotovo. Uloženo {saved}, oddělovačů přeskočeno {dividers}, " f"selhalo {failed} → {OUT_DIR}") input(" Stiskni Enter pro zavření okna... ") context.close() if __name__ == "__main__": main()