#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Smazani rezervace / poznamky v Medevio kalendari. Defaultni chovani: - jednorazova: smazat (cancel) - opakujici se: smazat tuto a nasledujici (ThisAndFuture) Pouziti jako modul: from smaz_poznamku import smaz_jednorazovou, smaz_opakujici smaz_jednorazovou("") smaz_opakujici("", date="2026-05-30T14:40:00.000Z") # default ThisAndFuture smaz_opakujici("", date="...Z", update_type="All") smaz_opakujici("", date="...Z", update_type="Single") CLI: python smaz_poznamku.py --jednorazova python smaz_poznamku.py --opakujici --datum 2026-05-30T14:40:00.000Z python smaz_poznamku.py --opakujici --datum ...Z --typ All """ import sys import json import argparse from pathlib import Path from datetime import datetime import requests from dateutil import parser as dtparser, tz try: sys.stdout.reconfigure(encoding="utf-8") except AttributeError: pass GRAPHQL_URL = "https://api.medevio.cz/graphql" CLINIC_SLUG = "mudr-buzalkova" PRAGUE_TZ = tz.gettz("Europe/Prague") BASE_DIR = Path(__file__).resolve().parent TOKEN_PATH = BASE_DIR / "token.txt" DEBUG_DIR = BASE_DIR / "debug" VALID_UPDATE_TYPES = ("Single", "ThisAndFuture", "All") MUTATION_SINGLE = """mutation UpdateReservation_CancelReservationByDoctor( $clinicSlug: String!, $reservationId: UUID! ) { reservation: cancelReservationByDoctor( clinicSlug: $clinicSlug, reservationId: $reservationId ) { id __typename } }""" MUTATION_RECURRING = """mutation UpdateReservation_CancelRecurringReservationByDoctor( $input: RemoveRecurringReservationInput! ) { success: removeDateFromRecurringReservation(input: $input) }""" def _load_token() -> str: token = TOKEN_PATH.read_text(encoding="utf-8").strip() if token.startswith("Bearer "): token = token.split(" ", 1)[1] return token def _headers() -> dict: return { "content-type": "application/json", "authorization": f"Bearer {_load_token()}", "origin": "https://my.medevio.cz", "referer": "https://my.medevio.cz/", } def _to_utc_iso(value) -> str: """Prijme str/datetime/date a vrati '....000Z'.""" if isinstance(value, str): if value.endswith("Z") and "T" in value: return value dt = dtparser.parse(value) elif isinstance(value, datetime): dt = value else: raise TypeError(f"Nelze prevest na datetime: {value!r}") if dt.tzinfo is None: dt = dt.replace(tzinfo=PRAGUE_TZ) return dt.astimezone(tz.UTC).strftime("%Y-%m-%dT%H:%M:%S.000Z") def _post(payload: dict, label: str, save_debug: bool = True) -> dict: r = requests.post(GRAPHQL_URL, headers=_headers(), data=json.dumps(payload), timeout=30) r.raise_for_status() data = r.json() if save_debug: DEBUG_DIR.mkdir(exist_ok=True) p = DEBUG_DIR / f"smaz_{label}_{datetime.now():%Y%m%d_%H%M%S}.json" p.write_text( json.dumps({"request": payload["variables"], "response": data}, ensure_ascii=False, indent=2), encoding="utf-8", ) print(f"[debug] {p}") if "errors" in data: raise RuntimeError(f"GraphQL error: {data['errors']}") return data["data"] def smaz_jednorazovou(reservation_id: str, clinic_slug: str = CLINIC_SLUG) -> dict: """Zrusi (cancel) jednorazovou rezervaci. Vraci {'id': ..., '__typename': ...}.""" payload = { "operationName": "UpdateReservation_CancelReservationByDoctor", "variables": {"clinicSlug": clinic_slug, "reservationId": reservation_id}, "query": MUTATION_SINGLE, } result = _post(payload, "single")["reservation"] print(f"OK zruseno: {result['id']}") return result def smaz_opakujici( recurring_id: str, date, update_type: str = "ThisAndFuture", clinic_slug: str = CLINIC_SLUG, ) -> bool: """Smaze opakujici se rezervaci. Args: recurring_id: UUID `recurringReservation.id` date: datum konkretni instance (ISO str s Z, nebo datetime, nebo 'YYYY-MM-DD HH:MM' v lokal cas) update_type: 'Single' | 'ThisAndFuture' (default) | 'All' """ if update_type not in VALID_UPDATE_TYPES: raise ValueError(f"update_type musi byt {VALID_UPDATE_TYPES}, dostal '{update_type}'") payload = { "operationName": "UpdateReservation_CancelRecurringReservationByDoctor", "variables": { "input": { "clinicSlug": clinic_slug, "recurringReservationId": recurring_id, "date": _to_utc_iso(date), "updateType": update_type, } }, "query": MUTATION_RECURRING, } success = _post(payload, f"recurring_{update_type}")["success"] print(f"OK smazano ({update_type}): {recurring_id} @ {payload['variables']['input']['date']} -> success={success}") return success def main(): ap = argparse.ArgumentParser(description="Smazani rezervace v Medevio kalendari.") g = ap.add_mutually_exclusive_group(required=True) g.add_argument("--jednorazova", help="UUID jednorazove rezervace ke zruseni") g.add_argument("--opakujici", help="UUID opakujiciho se pravidla") ap.add_argument("--datum", help="Datum instance opakujici (ISO Z nebo 'YYYY-MM-DD HH:MM')") ap.add_argument("--typ", choices=VALID_UPDATE_TYPES, default="ThisAndFuture", help="Pro --opakujici. Default ThisAndFuture.") args = ap.parse_args() if args.jednorazova: smaz_jednorazovou(args.jednorazova) else: if not args.datum: ap.error("--opakujici vyzaduje --datum") smaz_opakujici(args.opakujici, args.datum, update_type=args.typ) if __name__ == "__main__": main()