koleo-cli 0.2.137.3__tar.gz → 0.2.137.5__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.

Potentially problematic release.


This version of koleo-cli might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: koleo-cli
3
- Version: 0.2.137.3
3
+ Version: 0.2.137.5
4
4
  Summary: Koleo CLI
5
5
  Home-page: https://github.com/lzgirlcat/koleo-cli
6
6
  Author: Zoey !
@@ -6,11 +6,6 @@ from requests import PreparedRequest, Response, Session
6
6
  from koleo.types import *
7
7
 
8
8
 
9
-
10
-
11
-
12
-
13
-
14
9
  class errors:
15
10
  class KoleoAPIException(Exception):
16
11
  status: int
@@ -88,6 +83,12 @@ class KoleoAPI:
88
83
  # https://koleo.pl/ls?q=tere&language=pl
89
84
  return self._get_json("/ls", params={"q": query, "language": language})["stations"]
90
85
 
86
+ def get_station_by_id(self, id: int) -> ExtendedBaseStationInfo:
87
+ # https://koleo.pl/api/v2/main/stations/by_id/24000
88
+ return self._get_json(
89
+ f"/api/v2/main/stations/by_id/{id}",
90
+ )
91
+
91
92
  def get_station_by_slug(self, slug: str) -> ExtendedBaseStationInfo:
92
93
  # https://koleo.pl/api/v2/main/stations/by_slug/inowroclaw
93
94
  return self._get_json(
@@ -132,16 +133,16 @@ class KoleoAPI:
132
133
  date: datetime,
133
134
  direct: bool = False,
134
135
  purchasable: bool = False,
135
- ) -> ...:
136
+ ) -> list[ConnectionDetail]:
136
137
  params = {
137
138
  "query[date]": date.strftime("%d-%m-%Y %H:%M:%S"),
138
139
  "query[start_station]": start,
139
140
  "query[end_station]": end,
140
- "query[only_purchasable]": purchasable,
141
- "query[direct]": direct,
141
+ "query[only_purchasable]": str(direct).lower(),
142
+ "query[only_direct]": str(direct).lower(),
142
143
  "query[brand_ids][]": brand_ids,
143
144
  }
144
- return self._get_json("/api/v2/main/connections", params=params)
145
+ return self._get_json("/api/v2/main/connections", params=params)["connections"]
145
146
 
146
147
  def get_brands(self) -> list[ApiBrand]:
147
148
  # https://koleo.pl/api/v2/main/brands
@@ -33,7 +33,6 @@ class CLI:
33
33
  else:
34
34
  self.console.print(text, *args, **kwargs)
35
35
 
36
-
37
36
  @property
38
37
  def client(self) -> KoleoAPI:
39
38
  if not self._client:
@@ -54,9 +53,6 @@ class CLI:
54
53
  def storage(self, storage: Storage):
55
54
  self._storage = storage
56
55
 
57
- def list_stations(self, name: str):
58
- ...
59
-
60
56
  def get_departures(self, station_id: int, date: datetime):
61
57
  cache_id = f"dep-{station_id}-{date.strftime("%Y-%m-%d")}"
62
58
  trains = (
@@ -89,13 +85,13 @@ class CLI:
89
85
 
90
86
  def full_departures(self, station: str, date: datetime):
91
87
  st = self.get_station(station)
92
- station_info = f"[bold blue]{st["name"]}[/bold blue] ID: {st["id"]}"
88
+ station_info = f"[bold blue][link=https://koleo.pl/dworzec-pkp/{st["name_slug"]}/{date.strftime("%Y-%m-%d")}]{st["name"]} at {date.strftime("%d-%m %H:%M")}[/bold blue] ID: {st["id"]}[/link]"
93
89
  self.print(station_info)
94
90
  self.get_departures(st["id"], date)
95
91
 
96
92
  def full_arrivals(self, station: str, date: datetime):
97
93
  st = self.get_station(station)
98
- station_info = f"[bold blue]{st["name"]}[/bold blue] ID: {st["id"]}"
94
+ station_info = f"[bold blue][link=https://koleo.pl/dworzec-pkp/{st["name_slug"]}/{date.strftime("%Y-%m-%d")}]{st["name"]} at {date.strftime("%d-%m %H:%M")}[/bold blue] ID: {st["id"]}[/link]"
99
95
  self.print(station_info)
100
96
  self.get_arrivals(st["id"], date)
101
97
 
@@ -108,17 +104,16 @@ class CLI:
108
104
  self.storage.set_cache("stations", self.client.get_stations())
109
105
  )
110
106
  for st in stations:
111
- self.print(f"[bold blue]{st["name"]}[/bold blue] ID: {st["id"]}")
107
+ self.print(f"[bold blue][link=https://koleo.pl/dworzec-pkp/{st["name_slug"]}]{st["name"]}[/bold blue] ID: {st["id"]}[/link]")
112
108
 
113
- def train_info(self, brand: str, name: str, date: datetime):
109
+ def train_info(self, brand: str, name: list[str], date: datetime):
114
110
  brand = brand.upper().strip()
115
- name = name.strip()
116
- if name.isnumeric():
117
- number = int(name)
111
+ if len(name) == 1 and name[0].isnumeric():
112
+ number = int(name[0])
118
113
  train_name = ""
119
- elif len((parts := name.split(" "))) == 2 or len((parts := name.split("-"))) == 2:
120
- number, train_name = parts
121
- number = int(number)
114
+ elif len(name) > 1:
115
+ number = int(name.pop(0))
116
+ train_name = " ".join(name)
122
117
  else:
123
118
  raise ValueError("Invalid train name!")
124
119
  brands = self.storage.get_cache("brands") or self.storage.set_cache("brands", self.client.get_brands())
@@ -128,9 +123,13 @@ class CLI:
128
123
  raise ValueError("Invalid brand name!")
129
124
  brand = res
130
125
  cache_id = f"tc-{brand}-{number}-{name}"
131
- train_calendars = self.storage.get_cache(cache_id) or self.storage.set_cache(
132
- cache_id, self.client.get_train_calendars(brand, number, train_name)
133
- )
126
+ try:
127
+ train_calendars = self.storage.get_cache(cache_id) or self.storage.set_cache(
128
+ cache_id, self.client.get_train_calendars(brand, number, train_name)
129
+ )
130
+ except self.client.errors.KoleoNotFound:
131
+ self.print(f'[bold red]Train not found: nr={number}, name="{train_name}"[/bold red]')
132
+ exit(2)
134
133
  train_id = train_calendars["train_calendars"][0]["date_train_map"][date.strftime("%Y-%m-%d")]
135
134
  train_details = self.client.get_train(train_id)
136
135
  brand = next(iter(i for i in brands if i["id"] == train_details["train"]["brand_id"]), {}).get("logo_text", "")
@@ -152,13 +151,13 @@ class CLI:
152
151
  start = keys[0]
153
152
  for i in range(1, len(keys)):
154
153
  if vehicle_types[keys[i]] != vehicle_types[start]:
155
- parts.append(f"[bold green] {start} - {keys[i]}:[/bold green] {vehicle_types[start]}")
154
+ parts.append(f" {start} - {keys[i]}: [bold green]{vehicle_types[start]}[/bold green]")
156
155
  start = keys[i]
157
- parts.append(f"[bold green] {start} - {keys[-1]}:[/bold green] {vehicle_types[start]}")
156
+ parts.append(f" {start} - {keys[-1]}: [bold green]{vehicle_types[start]}[/bold green]")
158
157
  self.print("\n".join(parts))
159
158
  self.print(self.train_route_table(train_details))
160
159
 
161
- def connections(self, start: str, end: str, date: datetime, brands: list[str], direct: bool = False, purchasable: bool = False):
160
+ def connections(self, start: str, end: str, date: datetime, brands: list[str], direct: bool, purchasable: bool):
162
161
  start_station = self.get_station(start)
163
162
  end_station = self.get_station(end)
164
163
  brands = [i.lower().strip() for i in brands]
@@ -178,6 +177,59 @@ class CLI:
178
177
  direct,
179
178
  purchasable
180
179
  )
180
+ parts = [f"[bold blue]{start_station["name"]} → {end_station["name"]} at {date.strftime("%H:%M %d-%m")}[/bold blue]"]
181
+ for i in connections:
182
+ arr = arr_dep_to_dt(i["arrival"])
183
+ dep = arr_dep_to_dt(i["departure"])
184
+ travel_time = (arr - dep).seconds
185
+ parts.append(f"[bold green][link=https://koleo.pl/travel-options/{i["id"]}]{dep.strftime("%H:%M")} - {arr.strftime("%H:%M")}[/bold green] {travel_time//3600}h{(travel_time % 3600)/60:.0f}m {i['distance']}km: [/link]")
186
+ if i["constriction_info"]:
187
+ for constriction in i["constriction_info"]:
188
+ parts.append(f" [bold red]- {constriction} [/bold red]")
189
+ if len(i["trains"]) == 1:
190
+ train = i["trains"][0]
191
+ stop = next(iter(i for i in train["stops"] if i["station_id"] == train["start_station_id"]), {})
192
+ stop_station = (
193
+ start_station if stop["station_id"] == start_station["id"] else
194
+ self.storage.get_cache(f"st-{stop['station_id']}")
195
+ or self.storage.set_cache(f"st-{stop['station_id']}", self.client.get_station_by_id(stop['station_id']))
196
+ )
197
+ platform = convert_platform_number(stop["platform"]) if stop["platform"] else ""
198
+ position_info = f"{platform}/{stop["track"]}" if stop["track"] else platform
199
+ brand = next(iter(i for i in api_brands if i["id"] == train["brand_id"]), {}).get("logo_text")
200
+ parts[-1]+=f" [red]{brand}[/red] {train["train_full_name"]}[purple] {stop_station['name']} {position_info}[/purple]"
201
+ else:
202
+ previous_arrival: datetime | None = None
203
+ for train in i["trains"]:
204
+ brand = next(iter(i for i in api_brands if i["id"] == train["brand_id"]), {}).get("logo_text")
205
+
206
+ fs = next(iter(i for i in train["stops"] if i["station_id"] == train["start_station_id"]), {})
207
+ fs_station = (
208
+ start_station if fs["station_id"] == start_station["id"] else
209
+ self.storage.get_cache(f"st-{fs['station_id']}")
210
+ or self.storage.set_cache(f"st-{fs['station_id']}", self.client.get_station_by_id(fs['station_id']))
211
+ )
212
+ fs_platform = convert_platform_number(fs["platform"]) if fs["platform"] else ""
213
+ fs_arr = arr_dep_to_dt(fs["arrival"])
214
+ fs_dep = arr_dep_to_dt(fs["departure"])
215
+ fs_info = f"[bold green]{fs_dep.strftime("%H:%M")} [/bold green][purple]{fs_station['name']} {f"{fs_platform}/{fs["track"]}" if fs["track"] else fs_platform}[/purple]"
216
+
217
+ ls = next(iter(i for i in train["stops"] if i["station_id"] == train["end_station_id"]), {})
218
+ ls_station = (
219
+ start_station if ls["station_id"] == start_station["id"] else
220
+ self.storage.get_cache(f"st-{ls['station_id']}")
221
+ or self.storage.set_cache(f"st-{ls['station_id']}", self.client.get_station_by_id(ls['station_id']))
222
+ )
223
+ ls_platform = convert_platform_number(ls["platform"]) if ls["platform"] else ""
224
+ ls_arr = arr_dep_to_dt(ls["arrival"])
225
+ ls_dep = arr_dep_to_dt(ls["departure"])
226
+ ls_info = f"[bold green]{ls_arr.strftime("%H:%M")} [/bold green][purple]{ls_station['name']} {f"{ls_platform}/{ls["track"]}" if ls["track"] else ls_platform}[/purple]"
227
+ connection_time = (fs_dep - previous_arrival).seconds if previous_arrival else ""
228
+ previous_arrival = ls_arr
229
+ if connection_time:
230
+ parts.append(f" {connection_time//3600}h{(connection_time % 3600)/60:.0f}m at [purple]{fs_station['name']}[/purple]")
231
+ parts.append(f" [red]{brand}[/red] {train["train_full_name"]} {fs_info} - {ls_info}")
232
+ self.print("\n".join(parts))
181
233
 
182
234
  def trains_on_station_table(self, trains: list[TrainOnStationInfo], type: int = 1):
183
235
  parts = []
@@ -262,7 +314,7 @@ def main():
262
314
  help="Allows you to show the train's route",
263
315
  )
264
316
  train_route.add_argument("brand", help="The brand name", type=str)
265
- train_route.add_argument("name", help="The train name", type=str)
317
+ train_route.add_argument("name", help="The train name", type=str, nargs="+")
266
318
  train_route.add_argument(
267
319
  "-d",
268
320
  "--date",
@@ -272,7 +324,7 @@ def main():
272
324
  )
273
325
  train_route.set_defaults(func=cli.train_info, pass_=["brand", "name", "date"])
274
326
 
275
- stations = subparsers.add_parser("stations", aliases=["s", "find", "f", "stacje"], help="Allows you to find stations by their name")
327
+ stations = subparsers.add_parser("stations", aliases=["s", "find", "f", "stacje", "ls"], help="Allows you to find stations by their name")
276
328
  stations.add_argument(
277
329
  "query",
278
330
  help="The station name",
@@ -313,7 +365,7 @@ def main():
313
365
  help="whether or not the result should only trains purchasable on koleo",
314
366
  action="store_true", default=False
315
367
  )
316
- connections.set_defaults(func=cli.connections, pass_=["start", "end", "brands", "date", "direct" "purchasable"])
368
+ connections.set_defaults(func=cli.connections, pass_=["start", "end", "brands", "date", "direct", "purchasable"])
317
369
 
318
370
  args = parser.parse_args()
319
371
 
@@ -178,7 +178,60 @@ class TrainDetailResponse(t.TypedDict):
178
178
  stops: list[TrainStop]
179
179
 
180
180
 
181
+ class ConnectionTrainStop(t.TypedDict):
182
+ arrival: TimeDict | str # WTF KOLEO!!!!
183
+ departure: TimeDict | str # WTF KOLEO!!!!
184
+ distance: int
185
+ in_path: bool
186
+ station_id: int
187
+ next_day: bool
188
+ position: int
189
+ train_nr: int
190
+ brand_id: int
191
+ entry_only: bool
192
+ exit_only: bool
193
+ platform: str
194
+ track: str
195
+ on_demand: bool
196
+
197
+
181
198
  class ConnectiontrainDetail(TrainDetail):
199
+ arrival: TimeDict | str # WTF KOLEO!!!!
200
+ departure: TimeDict | str # WTF KOLEO!!!!
201
+ stops: list[ConnectionTrainStop]
202
+ bookable: bool
203
+ train_attribute_ids: list[int]
204
+ travel_time: int
205
+ direction: str
206
+ start_station_id: int
207
+ end_station_id: int
208
+ fixed_carriage_composition: bool
209
+ is_option_groups_available: bool
182
210
 
211
+
212
+ class ErrorDict(t.TypedDict):
213
+ type: str
214
+ value: str
215
+
216
+
217
+ class ConnectionDetail(t.TypedDict):
218
+ id: int
219
+ distance: int
220
+ purchasable: bool
221
+ purchasable_errors: list[ErrorDict]
222
+ travel_time: int
223
+ changes: int
224
+ needs_document: bool
225
+ brand_ids: list[int]
226
+ start_station_id: int
227
+ end_station_id: int
183
228
  arrival: TimeDict | str # WTF KOLEO!!!!
184
229
  departure: TimeDict | str # WTF KOLEO!!!!
230
+ bookable: bool
231
+ special_event_slug: str | None
232
+ is_advanced_travel_options: bool
233
+ is_child_birthday_required: bool
234
+ max_passengers_count: bool
235
+ constriction_info: list[str]
236
+ is_estimated_timetable_available: bool
237
+ trains: list[ConnectiontrainDetail]
@@ -16,6 +16,12 @@ def parse_datetime(s: str):
16
16
  pass
17
17
  if s[0] == "+":
18
18
  return datetime.now().replace(hour=0, minute=0) + timedelta(days=int(s[1:]))
19
+ try:
20
+ now = datetime.now()
21
+ dt = datetime.strptime(s, "%d-%m %H:%M")
22
+ return dt.replace(year=now.year)
23
+ except ValueError:
24
+ pass
19
25
  return datetime.combine(datetime.now(), datetime.strptime(s, "%H:%M").time())
20
26
 
21
27
 
@@ -58,8 +64,9 @@ NUMERAL_TO_ARABIC = {
58
64
  "X": 10,
59
65
  "XI": 11, # wtf poznań???
60
66
  "XII": 12, # just to be safe
67
+ "BUS": "BUS"
61
68
  }
62
69
 
63
70
 
64
- def convert_platform_number(number: str) -> int | None:
71
+ def convert_platform_number(number: str) -> int | None | str:
65
72
  return NUMERAL_TO_ARABIC.get(number)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: koleo-cli
3
- Version: 0.2.137.3
3
+ Version: 0.2.137.5
4
4
  Summary: Koleo CLI
5
5
  Home-page: https://github.com/lzgirlcat/koleo-cli
6
6
  Author: Zoey !
@@ -14,7 +14,7 @@ def parse_requirements_file(path):
14
14
 
15
15
  setuptools.setup(
16
16
  name="koleo-cli",
17
- version="0.2.137.3",
17
+ version="0.2.137.5",
18
18
  description="Koleo CLI",
19
19
  long_description=long_description(),
20
20
  long_description_content_type="text/markdown",
File without changes
File without changes