python-ballchasing 0.4.1__py3-none-any.whl → 0.5.1__py3-none-any.whl
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.
- ballchasing/api.py +120 -85
- ballchasing/constants.py +84 -81
- ballchasing/typed/deep_group.py +27 -27
- ballchasing/typed/deep_replay.py +16 -16
- ballchasing/typed/shared.py +33 -8
- ballchasing/util/__init__.py +4 -0
- ballchasing/util/dates.py +35 -0
- ballchasing/util/iterators.py +118 -0
- ballchasing/util/replays.py +185 -0
- ballchasing/{util.py → util/stats.py} +7 -30
- {python_ballchasing-0.4.1.dist-info → python_ballchasing-0.5.1.dist-info}/METADATA +5 -5
- python_ballchasing-0.5.1.dist-info/RECORD +24 -0
- {python_ballchasing-0.4.1.dist-info → python_ballchasing-0.5.1.dist-info}/WHEEL +1 -1
- scripts/get_maps.py +147 -0
- scripts/test_typed.py +53 -0
- python_ballchasing-0.4.1.dist-info/RECORD +0 -19
- {python_ballchasing-0.4.1.dist-info → python_ballchasing-0.5.1.dist-info}/licenses/LICENSE +0 -0
- {python_ballchasing-0.4.1.dist-info → python_ballchasing-0.5.1.dist-info}/top_level.txt +0 -0
ballchasing/api.py
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import time
|
|
3
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
3
4
|
from datetime import datetime
|
|
4
5
|
from pathlib import Path
|
|
5
|
-
from typing import
|
|
6
|
+
from typing import BinaryIO, Iterator
|
|
6
7
|
from urllib.parse import parse_qs, urlparse
|
|
7
8
|
|
|
8
|
-
from requests import sessions, Response, ConnectionError
|
|
9
|
+
from requests import sessions, Response, ConnectionError
|
|
9
10
|
|
|
10
|
-
from ballchasing.constants import GroupSortBy, SortDir,
|
|
11
|
-
|
|
11
|
+
from ballchasing.constants import GroupSortBy, SortDir, AnySeason, AnyRank, AnyReplaySortBy, AnySortDir, \
|
|
12
|
+
AnyVisibility, AnyGroupSortBy, AnyPlayerIdentification, AnyTeamIdentification, AnyMatchResult, NoneOrMore
|
|
12
13
|
from ballchasing.typed import DeepReplay, ShallowReplay, DeepGroup, ShallowGroup
|
|
13
14
|
from .typed.shared import BaseGroup, BasicGroup
|
|
14
|
-
from .util import to_rfc3339
|
|
15
|
+
from .util.dates import to_rfc3339
|
|
16
|
+
from .util.iterators import deduplicate as deduplicator
|
|
17
|
+
from .util.stats import parse_replay_stats
|
|
15
18
|
|
|
16
19
|
DEFAULT_URL = "https://ballchasing.com/api"
|
|
17
20
|
|
|
@@ -25,11 +28,11 @@ class BallchasingApi:
|
|
|
25
28
|
self,
|
|
26
29
|
auth_key: str,
|
|
27
30
|
*,
|
|
28
|
-
sleep_time_on_rate_limit:
|
|
31
|
+
sleep_time_on_rate_limit: float | None = None,
|
|
29
32
|
print_on_rate_limit: bool = False,
|
|
30
|
-
base_url=None,
|
|
31
|
-
do_initial_ping=True,
|
|
32
|
-
typed=False,
|
|
33
|
+
base_url: str | None = None,
|
|
34
|
+
do_initial_ping: bool = True,
|
|
35
|
+
typed: bool = False,
|
|
33
36
|
):
|
|
34
37
|
"""
|
|
35
38
|
|
|
@@ -117,6 +120,8 @@ class BallchasingApi:
|
|
|
117
120
|
time.sleep(retry_after)
|
|
118
121
|
elif self.sleep_time_on_rate_limit:
|
|
119
122
|
time.sleep(self.sleep_time_on_rate_limit)
|
|
123
|
+
elif r.status_code == 504:
|
|
124
|
+
raise ConnectionError("Gateway Timeout. The server did not respond in time.")
|
|
120
125
|
else:
|
|
121
126
|
r.raise_for_status() # Raise an error for any other status code'
|
|
122
127
|
except ConnectionError as e:
|
|
@@ -141,58 +146,69 @@ class BallchasingApi:
|
|
|
141
146
|
self._ping_result = result
|
|
142
147
|
return result
|
|
143
148
|
|
|
144
|
-
def _iterable_from_request(self, url, params):
|
|
145
|
-
# Shared by get_replays and get_groups
|
|
149
|
+
def _iterable_from_request(self, url, params, prefetch=True):
|
|
150
|
+
# Shared by get_replays and get_groups.
|
|
151
|
+
# When prefetch=True, the next page is requested in a background
|
|
152
|
+
# thread *before* yielding, so network I/O overlaps with the
|
|
153
|
+
# consumer processing items. When prefetch=False (e.g. deep mode
|
|
154
|
+
# where the consumer also makes API calls), the next request is
|
|
155
|
+
# submitted *after* yielding to avoid doubling the request rate.
|
|
146
156
|
remaining = params["count"]
|
|
147
|
-
# return_length = True
|
|
148
|
-
while remaining > 0:
|
|
149
|
-
request_count = min(remaining, 200)
|
|
150
|
-
params["count"] = request_count
|
|
151
|
-
try:
|
|
152
|
-
d = self._request(url, "GET", params=params).json()
|
|
153
|
-
except HTTPError as e:
|
|
154
|
-
if e.response.status_code == 504:
|
|
155
|
-
# Gateway Timeout, retry
|
|
156
|
-
time.sleep(5)
|
|
157
|
-
continue
|
|
158
|
-
else:
|
|
159
|
-
raise e
|
|
160
157
|
|
|
161
|
-
|
|
162
|
-
|
|
158
|
+
def fetch_page(p):
|
|
159
|
+
return self._request(url, "GET", params=p).json()
|
|
160
|
+
|
|
161
|
+
with ThreadPoolExecutor(max_workers=1) as executor:
|
|
162
|
+
params["count"] = min(remaining, 200)
|
|
163
|
+
future = executor.submit(fetch_page, dict(params))
|
|
163
164
|
|
|
164
|
-
|
|
165
|
-
|
|
165
|
+
while remaining > 0:
|
|
166
|
+
d = future.result()
|
|
167
|
+
batch = d["list"][:min(remaining, 200)]
|
|
168
|
+
remaining -= len(batch)
|
|
166
169
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
+
has_next = "next" in d and remaining > 0
|
|
171
|
+
if has_next:
|
|
172
|
+
next_url = d["next"]
|
|
173
|
+
params["after"] = parse_qs(urlparse(next_url).query)["after"][0]
|
|
174
|
+
params["count"] = min(remaining, 200)
|
|
175
|
+
if prefetch:
|
|
176
|
+
future = executor.submit(fetch_page, dict(params))
|
|
177
|
+
|
|
178
|
+
yield from batch
|
|
179
|
+
|
|
180
|
+
if not has_next:
|
|
181
|
+
break
|
|
182
|
+
if not prefetch:
|
|
183
|
+
future = executor.submit(fetch_page, dict(params))
|
|
170
184
|
|
|
171
185
|
def get_replays(
|
|
172
186
|
self,
|
|
173
187
|
*,
|
|
174
|
-
title:
|
|
175
|
-
player_name:
|
|
176
|
-
player_id:
|
|
177
|
-
playlist:
|
|
178
|
-
season:
|
|
179
|
-
match_result:
|
|
180
|
-
min_rank:
|
|
181
|
-
max_rank:
|
|
182
|
-
pro:
|
|
183
|
-
uploader:
|
|
184
|
-
group_id:
|
|
185
|
-
map_id:
|
|
186
|
-
created_before:
|
|
187
|
-
created_after:
|
|
188
|
-
replay_after:
|
|
189
|
-
replay_before:
|
|
188
|
+
title: NoneOrMore[str] = None,
|
|
189
|
+
player_name: NoneOrMore[str] = None,
|
|
190
|
+
player_id: NoneOrMore[str] = None,
|
|
191
|
+
playlist: NoneOrMore[str] = None,
|
|
192
|
+
season: NoneOrMore[AnySeason] = None,
|
|
193
|
+
match_result: NoneOrMore[AnyMatchResult] = None,
|
|
194
|
+
min_rank: AnyRank | None = None,
|
|
195
|
+
max_rank: AnyRank | None = None,
|
|
196
|
+
pro: bool | None = None,
|
|
197
|
+
uploader: str | None = None,
|
|
198
|
+
group_id: NoneOrMore[str] = None,
|
|
199
|
+
map_id: NoneOrMore[str] = None,
|
|
200
|
+
created_before: str | datetime | None = None,
|
|
201
|
+
created_after: str | datetime | None = None,
|
|
202
|
+
replay_after: str | datetime | None = None,
|
|
203
|
+
replay_before: str | datetime | None = None,
|
|
190
204
|
count: int = 150,
|
|
191
|
-
sort_by:
|
|
205
|
+
sort_by: AnyReplaySortBy | None = None,
|
|
192
206
|
sort_dir: AnySortDir = SortDir.DESCENDING,
|
|
193
207
|
deep: bool = False,
|
|
194
|
-
typed:
|
|
195
|
-
|
|
208
|
+
typed: bool | None = None,
|
|
209
|
+
deduplicate: bool = False,
|
|
210
|
+
disable_prefetch: bool | None = None,
|
|
211
|
+
) -> Iterator[dict | ShallowReplay | DeepReplay]:
|
|
196
212
|
"""
|
|
197
213
|
This endpoint lets you filter and retrieve replays. The implementation returns an iterator.
|
|
198
214
|
|
|
@@ -226,12 +242,15 @@ class BallchasingApi:
|
|
|
226
242
|
:param sort_dir: sort direction
|
|
227
243
|
:param deep: whether to get full stats for each replay (will be much slower).
|
|
228
244
|
:param typed: whether to return a typed object (default is self.typed).
|
|
245
|
+
:param deduplicate: whether to deduplicate replays that seem to be the same game.
|
|
246
|
+
:param disable_prefetch: whether to disable prefetching replays.
|
|
229
247
|
:return: an iterator over the replays returned by the API.
|
|
230
248
|
"""
|
|
231
249
|
url = f"{self.base_url}/replays"
|
|
232
250
|
params = {"title": title, "player-name": player_name, "player-id": player_id, "playlist": playlist,
|
|
233
251
|
"season": season, "match-result": match_result, "min-rank": min_rank, "max-rank": max_rank,
|
|
234
|
-
"pro": pro, "uploader": uploader, "group": group_id,
|
|
252
|
+
"pro": str(pro).lower() if isinstance(pro, bool) else pro, "uploader": uploader, "group": group_id,
|
|
253
|
+
"map": map_id,
|
|
235
254
|
"created-before": to_rfc3339(created_before), "created-after": to_rfc3339(created_after),
|
|
236
255
|
"replay-date-after": to_rfc3339(replay_after), "replay-date-before": to_rfc3339(replay_before),
|
|
237
256
|
"count": count, "sort-by": sort_by, "sort-dir": sort_dir}
|
|
@@ -239,14 +258,23 @@ class BallchasingApi:
|
|
|
239
258
|
if typed is None:
|
|
240
259
|
typed = self.typed
|
|
241
260
|
|
|
242
|
-
|
|
261
|
+
if disable_prefetch is None:
|
|
262
|
+
disable_prefetch = deep
|
|
263
|
+
|
|
264
|
+
iterator = self._iterable_from_request(url, params, prefetch=not disable_prefetch)
|
|
243
265
|
if deep:
|
|
244
|
-
iterator = (self.get_replay(r["id"]
|
|
245
|
-
|
|
246
|
-
|
|
266
|
+
iterator = (self.get_replay(r["id"]) for r in iterator)
|
|
267
|
+
if deduplicate:
|
|
268
|
+
# Deep replays have match and replay IDs to deduplicate with. For shallow replays we check dates.
|
|
269
|
+
iterator = deduplicator(iterator, check_dates=not deep)
|
|
270
|
+
if typed:
|
|
271
|
+
if deep:
|
|
272
|
+
iterator = (DeepReplay(**r) for r in iterator)
|
|
273
|
+
else:
|
|
274
|
+
iterator = (ShallowReplay(**r) for r in iterator)
|
|
247
275
|
yield from iterator
|
|
248
276
|
|
|
249
|
-
def get_replay(self, replay_id: str, *, typed:
|
|
277
|
+
def get_replay(self, replay_id: str, *, typed: bool | None = None) -> dict | DeepReplay:
|
|
250
278
|
"""
|
|
251
279
|
Retrieve a given replay’s details and stats.
|
|
252
280
|
|
|
@@ -272,10 +300,10 @@ class BallchasingApi:
|
|
|
272
300
|
|
|
273
301
|
def upload_replay(
|
|
274
302
|
self,
|
|
275
|
-
replay_file:
|
|
303
|
+
replay_file: str | Path | BinaryIO,
|
|
276
304
|
*,
|
|
277
|
-
visibility:
|
|
278
|
-
group:
|
|
305
|
+
visibility: AnyVisibility | None = None,
|
|
306
|
+
group: str | None = None
|
|
279
307
|
) -> dict:
|
|
280
308
|
"""
|
|
281
309
|
Use this API to upload a replay file to ballchasing.com.
|
|
@@ -303,17 +331,18 @@ class BallchasingApi:
|
|
|
303
331
|
def get_groups(
|
|
304
332
|
self,
|
|
305
333
|
*,
|
|
306
|
-
name:
|
|
307
|
-
creator:
|
|
308
|
-
group:
|
|
309
|
-
created_before:
|
|
310
|
-
created_after:
|
|
334
|
+
name: str | None = None,
|
|
335
|
+
creator: str | None = None,
|
|
336
|
+
group: str | None = None,
|
|
337
|
+
created_before: str | datetime | None = None,
|
|
338
|
+
created_after: str | datetime | None = None,
|
|
311
339
|
count: int = 200,
|
|
312
340
|
sort_by: AnyGroupSortBy = GroupSortBy.CREATED,
|
|
313
341
|
sort_dir: AnySortDir = SortDir.DESCENDING,
|
|
314
342
|
deep: bool = False,
|
|
315
|
-
typed: bool = None,
|
|
316
|
-
|
|
343
|
+
typed: bool | None = None,
|
|
344
|
+
disable_prefetch: bool | None = None,
|
|
345
|
+
) -> Iterator[dict | ShallowGroup | DeepGroup]:
|
|
317
346
|
"""
|
|
318
347
|
This endpoint lets you filter and retrieve replay groups.
|
|
319
348
|
|
|
@@ -331,12 +360,17 @@ class BallchasingApi:
|
|
|
331
360
|
:param sort_dir: Sort direction.
|
|
332
361
|
:param deep: whether to get full stats for each group (will be much slower).
|
|
333
362
|
:param typed: whether to return a typed object (default is self.typed).
|
|
363
|
+
:param disable_prefetch: whether to disable prefetching.
|
|
334
364
|
:return: an iterator over the groups returned by the API.
|
|
335
365
|
"""
|
|
336
366
|
url = f"{self.base_url}/groups/"
|
|
337
367
|
params = {"name": name, "creator": creator, "group": group, "created-before": to_rfc3339(created_before),
|
|
338
368
|
"created-after": to_rfc3339(created_after), "count": count, "sort-by": sort_by, "sort-dir": sort_dir}
|
|
339
|
-
|
|
369
|
+
|
|
370
|
+
if disable_prefetch is None:
|
|
371
|
+
disable_prefetch = deep
|
|
372
|
+
|
|
373
|
+
iterator = self._iterable_from_request(url, params, prefetch=not disable_prefetch)
|
|
340
374
|
if typed is None:
|
|
341
375
|
typed = self.typed
|
|
342
376
|
if deep:
|
|
@@ -351,7 +385,7 @@ class BallchasingApi:
|
|
|
351
385
|
name: str,
|
|
352
386
|
player_identification: AnyPlayerIdentification,
|
|
353
387
|
team_identification: AnyTeamIdentification,
|
|
354
|
-
parent:
|
|
388
|
+
parent: str | None = None
|
|
355
389
|
) -> dict:
|
|
356
390
|
"""
|
|
357
391
|
Use this API to create a new replay group.
|
|
@@ -376,8 +410,8 @@ class BallchasingApi:
|
|
|
376
410
|
self,
|
|
377
411
|
group_id: str,
|
|
378
412
|
*,
|
|
379
|
-
typed:
|
|
380
|
-
) ->
|
|
413
|
+
typed: bool | None = None
|
|
414
|
+
) -> dict | DeepGroup:
|
|
381
415
|
"""
|
|
382
416
|
This endpoint retrieves a specific replay group info and stats given its id.
|
|
383
417
|
|
|
@@ -412,11 +446,11 @@ class BallchasingApi:
|
|
|
412
446
|
|
|
413
447
|
def get_group_replays(
|
|
414
448
|
self,
|
|
415
|
-
group:
|
|
449
|
+
group: str | dict | BasicGroup,
|
|
416
450
|
*,
|
|
417
451
|
deep: bool = False,
|
|
418
|
-
typed:
|
|
419
|
-
) -> Iterator[
|
|
452
|
+
typed: bool | None = None
|
|
453
|
+
) -> Iterator[dict | ShallowReplay | DeepReplay]:
|
|
420
454
|
"""
|
|
421
455
|
Finds all replays in a group, including child groups.
|
|
422
456
|
|
|
@@ -425,35 +459,36 @@ class BallchasingApi:
|
|
|
425
459
|
:param typed: whether to return a typed object (default is self.typed).
|
|
426
460
|
:return: an iterator over all the replays in the group.
|
|
427
461
|
"""
|
|
428
|
-
for path in self.get_group_tree(group, deep=deep, typed=typed):
|
|
429
|
-
group, replay = path
|
|
462
|
+
for path, replay in self.get_group_tree(group, deep=deep, typed=typed):
|
|
430
463
|
yield replay
|
|
431
464
|
|
|
432
465
|
def get_group_tree(
|
|
433
466
|
self,
|
|
434
|
-
group:
|
|
467
|
+
group: str | dict | BaseGroup,
|
|
435
468
|
*,
|
|
436
469
|
deep: bool = False,
|
|
437
|
-
typed:
|
|
438
|
-
):
|
|
470
|
+
typed: bool | None = None
|
|
471
|
+
) -> Iterator[tuple[list[str], dict | ShallowReplay | DeepReplay]]:
|
|
439
472
|
"""
|
|
440
|
-
Finds all replays in a group, and includes the
|
|
473
|
+
Finds all replays in a group, and includes the group path leading up to each replay.
|
|
474
|
+
|
|
441
475
|
:param group: the group id or a group dict.
|
|
442
476
|
:param deep: whether to get full stats for each replay and group (will be much slower).
|
|
443
477
|
:param typed: whether to return a typed object (default is self.typed).
|
|
478
|
+
:return: an iterator of (path, replay) tuples, where path is a list of group ids.
|
|
444
479
|
"""
|
|
445
480
|
if isinstance(group, str):
|
|
446
|
-
|
|
447
|
-
|
|
481
|
+
group_id = group
|
|
482
|
+
elif isinstance(group, BasicGroup):
|
|
448
483
|
group_id = group.id
|
|
449
484
|
else:
|
|
450
485
|
group_id = group["id"]
|
|
451
486
|
child_groups = self.get_groups(group=group_id, typed=typed)
|
|
452
487
|
for child in child_groups:
|
|
453
|
-
for path in self.get_group_tree(child, deep=deep, typed=typed):
|
|
454
|
-
yield group_id,
|
|
488
|
+
for path, replay in self.get_group_tree(child, deep=deep, typed=typed):
|
|
489
|
+
yield [group_id] + path, replay
|
|
455
490
|
for replay in self.get_replays(group_id=group_id, deep=deep, typed=typed):
|
|
456
|
-
yield group_id, replay
|
|
491
|
+
yield [group_id], replay
|
|
457
492
|
|
|
458
493
|
def download_replay(self, replay_id: str, path: str):
|
|
459
494
|
"""
|
|
@@ -496,7 +531,7 @@ class BallchasingApi:
|
|
|
496
531
|
res = self._request("/maps", "GET").json()
|
|
497
532
|
return res
|
|
498
533
|
|
|
499
|
-
def get_stats(self, replay:
|
|
534
|
+
def get_stats(self, replay: dict | str):
|
|
500
535
|
"""
|
|
501
536
|
Gets stats for players, teams and replay info.
|
|
502
537
|
|
ballchasing/constants.py
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
from typing import Literal, get_args,
|
|
2
|
-
|
|
3
|
-
AnyPlaylist = Union[
|
|
4
|
-
AnyStr,
|
|
5
|
-
Literal[
|
|
6
|
-
"unranked-duels", "unranked-doubles", "unranked-standard", "unranked-chaos",
|
|
7
|
-
"private", "season", "offline", "local-lobby",
|
|
8
|
-
"ranked-duels", "ranked-doubles", "ranked-solo-standard", "ranked-standard",
|
|
9
|
-
"snowday", "rocketlabs", "hoops", "rumble", "tournament", "dropshot",
|
|
10
|
-
"ranked-hoops", "ranked-rumble", "ranked-dropshot", "ranked-snowday",
|
|
11
|
-
"dropshot-rumble", "heatseeker", "gridiron", "spooky-cube"
|
|
12
|
-
]
|
|
13
|
-
]
|
|
1
|
+
from typing import Literal, get_args, TypeAlias, TypeVar, Sequence
|
|
14
2
|
|
|
15
3
|
|
|
16
4
|
def _get_literals(type_):
|
|
17
5
|
return get_args(get_args(type_)[1])
|
|
18
6
|
|
|
19
7
|
|
|
8
|
+
T = TypeVar('T')
|
|
9
|
+
OneOrMore: TypeAlias = T | Sequence[T]
|
|
10
|
+
NoneOrMore: TypeAlias = OneOrMore[T] | None
|
|
11
|
+
|
|
12
|
+
AnyPlaylist: TypeAlias = str | Literal[
|
|
13
|
+
"unranked-duels", "unranked-doubles", "unranked-standard", "unranked-chaos",
|
|
14
|
+
"private", "season", "offline", "local-lobby",
|
|
15
|
+
"ranked-duels", "ranked-doubles", "ranked-solo-standard", "ranked-standard",
|
|
16
|
+
"snowday", "rocketlabs", "hoops", "rumble", "tournament", "dropshot",
|
|
17
|
+
"ranked-hoops", "ranked-rumble", "ranked-dropshot", "ranked-snowday",
|
|
18
|
+
"dropshot-rumble", "heatseeker", "gridiron", "spooky-cube"
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
20
22
|
class Playlist:
|
|
21
23
|
ALL = (UNRANKED_DUELS, UNRANKED_DOUBLES, UNRANKED_STANDARD, UNRANKED_CHAOS, PRIVATE, SEASON, OFFLINE, LOCAL_LOBBY,
|
|
22
24
|
RANKED_DUELS, RANKED_DOUBLES, RANKED_SOLO_STANDARD, RANKED_STANDARD, SNOWDAY, ROCKETLABS, HOOPS, RUMBLE,
|
|
@@ -31,20 +33,17 @@ class Playlist:
|
|
|
31
33
|
MISC = (PRIVATE, SEASON, OFFLINE, LOCAL_LOBBY)
|
|
32
34
|
|
|
33
35
|
|
|
34
|
-
AnyRank =
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"grand-champion-1", "grand-champion-2", "grand-champion-3",
|
|
46
|
-
"supersonic-legend"
|
|
47
|
-
],
|
|
36
|
+
AnyRank: TypeAlias = str | Literal[
|
|
37
|
+
"unranked",
|
|
38
|
+
"bronze-1", "bronze-2", "bronze-3",
|
|
39
|
+
"silver-1", "silver-2", "silver-3",
|
|
40
|
+
"gold-1", "gold-2", "gold-3",
|
|
41
|
+
"platinum-1", "platinum-2", "platinum-3",
|
|
42
|
+
"diamond-1", "diamond-2", "diamond-3",
|
|
43
|
+
"champion-1", "champion-2", "champion-3",
|
|
44
|
+
"grand-champion", # Legacy. Seems to be interchangeable with "grand-champion-1"
|
|
45
|
+
"grand-champion-1", "grand-champion-2", "grand-champion-3",
|
|
46
|
+
"supersonic-legend"
|
|
48
47
|
]
|
|
49
48
|
|
|
50
49
|
|
|
@@ -70,13 +69,11 @@ class Rank:
|
|
|
70
69
|
GRAND_CHAMPION = (GRAND_CHAMPION_1, GRAND_CHAMPION_2, GRAND_CHAMPION_3)
|
|
71
70
|
|
|
72
71
|
|
|
73
|
-
AnySeason =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19"
|
|
79
|
-
]
|
|
72
|
+
AnySeason: TypeAlias = str | Literal[
|
|
73
|
+
"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14",
|
|
74
|
+
"f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9",
|
|
75
|
+
"f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19",
|
|
76
|
+
"f20", "f21", "f22"
|
|
80
77
|
]
|
|
81
78
|
|
|
82
79
|
|
|
@@ -87,79 +84,81 @@ class Season:
|
|
|
87
84
|
SEASON_13_LEGACY, SEASON_14_LEGACY,
|
|
88
85
|
SEASON_1_FTP, SEASON_2_FTP, SEASON_3_FTP, SEASON_4_FTP, SEASON_5_FTP, SEASON_6_FTP, SEASON_7_FTP,
|
|
89
86
|
SEASON_8_FTP, SEASON_9_FTP, SEASON_10_FTP, SEASON_11_FTP, SEASON_12_FTP, SEASON_13_FTP, SEASON_14_FTP,
|
|
90
|
-
SEASON_15_FTP, SEASON_16_FTP, SEASON_17_FTP, SEASON_18_FTP, SEASON_19_FTP
|
|
87
|
+
SEASON_15_FTP, SEASON_16_FTP, SEASON_17_FTP, SEASON_18_FTP, SEASON_19_FTP, SEASON_20_FTP, SEASON_21_FTP,
|
|
88
|
+
SEASON_22_FTP
|
|
91
89
|
) = _get_literals(AnySeason)
|
|
92
90
|
LEGACY = ALL[:14]
|
|
93
91
|
FREE_TO_PLAY = ALL[14:]
|
|
94
92
|
|
|
95
93
|
|
|
96
|
-
AnyMatchResult =
|
|
94
|
+
AnyMatchResult: TypeAlias = str | Literal["win", "loss"]
|
|
97
95
|
|
|
98
96
|
|
|
99
97
|
class MatchResult:
|
|
100
98
|
WIN, LOSS = _get_literals(AnyMatchResult)
|
|
101
99
|
|
|
102
100
|
|
|
103
|
-
AnyReplaySortBy =
|
|
101
|
+
AnyReplaySortBy: TypeAlias = str | Literal["replay-date", "upload-date"]
|
|
104
102
|
|
|
105
103
|
|
|
106
104
|
class ReplaySortBy:
|
|
107
105
|
REPLAY_DATE, UPLOAD_DATE = _get_literals(AnyReplaySortBy)
|
|
108
106
|
|
|
109
107
|
|
|
110
|
-
AnyGroupSortBy =
|
|
108
|
+
AnyGroupSortBy: TypeAlias = str | Literal["created", "name"]
|
|
111
109
|
|
|
112
110
|
|
|
113
111
|
class GroupSortBy:
|
|
114
112
|
CREATED, NAME = _get_literals(AnyGroupSortBy)
|
|
115
113
|
|
|
116
114
|
|
|
117
|
-
AnySortDir =
|
|
115
|
+
AnySortDir: TypeAlias = str | Literal["asc", "desc"]
|
|
118
116
|
|
|
119
117
|
|
|
120
118
|
class SortDir:
|
|
121
119
|
ASCENDING, DESCENDING = ASC, DESC = _get_literals(AnySortDir)
|
|
122
120
|
|
|
123
121
|
|
|
124
|
-
AnyVisibility =
|
|
122
|
+
AnyVisibility: TypeAlias = str | Literal["public", "unlisted", "private"]
|
|
125
123
|
|
|
126
124
|
|
|
127
125
|
class Visibility:
|
|
128
126
|
PUBLIC, UNLISTED, PRIVATE = _get_literals(AnyVisibility)
|
|
129
127
|
|
|
130
128
|
|
|
131
|
-
AnyPlayerIdentification =
|
|
129
|
+
AnyPlayerIdentification: TypeAlias = str | Literal["by-id", "by-name"]
|
|
132
130
|
|
|
133
131
|
|
|
134
132
|
class PlayerIdentification:
|
|
135
133
|
BY_ID, BY_NAME = _get_literals(AnyPlayerIdentification)
|
|
136
134
|
|
|
137
135
|
|
|
138
|
-
AnyTeamIdentification =
|
|
136
|
+
AnyTeamIdentification: TypeAlias = str | Literal["by-distinct-players", "by-player-clusters"]
|
|
139
137
|
|
|
140
138
|
|
|
141
139
|
class TeamIdentification:
|
|
142
140
|
BY_DISTINCT_PLAYERS, BY_PLAYER_CLUSTERS = _get_literals(AnyTeamIdentification)
|
|
143
141
|
|
|
144
142
|
|
|
145
|
-
AnyMap =
|
|
143
|
+
AnyMap: TypeAlias = str | Literal[
|
|
146
144
|
"arc_darc_p", "arc_p", "arc_standard_p", "bb_p", "beach_night_grs_p", "beach_night_p", "beach_p", "beachvolley",
|
|
147
145
|
"chn_stadium_day_p", "chn_stadium_p", "cs_day_p", "cs_hw_p", "cs_p", "eurostadium_dusk_p", "eurostadium_night_p",
|
|
148
146
|
"eurostadium_p", "eurostadium_rainy_p", "eurostadium_snownight_p", "farm_grs_p", "farm_hw_p", "farm_night_p",
|
|
149
147
|
"farm_p", "farm_upsidedown_p", "ff_dusk_p", "fni_stadium_p", "haunted_trainstation_p", "hoopsstadium_p",
|
|
150
|
-
"hoopsstreet_p", "ko_calavera_p", "ko_carbon_p", "ko_quadron_p", "
|
|
148
|
+
"hoopsstreet_p", "ko_calavera_p", "ko_carbon_p", "ko_quadron_p", "labs_4v4_arena15_blackout_p",
|
|
149
|
+
"labs_4v4_arena15_eurostadium_night_p", "labs_4v4_arena15_retro_p", "labs_basin_p", "labs_circlepillars_p",
|
|
151
150
|
"labs_corridor_p", "labs_cosmic_p", "labs_cosmic_v4_p", "labs_doublegoal_p", "labs_doublegoal_v2_p",
|
|
152
151
|
"labs_galleon_mast_p", "labs_galleon_p", "labs_holyfield_p", "labs_holyfield_space_p", "labs_octagon_02_p",
|
|
153
|
-
"labs_octagon_p", "labs_pillarglass_p", "labs_pillarheat_p", "labs_pillarwings_p",
|
|
154
|
-
"
|
|
155
|
-
"
|
|
156
|
-
"
|
|
157
|
-
"
|
|
158
|
-
"
|
|
159
|
-
"
|
|
160
|
-
"
|
|
161
|
-
"wasteland_s_p", "woods_night_p", "woods_p"
|
|
162
|
-
]
|
|
152
|
+
"labs_octagon_b2b_02_p", "labs_octagon_p", "labs_pillarglass_p", "labs_pillarheat_p", "labs_pillarwings_p",
|
|
153
|
+
"labs_underpass_p", "labs_underpass_v0_p", "labs_utopia_p", "mall_day_p", "music_p", "neotokyo_arcade_p",
|
|
154
|
+
"neotokyo_hax_p", "neotokyo_p", "neotokyo_standard_p", "neotokyo_toon_p", "outlaw_oasis_p", "outlaw_p",
|
|
155
|
+
"paname_dusk_p", "park_bman_p", "park_night_p", "park_p", "park_rainy_p", "park_snowy_p", "shattershot_p",
|
|
156
|
+
"stadium_10a_p", "stadium_day_p", "stadium_foggy_p", "stadium_p", "stadium_race_day_p", "stadium_winter_p",
|
|
157
|
+
"street_p", "swoosh_p", "throwbackhockey_p", "throwbackstadium_p", "trainstation_dawn_p", "trainstation_night_p",
|
|
158
|
+
"trainstation_p", "trainstation_spooky_p", "uf_day_p", "underwater_grs_p", "underwater_p", "utopiastadium_dusk_p",
|
|
159
|
+
"utopiastadium_lux_p", "utopiastadium_p", "utopiastadium_snow_p", "wasteland_grs_p", "wasteland_night_p",
|
|
160
|
+
"wasteland_night_s_p", "wasteland_p", "wasteland_s_p", "woods_night_p", "woods_p"
|
|
161
|
+
]
|
|
163
162
|
|
|
164
163
|
|
|
165
164
|
class Map:
|
|
@@ -168,17 +167,18 @@ class Map:
|
|
|
168
167
|
CHN_STADIUM_DAY_P, CHN_STADIUM_P, CS_DAY_P, CS_HW_P, CS_P, EUROSTADIUM_DUSK_P, EUROSTADIUM_NIGHT_P,
|
|
169
168
|
EUROSTADIUM_P, EUROSTADIUM_RAINY_P, EUROSTADIUM_SNOWNIGHT_P, FARM_GRS_P, FARM_HW_P, FARM_NIGHT_P, FARM_P,
|
|
170
169
|
FARM_UPSIDEDOWN_P, FF_DUSK_P, FNI_STADIUM_P, HAUNTED_TRAINSTATION_P, HOOPSSTADIUM_P, HOOPSSTREET_P,
|
|
171
|
-
KO_CALAVERA_P, KO_CARBON_P, KO_QUADRON_P,
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
170
|
+
KO_CALAVERA_P, KO_CARBON_P, KO_QUADRON_P, LABS_4V4_ARENA15_BLACKOUT_P, LABS_4V4_ARENA15_EUROSTADIUM_NIGHT_P,
|
|
171
|
+
LABS_4V4_ARENA15_RETRO_P, LABS_BASIN_P, LABS_CIRCLEPILLARS_P, LABS_CORRIDOR_P, LABS_COSMIC_P, LABS_COSMIC_V4_P,
|
|
172
|
+
LABS_DOUBLEGOAL_P, LABS_DOUBLEGOAL_V2_P, LABS_GALLEON_MAST_P, LABS_GALLEON_P, LABS_HOLYFIELD_P,
|
|
173
|
+
LABS_HOLYFIELD_SPACE_P, LABS_OCTAGON_02_P, LABS_OCTAGON_B2B_02_P, LABS_OCTAGON_P, LABS_PILLARGLASS_P,
|
|
174
|
+
LABS_PILLARHEAT_P, LABS_PILLARWINGS_P, LABS_UNDERPASS_P, LABS_UNDERPASS_V0_P, LABS_UTOPIA_P, MALL_DAY_P,
|
|
175
|
+
MUSIC_P, NEOTOKYO_ARCADE_P, NEOTOKYO_HAX_P, NEOTOKYO_P, NEOTOKYO_STANDARD_P, NEOTOKYO_TOON_P, OUTLAW_OASIS_P,
|
|
176
|
+
OUTLAW_P, PANAME_DUSK_P, PARK_BMAN_P, PARK_NIGHT_P, PARK_P, PARK_RAINY_P, PARK_SNOWY_P, SHATTERSHOT_P,
|
|
177
|
+
STADIUM_10A_P, STADIUM_DAY_P, STADIUM_FOGGY_P, STADIUM_P, STADIUM_RACE_DAY_P, STADIUM_WINTER_P, STREET_P,
|
|
178
|
+
SWOOSH_P, THROWBACKHOCKEY_P, THROWBACKSTADIUM_P, TRAINSTATION_DAWN_P, TRAINSTATION_NIGHT_P, TRAINSTATION_P,
|
|
179
|
+
TRAINSTATION_SPOOKY_P, UF_DAY_P, UNDERWATER_GRS_P, UNDERWATER_P, UTOPIASTADIUM_DUSK_P, UTOPIASTADIUM_LUX_P,
|
|
180
|
+
UTOPIASTADIUM_P, UTOPIASTADIUM_SNOW_P, WASTELAND_GRS_P, WASTELAND_NIGHT_P, WASTELAND_NIGHT_S_P, WASTELAND_P,
|
|
181
|
+
WASTELAND_S_P, WOODS_NIGHT_P, WOODS_P
|
|
182
182
|
) = _get_literals(AnyMap)
|
|
183
183
|
NAMES = (
|
|
184
184
|
"Starbase ARC (Aftermath)", "Starbase ARC", "Starbase ARC (Standard)", "Champions Field (NFL)",
|
|
@@ -187,12 +187,13 @@ class Map:
|
|
|
187
187
|
"Mannfield (Dusk)", "Mannfield (Night)", "Mannfield", "Mannfield (Stormy)", "Mannfield (Snowy)",
|
|
188
188
|
"Farmstead (Pitched)", "Farmstead (Spooky)", "Farmstead (Night)", "Farmstead", "Farmstead (The Upside Down)",
|
|
189
189
|
"Estadio Vida (Dusk)", "Forbidden Temple (Fire & Ice)", "Urban Central (Haunted)", "Dunk House",
|
|
190
|
-
"The Block (Dusk)", "Calavera", "Carbon", "Quadron", "
|
|
191
|
-
"
|
|
192
|
-
"
|
|
190
|
+
"The Block (Dusk)", "Calavera", "Carbon", "Quadron", "Midnight Metro (Quads)", "Mannfield (Quads)",
|
|
191
|
+
"Sunset Dunes (Quads)", "Basin", "Pillars", "Corridor", "Cosmic", "Cosmic", "Double Goal", "Double Goal",
|
|
192
|
+
"Galleon Retro", "Galleon", "Loophole", "Holyfield", "Octagon", "Roadblock", "Octagon", "Hourglass",
|
|
193
|
+
"Barricade", "Colossus", "Underpass", "Underpass", "Utopia Retro", "Boostfield Mall", "Neon Fields",
|
|
193
194
|
"Neo Tokyo (Arcade)", "Neo Tokyo (Hacked)", "Neo Tokyo", "Neo Tokyo (Standard)", "Neo Tokyo (Comic)",
|
|
194
|
-
"Deadeye Canyon (Oasis)", "Deadeye Canyon", "
|
|
195
|
-
"Beckwith Park", "Beckwith Park (Stormy)", "Beckwith Park (Snowy)", "Core 707",
|
|
195
|
+
"Deadeye Canyon (Oasis)", "Deadeye Canyon", "Parc De Paris", "Beckwith Park (Night)",
|
|
196
|
+
"Beckwith Park (Midnight)", "Beckwith Park", "Beckwith Park (Stormy)", "Beckwith Park (Snowy)", "Core 707",
|
|
196
197
|
"DFH Stadium (10th Anniversary)", "DFH Stadium (Day)", "DFH Stadium (Stormy)", "DFH Stadium",
|
|
197
198
|
"DFH Stadium (Circuit)", "DFH Stadium (Snowy)", "Sovereign Heights (Dusk)", "Champions Field (Nike FC)",
|
|
198
199
|
"Throwback Stadium (Snowy)", "Throwback Stadium", "Urban Central (Dawn)", "Urban Central (Night)",
|
|
@@ -205,19 +206,21 @@ class Map:
|
|
|
205
206
|
STANDARD_MAPS = (
|
|
206
207
|
ARC_DARC_P, ARC_STANDARD_P, BEACH_NIGHT_GRS_P, BEACH_NIGHT_P, BEACH_P, CHN_STADIUM_DAY_P, CHN_STADIUM_P,
|
|
207
208
|
CS_DAY_P, CS_HW_P, CS_P, EUROSTADIUM_DUSK_P, EUROSTADIUM_NIGHT_P, EUROSTADIUM_P, EUROSTADIUM_RAINY_P,
|
|
208
|
-
EUROSTADIUM_SNOWNIGHT_P, FARM_GRS_P, FARM_NIGHT_P, FARM_P, FARM_UPSIDEDOWN_P, FF_DUSK_P, FNI_STADIUM_P,
|
|
209
|
-
NEOTOKYO_ARCADE_P, NEOTOKYO_HAX_P, NEOTOKYO_STANDARD_P, OUTLAW_OASIS_P, OUTLAW_P,
|
|
210
|
-
PARK_RAINY_P, PARK_SNOWY_P, STADIUM_10A_P, STADIUM_DAY_P, STADIUM_FOGGY_P,
|
|
211
|
-
STADIUM_WINTER_P, STREET_P, TRAINSTATION_DAWN_P, TRAINSTATION_NIGHT_P,
|
|
212
|
-
UNDERWATER_GRS_P, UNDERWATER_P, UTOPIASTADIUM_DUSK_P, UTOPIASTADIUM_LUX_P,
|
|
213
|
-
UTOPIASTADIUM_SNOW_P, WASTELAND_GRS_P, WASTELAND_NIGHT_P, WASTELAND_NIGHT_S_P, WASTELAND_P,
|
|
214
|
-
WOODS_NIGHT_P, WOODS_P
|
|
209
|
+
EUROSTADIUM_SNOWNIGHT_P, FARM_GRS_P, FARM_NIGHT_P, FARM_P, FARM_UPSIDEDOWN_P, FF_DUSK_P, FNI_STADIUM_P,
|
|
210
|
+
MALL_DAY_P, MUSIC_P, NEOTOKYO_ARCADE_P, NEOTOKYO_HAX_P, NEOTOKYO_STANDARD_P, OUTLAW_OASIS_P, OUTLAW_P,
|
|
211
|
+
PANAME_DUSK_P, PARK_NIGHT_P, PARK_P, PARK_RAINY_P, PARK_SNOWY_P, STADIUM_10A_P, STADIUM_DAY_P, STADIUM_FOGGY_P,
|
|
212
|
+
STADIUM_P, STADIUM_RACE_DAY_P, STADIUM_WINTER_P, STREET_P, TRAINSTATION_DAWN_P, TRAINSTATION_NIGHT_P,
|
|
213
|
+
TRAINSTATION_P, UF_DAY_P, UNDERWATER_GRS_P, UNDERWATER_P, UTOPIASTADIUM_DUSK_P, UTOPIASTADIUM_LUX_P,
|
|
214
|
+
UTOPIASTADIUM_P, UTOPIASTADIUM_SNOW_P, WASTELAND_GRS_P, WASTELAND_NIGHT_P, WASTELAND_NIGHT_S_P, WASTELAND_P,
|
|
215
|
+
WASTELAND_S_P, WOODS_NIGHT_P, WOODS_P
|
|
215
216
|
)
|
|
216
217
|
NON_STANDARD_MAPS = (
|
|
217
218
|
ARC_P, BB_P, BEACHVOLLEY, FARM_HW_P, HAUNTED_TRAINSTATION_P, HOOPSSTADIUM_P, HOOPSSTREET_P, KO_CALAVERA_P,
|
|
218
|
-
KO_CARBON_P, KO_QUADRON_P,
|
|
219
|
+
KO_CARBON_P, KO_QUADRON_P, LABS_4V4_ARENA15_BLACKOUT_P, LABS_4V4_ARENA15_EUROSTADIUM_NIGHT_P,
|
|
220
|
+
LABS_4V4_ARENA15_RETRO_P, LABS_BASIN_P, LABS_CIRCLEPILLARS_P, LABS_CORRIDOR_P, LABS_COSMIC_P, LABS_COSMIC_V4_P,
|
|
219
221
|
LABS_DOUBLEGOAL_P, LABS_DOUBLEGOAL_V2_P, LABS_GALLEON_MAST_P, LABS_GALLEON_P, LABS_HOLYFIELD_P,
|
|
220
|
-
LABS_HOLYFIELD_SPACE_P, LABS_OCTAGON_02_P, LABS_OCTAGON_P, LABS_PILLARGLASS_P,
|
|
221
|
-
LABS_PILLARWINGS_P, LABS_UNDERPASS_P, LABS_UNDERPASS_V0_P, LABS_UTOPIA_P, NEOTOKYO_P,
|
|
222
|
-
PARK_BMAN_P, SHATTERSHOT_P, SWOOSH_P, THROWBACKHOCKEY_P, THROWBACKSTADIUM_P,
|
|
222
|
+
LABS_HOLYFIELD_SPACE_P, LABS_OCTAGON_02_P, LABS_OCTAGON_B2B_02_P, LABS_OCTAGON_P, LABS_PILLARGLASS_P,
|
|
223
|
+
LABS_PILLARHEAT_P, LABS_PILLARWINGS_P, LABS_UNDERPASS_P, LABS_UNDERPASS_V0_P, LABS_UTOPIA_P, NEOTOKYO_P,
|
|
224
|
+
NEOTOKYO_TOON_P, PARK_BMAN_P, SHATTERSHOT_P, SWOOSH_P, THROWBACKHOCKEY_P, THROWBACKSTADIUM_P,
|
|
225
|
+
TRAINSTATION_SPOOKY_P
|
|
223
226
|
)
|