# ČPZP — Automatické stahování zpráv ## Co to dělá Dva skripty které se přihlásí na portál ČPZP a stáhnou všechny zprávy ze schránek: 1. `01_prihlaseni.py` — přihlásí se certifikátem, uloží cookies do `cpzp_cookies.json` 2. `02_stahuj_vse.py` — použije cookies, projde schránky, stáhne soubory do `Staženo/` --- ## Jak funguje přihlášení Portál **nepoužívá heslo** — autentizuje certifikátem přes 3 kroky: ### Krok 1 — Získej session a challenge ``` GET https://portal.cpzp.cz/app/login/ → server nastaví cookie: PHPSESSID=... → v HTML stránce je vložen JS objekt s challengem a CSRF tokenem ``` Challenge je v HTML jako JS výraz (ne JSON): ```javascript CPZP = { settings : { certificateLoginKey : 'Prohlášení:'+ String.fromCharCode(13, 10) + 'Tímto se přihlašuji k Portálu ČPZP'+ String.fromCharCode(13, 10) + ''+ String.fromCharCode(13, 10) + 'Okamžik vygenerování tohoto prohlášení: 20.04.2026 14:52:47', ... } } ``` CSRF token je v HTML formuláři: ```html
``` ### Krok 2 — Podpis certifikátem Challenge (sestavená z JS výrazu) se podepíše jako **PKCS7 / CMS SignedData**: - Algoritmus: **RSA + SHA-256** - Typ: **DetachedSignature** (obsah není vložen do podpisu) - **BEZ CA řetězu** — pouze end-entity certifikát - Výsledný formát: **PEM s hlavičkami** (`-----BEGIN PKCS7-----`) ```python pem_podpis = ( pkcs7.PKCS7SignatureBuilder() .set_data(challenge.encode("utf-8")) .add_signer(cert, private_key, hashes.SHA256()) .sign(Encoding.PEM, [PKCS7Options.DetachedSignature]) ) ``` > ⚠️ Na rozdíl od VoZP portál ČPZP očekává **celý PEM string včetně hlaviček**, > ne jen base64 DER (přestože oboje přes NMSigner). Bez hlaviček vrátí 500. ### Krok 3 — Přihlášení ``` POST https://portal.cpzp.cz/app/ Content-Type: application/x-www-form-urlencoded csrfCert=&sign= Úspěch: odpověď neobsahuje "frmPrihlasCert" (login stránka) ``` --- ## Certifikát | Položka | Hodnota | |---|---| | Soubor | `U:\ordinaceprojekt\Insurance\Certificates\MBQualifiedCert.pfx` | | Vlastník | MUDr. Michaela Buzalková | | Vydavatel | I.CA EU Qualified CA2/RSA 06/2022 | | Platnost | do 2027-01-16 | | Thumbprint | `056ED80A3CDDE31DD36EECE0181B4E78D61122A7` | --- ## Stahování souborů `02_stahuj_vse.py` používá `requests` (bez Playwright) — portál nevyžaduje JS pro navigaci. ### Schránky které se prochází | URL | Název | |---|---| | `/app/schranka/` | Schránka klienta | | `/app/schranka-pzs/` | Schránka PZS | > Obě schránky obsahují stejné zprávy — skript deduplicuje podle ID zprávy. ### Paginace Portál zobrazuje 20 zpráv na stránku, stránkování přes `?offset=N`: ``` GET /app/schranka/?offset=0 → zprávy 1–20 GET /app/schranka/?offset=20 → zprávy 21–40 ... ``` Detekce konce: pokud stránka neobsahuje žádné nové ID, zastav. ### Struktura zprávy v seznamu ```html ... zpracováno 09305000 - MUDr. Michaela Buzalková VYÚČTOVÁNÍ ZDRAVOTNÍ PÉČE Ref. č. 26274350 01.04.2026 22:05:42 zobrazit detail ``` ID zprávy je hex string (24 znaků), ne číslo jako u VoZP. ### Detail zprávy a stažení ``` GET /app/schranka/detail/{hex_id}/ → stránka obsahuje odkaz na soubor ZU250168094V1.pdf ← název souboru (nebo "26274350 (stáhnout protokol)") ``` ``` GET /app/schranka/protokol/?path={hash} → vrátí soubor (PDF nebo HTML podle druhu zprávy) ``` ### Typy souborů | Druh zprávy | Typ souboru | Obsah | |---|---|---| | IČZ: ... Č.faktury: ... | **PDF** | Zúčtovací zpráva | | VYÚČTOVÁNÍ ZDRAVOTNÍ PÉČE | **HTML** | Protokol přijetí vyúčtování | | KLIENTELA | **HTML** | Protokol přijetí | | Konečné vyúčtování | **PDF** | Závěrečné vyúčtování | Přípona se určuje podle textu linku (`.pdf`) nebo Content-Type odpovědi. ### Pojmenování stažených souborů ``` YYYY-MM-DD Druh zprávy (Ref. XXXXXXXXX).přípona ``` Příklady: ``` 2026-03-26 IČZ_ 09305000 Č.faktury_ 260027 (Ref. 26220064).pdf 2026-04-01 VYÚČTOVÁNÍ ZDRAVOTNÍ PÉČE (Ref. 26274350).html 2025-05-29 Konečné vyúčtování (Ref. 24774290).pdf ``` Znaky nepovolené ve Windows názvech (`/ : * ? " < > |`) se nahrazují podtržítkem. --- ## Rozdíly oproti VoZP | | VoZP | ČPZP | |---|---|---| | Challenge zdroj | JSON API (`/json-api/prihlaseni/prihlasovaci-zprava`) | HTML stránka (JS výraz) | | Challenge formát | JSON string s `\r\n` | JS string concatenation + `String.fromCharCode` | | Podpis formát | PEM s hlavičkami | PEM s hlavičkami | | Odeslání | POST JSON na API endpoint | POST form-data na `/app/` | | CSRF | ne | ano (`csrfCert`) | | ID zprávy | číselné | hex string (24 znaků) | | Stahování | Playwright + `context.request.get()` | čisté `requests` | | Typy souborů | vždy HTML | HTML i PDF | --- ## Závislosti ``` pip install requests cryptography beautifulsoup4 ``` --- ## Spuštění ```bash python 01_prihlaseni.py # přihlásí, uloží cookies (nutné při expiraci session) python 02_stahuj_vse.py # stáhne vše, přeskočí již existující soubory ``` Session cookie (`PHPSESSID`) expiruje — pokud `02_stahuj_vse.py` zahlásí *"Cookies expirovala"*, spusť nejdřív `01_prihlaseni.py`.