strava.cz-api 1.2__tar.gz → 2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,98 @@
1
+ Metadata-Version: 2.4
2
+ Name: strava.cz_api
3
+ Version: 2.1
4
+ Summary: Rest API for strava.cz.
5
+ Author-email: Matěj Adamec <kralyk@kralyk.dev>
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Dynamic: license-file
10
+
11
+ # Strava.cz rest API
12
+ > ⚠️ **UPOZORNĚNÍ:** Strava.cz má vysoce proměnlivé prostředí (každá jídelna má jiný počet obědů, jiné názvy chodů apod.) z toho důvodu toto API **není plně univerzální**. I když jsem se snažil ho napsat univerzálně, nějaké metody nemusí plně fungovat a bude to vyžadovat vaší opravu.
13
+
14
+ Toto je **neoficiální** REST api pro stravu.cz. V tomto dokumentu je popsáno vše co potřebuješ vědet o tomto API. Je zde také vysvětleno dopodrobna jak to celé funguje.
15
+
16
+ ## Instalace
17
+ ```bash
18
+ pip install strava.cz_api
19
+ ```
20
+
21
+ ## Autentizace
22
+ K API endpointům potřebuješ **SID** a **s5url**. Můžeš si je získat sám z dev tools v prohlížeči, ale nejlepší cesta je pomocí metod: `Auth.login()` a `Auth.getCredentials()`:
23
+
24
+ ```py
25
+ from strava import Auth
26
+
27
+ cookie, data = Auth.login("demo", "demo", "0000")
28
+ sid, s5url = Auth.getCredentials(data)
29
+ ```
30
+
31
+ **Poznámky:**
32
+ - Podporované jazyky: `CZ`, `EN`, `SK`.
33
+ - `Auth.login()` vrací `cookie` a JSON data; `Auth.getCredentials()` z nich vytáhne `sid` a `s5url`.
34
+
35
+ ## Inicializace API
36
+ ```py
37
+ from strava import Api
38
+
39
+ api = Api(
40
+ sid="00000000000000000000000000000000",
41
+ s5url="",
42
+ cislo_jidelny="0000"
43
+ )
44
+ ```
45
+
46
+ **Poznámky:**
47
+ - `s5url=""` může být hash, url či prázdné. Někdy se stane, že musí být spravný input, někdy může být prázné.
48
+
49
+ ## Veřejné endpointy (bez přihlášení)
50
+
51
+ | Metoda | Popis | Poznámka |
52
+ | --- | --- | --- |
53
+ | `getJidelny()` | Seznam jídelen
54
+ | `getJidelna()` | Informace o jídělne
55
+ | `getS5url()` | URL jídelny
56
+ | `getJidelnicek()` | Get veřejného jídelníčku
57
+
58
+ ## Autentizované endpointy (Api)
59
+ | Metoda | Popis | Poznámka |
60
+ | --- | --- | --- |
61
+ | `getJidelnicekToday()` | Dnešní jídelníček | Vrací list (`table0`) |
62
+ | `getJidelnicekAll()` | Kompletní jídelníček
63
+ | `getInfo()` | Informace o uživateli
64
+ | `getUsername()` | Uživatelské jméno | Vytahuje z `getInfo()` |
65
+ | `getJidelna()` | Informace o jídelně
66
+ | `getHistorieKlienta(date)` | Historie objednávek za měsíc | `date` = první den měsíce (např. `2025-01-01`) |
67
+ | `getPlaby()` | Pohyby na účtu
68
+ | `getMessages()` | Zprávy pro uživatele
69
+ | `getProtokol()` | Vrátí protokol
70
+ | `getVydej()` | Vrátí list vydaných jídel.
71
+ | `postJidlo(veta, stav)` | Přihlásit/odhlásit jídlo | `stav`: 1 přihlásit, 0 odhlásit |
72
+ | `postDen(datum, stav)` | Přihlásit/odhlásit celý den | `datum` ve formátu `YYYY-MM-DD` |
73
+ | `postOrders()` | Uložit změny objednávek | Nutné po `postJidlo`/`postDen` |
74
+
75
+ ## Objednávky – správný postup
76
+ Změny objednávek se ukládají ve dvou krocích:
77
+ 1. Provedení změn (`postJidlo` nebo `postDen`)
78
+ 2. Uložení (`postOrders`)
79
+
80
+ ```py
81
+ cookie_data = api.postJidlo(5, 1)
82
+ cookie_data = api.postOrders()
83
+ ```
84
+
85
+ ## Návratové hodnoty
86
+ Většina metod vrací JSON string. Metody `postJidlo`, `postDen` a `postOrders` vrací dvojici `(cookie, data)` z POST odpovědi.
87
+
88
+ ## Chyby a omezení
89
+ - Error handling je dostupný přes výjimky, které lze importovat: `from strava import StravaError`.
90
+ - Při neúspěšném požadavku se vyhazuje `ConnectionError`.
91
+ - Některé jídelny mohou vyžadovat úpravy payloadů nebo cookie.
92
+
93
+ ## Příklady
94
+ Ukázkové skripty najdeš ve složce `./examples`.
95
+
96
+ ## Demo
97
+ - Demo uživatele si lze vytvořit na https://www.strava.cz/strava/Stravnik/Demo
98
+ - Poté se lze přihlásit na https://app.strava.cz/ na jídelně `0000`
@@ -0,0 +1,88 @@
1
+ # Strava.cz rest API
2
+ > ⚠️ **UPOZORNĚNÍ:** Strava.cz má vysoce proměnlivé prostředí (každá jídelna má jiný počet obědů, jiné názvy chodů apod.) z toho důvodu toto API **není plně univerzální**. I když jsem se snažil ho napsat univerzálně, nějaké metody nemusí plně fungovat a bude to vyžadovat vaší opravu.
3
+
4
+ Toto je **neoficiální** REST api pro stravu.cz. V tomto dokumentu je popsáno vše co potřebuješ vědet o tomto API. Je zde také vysvětleno dopodrobna jak to celé funguje.
5
+
6
+ ## Instalace
7
+ ```bash
8
+ pip install strava.cz_api
9
+ ```
10
+
11
+ ## Autentizace
12
+ K API endpointům potřebuješ **SID** a **s5url**. Můžeš si je získat sám z dev tools v prohlížeči, ale nejlepší cesta je pomocí metod: `Auth.login()` a `Auth.getCredentials()`:
13
+
14
+ ```py
15
+ from strava import Auth
16
+
17
+ cookie, data = Auth.login("demo", "demo", "0000")
18
+ sid, s5url = Auth.getCredentials(data)
19
+ ```
20
+
21
+ **Poznámky:**
22
+ - Podporované jazyky: `CZ`, `EN`, `SK`.
23
+ - `Auth.login()` vrací `cookie` a JSON data; `Auth.getCredentials()` z nich vytáhne `sid` a `s5url`.
24
+
25
+ ## Inicializace API
26
+ ```py
27
+ from strava import Api
28
+
29
+ api = Api(
30
+ sid="00000000000000000000000000000000",
31
+ s5url="",
32
+ cislo_jidelny="0000"
33
+ )
34
+ ```
35
+
36
+ **Poznámky:**
37
+ - `s5url=""` může být hash, url či prázdné. Někdy se stane, že musí být spravný input, někdy může být prázné.
38
+
39
+ ## Veřejné endpointy (bez přihlášení)
40
+
41
+ | Metoda | Popis | Poznámka |
42
+ | --- | --- | --- |
43
+ | `getJidelny()` | Seznam jídelen
44
+ | `getJidelna()` | Informace o jídělne
45
+ | `getS5url()` | URL jídelny
46
+ | `getJidelnicek()` | Get veřejného jídelníčku
47
+
48
+ ## Autentizované endpointy (Api)
49
+ | Metoda | Popis | Poznámka |
50
+ | --- | --- | --- |
51
+ | `getJidelnicekToday()` | Dnešní jídelníček | Vrací list (`table0`) |
52
+ | `getJidelnicekAll()` | Kompletní jídelníček
53
+ | `getInfo()` | Informace o uživateli
54
+ | `getUsername()` | Uživatelské jméno | Vytahuje z `getInfo()` |
55
+ | `getJidelna()` | Informace o jídelně
56
+ | `getHistorieKlienta(date)` | Historie objednávek za měsíc | `date` = první den měsíce (např. `2025-01-01`) |
57
+ | `getPlaby()` | Pohyby na účtu
58
+ | `getMessages()` | Zprávy pro uživatele
59
+ | `getProtokol()` | Vrátí protokol
60
+ | `getVydej()` | Vrátí list vydaných jídel.
61
+ | `postJidlo(veta, stav)` | Přihlásit/odhlásit jídlo | `stav`: 1 přihlásit, 0 odhlásit |
62
+ | `postDen(datum, stav)` | Přihlásit/odhlásit celý den | `datum` ve formátu `YYYY-MM-DD` |
63
+ | `postOrders()` | Uložit změny objednávek | Nutné po `postJidlo`/`postDen` |
64
+
65
+ ## Objednávky – správný postup
66
+ Změny objednávek se ukládají ve dvou krocích:
67
+ 1. Provedení změn (`postJidlo` nebo `postDen`)
68
+ 2. Uložení (`postOrders`)
69
+
70
+ ```py
71
+ cookie_data = api.postJidlo(5, 1)
72
+ cookie_data = api.postOrders()
73
+ ```
74
+
75
+ ## Návratové hodnoty
76
+ Většina metod vrací JSON string. Metody `postJidlo`, `postDen` a `postOrders` vrací dvojici `(cookie, data)` z POST odpovědi.
77
+
78
+ ## Chyby a omezení
79
+ - Error handling je dostupný přes výjimky, které lze importovat: `from strava import StravaError`.
80
+ - Při neúspěšném požadavku se vyhazuje `ConnectionError`.
81
+ - Některé jídelny mohou vyžadovat úpravy payloadů nebo cookie.
82
+
83
+ ## Příklady
84
+ Ukázkové skripty najdeš ve složce `./examples`.
85
+
86
+ ## Demo
87
+ - Demo uživatele si lze vytvořit na https://www.strava.cz/strava/Stravnik/Demo
88
+ - Poté se lze přihlásit na https://app.strava.cz/ na jídelně `0000`
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "strava.cz_api"
7
- version = "1.2"
7
+ version = "2.1"
8
8
  description = "Rest API for strava.cz."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -0,0 +1,78 @@
1
+ import requests
2
+ import json
3
+
4
+ class StravaError(Exception):
5
+ pass
6
+
7
+ class JidelnaNenalezenaError(StravaError):
8
+ pass
9
+
10
+ class ChybneHesloError(StravaError):
11
+ pass
12
+
13
+ class BackendError(StravaError):
14
+ pass
15
+
16
+ class ChybneSID(StravaError):
17
+ pass
18
+
19
+ def raise_api_error(data):
20
+ if data.get("state") != "error":
21
+ return
22
+
23
+ number = data.get("number")
24
+ message = data.get("message", "Neznámá chyba")
25
+
26
+ exc = ERROR_MAP.get(number, StravaError)
27
+ raise exc(message)
28
+
29
+
30
+ ERROR_MAP = {
31
+ 6: JidelnaNenalezenaError,
32
+ 15: ChybneSID,
33
+ 13405: ChybneHesloError,
34
+ 3002: BackendError,
35
+ }
36
+
37
+ class Get:
38
+ @staticmethod
39
+ def call(url, payload, headers=
40
+ {
41
+ "Content-Type": "text/plain;charset=UTF-8",
42
+ "Cookie": "NEXT_LOCALE=cs; multiContextSession=%7B%22printOpen%22%3A%7B%22value%22%3Afalse%2C%22expiration%22%3A-1%7D%7D",
43
+ "Referer": "https://app.strava.cz/"
44
+ }):
45
+
46
+ response = requests.post(url, headers=headers, json=payload)
47
+
48
+ data = response.json()
49
+
50
+ raise_api_error(data)
51
+
52
+ return json.dumps(data, indent=2, ensure_ascii=False)
53
+
54
+
55
+
56
+ class Post:
57
+ @staticmethod
58
+ def call(url, payload, cookie, headers=None):
59
+ if headers is None:
60
+ headers = {
61
+ "Content-Type": "text/plain;charset=UTF-8",
62
+ "Cookie": cookie,
63
+ "Referer": "https://app.strava.cz/"
64
+ }
65
+
66
+
67
+ response = requests.post(url, headers=headers, json=payload)
68
+
69
+ data = response.json()
70
+
71
+ raise_api_error(data)
72
+
73
+ new_cookie = "; ".join([f"{k}={v}" for k, v in response.cookies.items()])
74
+
75
+ return new_cookie, data
76
+
77
+
78
+
@@ -0,0 +1,362 @@
1
+ import requests
2
+ import json
3
+ from action import Get, Post
4
+
5
+ from action import (
6
+ StravaError,
7
+ JidelnaNenalezenaError,
8
+ ChybneHesloError,
9
+ BackendError,
10
+ ChybneSID
11
+ )
12
+
13
+ __all__ = [
14
+ "Api",
15
+ "Auth",
16
+ "StravaError",
17
+ "JidelnaNenalezenaError",
18
+ "ChybneHesloError",
19
+ "BackendError",
20
+ "ChybneSID"
21
+ ]
22
+
23
+
24
+ class Api:
25
+ def __init__(self, sid, s5url, cislo_jidelny, cookie="NEXT_LOCALE=cs; multiContextSession=%7B%22printOpen%22%3A%7B%22value%22%3Afalse%2C%22expiration%22%3A-1%7D%7D", lang="CZ"):
26
+ if lang not in ("CZ", "EN", "SK"):
27
+ raise ValueError("Podporované jazyky: EN, CZ, SK")
28
+
29
+ self.sid = sid
30
+ self.lang = lang
31
+ self.cislo_jidelny = cislo_jidelny
32
+ self.cookie = cookie
33
+ self.s5url = s5url
34
+
35
+ self.base = "https://app.strava.cz/api"
36
+
37
+
38
+ def getJidelnicekToday(self):
39
+ """
40
+ Vrátí dnešní jídelníček
41
+ """
42
+ return json.loads(self.getJidelnicekAll()).get("table0", [])
43
+
44
+
45
+ def getJidelnicekAll(self):
46
+ """
47
+ Vrátí celý jídelníček
48
+ """
49
+ url = f"{self.base}/objednavky"
50
+
51
+ payload = {
52
+ "cislo": self.cislo_jidelny,
53
+ "sid": self.sid,
54
+ "s5url": self.s5url,
55
+ "lang": self.lang,
56
+ "konto": 0,
57
+ "podminka": "",
58
+ "ignoreCert": "false"
59
+ }
60
+
61
+ return Get.call(url, payload)
62
+
63
+
64
+ def getInfo(self):
65
+ """
66
+ Vrátí info o uživateli
67
+ """
68
+ url = f"{self.base}/nactiVlastnostiPA"
69
+
70
+ payload = {
71
+ "sid": self.sid,
72
+ "url": self.s5url,
73
+ "cislo": self.cislo_jidelny,
74
+ "ignoreCert": "false",
75
+ "lang": self.lang,
76
+ "getText": True,
77
+ "checkVersion": True,
78
+ "resetTables" :True,
79
+ "frontendFunction": "refreshInformations"
80
+ }
81
+
82
+
83
+ return Get.call(url, payload)
84
+
85
+
86
+ def getUsername(self):
87
+ """
88
+ Vrátí uživatelské jméno
89
+ """
90
+
91
+ return json.loads(self.getInfo()).get("id")
92
+
93
+
94
+ def getJidelna(self):
95
+ """
96
+ Získá informaci o jídělně
97
+ """
98
+
99
+ url = f"{self.base}/jidelnaS5"
100
+
101
+ payload = {
102
+ "cislo": self.cislo_jidelny,
103
+ "url": self.s5url,
104
+ "lang": self.lang,
105
+ "ignoreCert": "false"
106
+ }
107
+
108
+
109
+ return Get.call(url, payload)
110
+
111
+
112
+ def getHistorieKlienta(self, date):
113
+ """
114
+ Získá info o historii objednávek.
115
+
116
+ date = počáteční datum měsíce.
117
+ 2025-01-01 - leden
118
+ 2025-12-01 - prosinec
119
+ """
120
+
121
+ url = f"{self.base}/historieKlienta"
122
+
123
+ payload = {
124
+ "sid": self.sid,
125
+ "url": self.s5url,
126
+ "cislo": self.cislo_jidelny,
127
+ "datum": date,
128
+ "lang": self.lang,
129
+ "ignoreCert": "false"
130
+ }
131
+
132
+
133
+ return Get.call(url, payload)
134
+
135
+
136
+ def getPlaby(self):
137
+ """
138
+ Vrátí platby na účtu.
139
+ """
140
+
141
+ url = f"{self.base}/platby"
142
+
143
+ payload = {
144
+ "sid": self.sid,
145
+ "url": self.s5url,
146
+ "cislo": self.cislo_jidelny,
147
+ "lang": self.lang,
148
+ "ignoreCert": "false"
149
+ }
150
+
151
+
152
+ return Get.call(url, payload)
153
+
154
+
155
+ def getMessages(self):
156
+ """
157
+ Vratí zprávy poslané uživatelovi.
158
+ """
159
+
160
+ url = f"{self.base}/messagesGetList"
161
+
162
+ payload = {
163
+ "sid": "",
164
+ "idO":"",
165
+ "idJ": self.cislo_jidelny,
166
+ "idU": self.getUsername(),
167
+ "typZpravy": ""
168
+ }
169
+
170
+ return Get.call(url, payload)
171
+
172
+
173
+ def getProtokol(self):
174
+ """
175
+ Vrátí protokol.
176
+ """
177
+
178
+ url = f"{self.base}/getProtokol"
179
+
180
+ payload = {
181
+ "cislo": self.cislo_jidelny,
182
+ "s5url": self.s5url,
183
+ "sid": self.sid,
184
+ "ignoreCert": "false",
185
+ "lang": self.lang,
186
+ "uroven": "KP",
187
+ "evCislo": 0
188
+ }
189
+
190
+
191
+ return Get.call(url, payload)
192
+
193
+ def getVydej(self):
194
+ """
195
+ Vrátí list vydaných jídel.
196
+ """
197
+
198
+ url = f"{self.base}/vydej"
199
+
200
+ payload = {
201
+ "cislo": self.cislo_jidelny,
202
+ "s5url": self.s5url,
203
+ "sid": self.sid,
204
+ "ignoreCert": "false",
205
+ "lang": self.lang,
206
+ }
207
+
208
+
209
+ return Get.call(url, payload)
210
+
211
+
212
+ def postJidlo(self, veta, stav):
213
+ """
214
+ Příhlásí nebo odhlásí jídlo.
215
+
216
+ veta = číslo políčka/jídla - jde získat např. z getJidelnicek()
217
+ stav = 0 odhlásit - 1 přihlásit
218
+ """
219
+ url = f"{self.base}/pridejJidloS5"
220
+
221
+ payload = {
222
+ "cislo": self.cislo_jidelny,
223
+ "sid": self.sid,
224
+ "url": self.s5url,
225
+ "veta": veta,
226
+ "pocet": stav, # 0 ohlasit - 1 prihlasit
227
+ "lang": self.lang,
228
+ "ignoreCert": "false"
229
+ }
230
+
231
+ self.cookie = Post.call(url, payload, self.cookie)
232
+
233
+ return self.cookie
234
+
235
+
236
+ def postOrders(self):
237
+ """
238
+ Uloží objednávky
239
+ """
240
+ url = f"{self.base}/saveOrders"
241
+
242
+
243
+ payload = {
244
+ "cislo": self.cislo_jidelny,
245
+ "sid": self.sid,
246
+ "url": self.s5url,
247
+ "xml": None,
248
+ "lang": self.lang,
249
+ "ignoreCert": "false"
250
+ }
251
+
252
+ self.cookie = Post.call(url, payload, self.cookie)
253
+
254
+ return self.cookie
255
+
256
+
257
+ def postDen(self, datum, stav):
258
+ """
259
+ Přihlásí nebo ohlásí celý den.
260
+
261
+ datum = datum dne jaký chceme odhlásit. 2025-12-30
262
+ stav = 0 ohlasit - 1 prihlasit
263
+ """
264
+
265
+ url = f"{self.base}/objednejDenS5"
266
+
267
+ payload = {
268
+ "cislo": self.cislo_jidelny,
269
+ "sid": self.sid,
270
+ "url": self.s5url,
271
+ "datum": datum,
272
+ "pocet": stav,
273
+ "lang": self.lang,
274
+ "ignoreCert": "false"
275
+ }
276
+
277
+ self.cookie = Post.call(url, payload, self.cookie)
278
+
279
+ return self.cookie
280
+
281
+
282
+ class Public:
283
+ def getJidelnicek(cislo_jidelny, lang):
284
+ """
285
+ Získání public jídelníčků.
286
+ """
287
+ if lang not in ("CZ", "EN", "SK"):
288
+ raise ValueError("Podporované jazyky: EN, CZ, SK")
289
+
290
+ url = "https://app.strava.cz/api/jidelnicky"
291
+
292
+ payload = {
293
+ "cislo": cislo_jidelny,
294
+ "s5url": Public.getS5url(cislo_jidelny),
295
+ "lang": lang,
296
+ "ignoreCert": False
297
+ }
298
+
299
+ return Get.call(url, payload)
300
+
301
+ def getJidelna(cislo_jidelny):
302
+ """
303
+ Vrátí informaci o jídelně.
304
+ """
305
+ url = "https://app.strava.cz/api/s4Polozky"
306
+
307
+ payload = {
308
+ "cislo": cislo_jidelny,
309
+ "lang": "CZ",
310
+ "polozky": "V_NAZEV,V_ULICE,V_MESTO,V_PSC,V_TELEFON,V_UCET,V_EMAIL,V_URL,DATCAS_AKT,VERZE,URLWSDL_S-URL,GPSDELKA,GPSSIRKA,IGN_CERT,TEXT_ANON,LOGO"}
311
+
312
+ return Get.call(url, payload)
313
+
314
+ def getS5url(cislo_jidelny):
315
+ """
316
+ Pomocná metoda co vratí url jídelny.
317
+ """
318
+ return json.loads(Public.getJidelna(cislo_jidelny)).get("urlwsdl_s")[0]
319
+
320
+
321
+ def getJidelny():
322
+ """
323
+ Vrátí seznam všech jídelen a jejich čísel.
324
+ """
325
+ url = "https://app.strava.cz/api/zarAMesta"
326
+
327
+ payload = {
328
+ "lang":"CZ"
329
+ }
330
+
331
+ return Get.call(url, payload)
332
+
333
+
334
+ class Auth:
335
+ def login(username, password, cislo_jidelny, lang="CZ", zustat_prihlasen=True, cookie="NEXT_LOCALE=cs"):
336
+ """
337
+ Vrátí data nutná pro další komunikaci.
338
+ """
339
+ if lang not in ("CZ", "EN", "SK"):
340
+ raise ValueError("Podporované jazyky: EN, CZ, SK")
341
+
342
+ url = "https://app.strava.cz/api/login"
343
+
344
+ payload = {
345
+ "cislo": cislo_jidelny,
346
+ "jmeno": username,
347
+ "heslo": password,
348
+ "zustatPrihlasen": zustat_prihlasen,
349
+ "environment": "W",
350
+ "lang": lang
351
+ }
352
+
353
+ return Post.call(url, payload, cookie)
354
+
355
+ def getCredentials(data):
356
+ """
357
+ Vyfiltruje SID a s5url z funkce login
358
+ """
359
+
360
+ sid = data.get("sid")
361
+ s5url = data.get("s5url")
362
+ return sid, s5url