Add Outlook/Soubory/Clario/Feasibility scripts and reports; ignore Incoming, Outlook downloads & profile

This commit is contained in:
2026-06-03 16:15:19 +02:00
parent 61c6aeea23
commit 6c57ab3ae6
36 changed files with 4949 additions and 0 deletions
+216
View File
@@ -0,0 +1,216 @@
"""
=======================================================================
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()