z230
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
# Č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
|
||||
<form name="frmPrihlasCert" method="post" action="/app/">
|
||||
<input name="csrfCert" type="hidden" value="42ead46f7d374805c5d9...">
|
||||
<input id="sign" name="sign" type="hidden" value="">
|
||||
</form>
|
||||
```
|
||||
|
||||
### 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=<token_z_formulare>&sign=<pem_podpis>
|
||||
|
||||
Ú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
|
||||
<tr id="message-69cd7aa38ec68a837b5d8cbc" class="mail-read status-done">
|
||||
<td>...</td>
|
||||
<td>zpracováno</td>
|
||||
<td>09305000 - MUDr. Michaela Buzalková</td>
|
||||
<td>VYÚČTOVÁNÍ ZDRAVOTNÍ PÉČE Ref. č. 26274350</td>
|
||||
<td>01.04.2026 22:05:42</td>
|
||||
<td><a href="/app/schranka/detail/69cd7aa38ec68a837b5d8cbc/">zobrazit detail</a></td>
|
||||
</tr>
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
<a href="/app/schranka/protokol/?path=c387260ba8f041609a663038be79281c">
|
||||
ZU250168094V1.pdf ← název souboru (nebo "26274350 (stáhnout protokol)")
|
||||
</a>
|
||||
```
|
||||
|
||||
```
|
||||
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`.
|
||||
Reference in New Issue
Block a user