Files
janssen/Outlook/download_all_inbox_eml_v1.0.py
T

217 lines
7.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
=======================================================================
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()