python-ballchasing 0.2.0__tar.gz → 0.3.0__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,92 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-ballchasing
3
+ Version: 0.3.0
4
+ Summary: A Python wrapper around the Ballchasing API
5
+ Author-email: Rolv-Arild Braaten <rolv_arild@hotmail.com>
6
+ License: MIT License
7
+ Project-URL: Homepage, https://github.com/Rolv-Arild/python-ballchasing
8
+ Project-URL: Download, https://pypi.python.org/pypi/python-ballchasing
9
+ Classifier: Programming Language :: Python :: 3.8
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: requests
16
+ Dynamic: license-file
17
+
18
+ # Python Ballchasing
19
+ An easy-to-use and comprehensive Python wrapper for the [ballchasing.com API](https://ballchasing.com/doc/api).
20
+
21
+ ## Installation
22
+ You can install the library from PyPI using pip:
23
+ ```
24
+ pip install python-ballchasing
25
+ ```
26
+
27
+ ## Authentication
28
+ Before you can use the library, you need an API authentication key from ballchasing.com.
29
+ 1. Log in to [ballchasing.com](https://ballchasing.com/).
30
+ 2. Navigate to the Upload tab.
31
+ 3. Create an API key and copy it.
32
+
33
+ **Keep your API key secure and do not share it publicly.**
34
+
35
+ ## API
36
+ For detailed information, please refer to the docs inside the code. For the most part it follows the API spec closely, but there are some differences.
37
+
38
+ The API is exposed via the `BallchasingApi` class.
39
+
40
+ Making the client:
41
+ ```python
42
+ from ballchasing import BallchasingApi
43
+ api = BallchasingApi("Your token here")
44
+ ```
45
+ or equivalently
46
+ ```python
47
+ import ballchasing
48
+ api = ballchasing.Api("Your token here")
49
+ ```
50
+ By default this will also ping the API to make sure it's working.
51
+
52
+ ---
53
+ Some simple examples:
54
+ ```python
55
+ # Get lots of SSL replays
56
+
57
+ from ballchasing import Rank # there's also Playlist, Season, Map, and more
58
+
59
+ replays = api.get_replays(
60
+ min_rank=Rank.SUPERSONIC_LEGEND,
61
+ count=10_000 # The API limits you to 200 replays per request but the library handles this for you
62
+ )
63
+
64
+ for replay in replays: # (replays is an iterable so you don't need to wait for all the replays to be collected)
65
+ ... # Do something with the replays
66
+ ```
67
+
68
+ ```python
69
+ # Get a specific replay with more detail than the iterator (including stats!)
70
+ replay = api.get_replay("2627e02a-aa46-4e13-b66b-b76a32069a07")
71
+ ```
72
+
73
+ ```python
74
+ # Get groups by the "RLCS Referee" account
75
+ groups = api.get_groups(creator="76561199225615730")
76
+
77
+ for group in groups:
78
+ # Download the group
79
+ api.download_group(
80
+ group_id=group["id"],
81
+ folder="/path/to/destination/"
82
+ recursive=True, # To download all the replays and retain the group structure with subfolders
83
+ )
84
+
85
+ # Get replays from the group
86
+ replays = get_group_replays(
87
+ group_id=group["id"],
88
+ deep=True, # To get detailed replay info
89
+ )
90
+ for replay in replays:
91
+ api.download_replay(replay_id=replay["id"], folder="/path/to/destination/") # You could also download like this
92
+ ```
@@ -0,0 +1,75 @@
1
+ # Python Ballchasing
2
+ An easy-to-use and comprehensive Python wrapper for the [ballchasing.com API](https://ballchasing.com/doc/api).
3
+
4
+ ## Installation
5
+ You can install the library from PyPI using pip:
6
+ ```
7
+ pip install python-ballchasing
8
+ ```
9
+
10
+ ## Authentication
11
+ Before you can use the library, you need an API authentication key from ballchasing.com.
12
+ 1. Log in to [ballchasing.com](https://ballchasing.com/).
13
+ 2. Navigate to the Upload tab.
14
+ 3. Create an API key and copy it.
15
+
16
+ **Keep your API key secure and do not share it publicly.**
17
+
18
+ ## API
19
+ For detailed information, please refer to the docs inside the code. For the most part it follows the API spec closely, but there are some differences.
20
+
21
+ The API is exposed via the `BallchasingApi` class.
22
+
23
+ Making the client:
24
+ ```python
25
+ from ballchasing import BallchasingApi
26
+ api = BallchasingApi("Your token here")
27
+ ```
28
+ or equivalently
29
+ ```python
30
+ import ballchasing
31
+ api = ballchasing.Api("Your token here")
32
+ ```
33
+ By default this will also ping the API to make sure it's working.
34
+
35
+ ---
36
+ Some simple examples:
37
+ ```python
38
+ # Get lots of SSL replays
39
+
40
+ from ballchasing import Rank # there's also Playlist, Season, Map, and more
41
+
42
+ replays = api.get_replays(
43
+ min_rank=Rank.SUPERSONIC_LEGEND,
44
+ count=10_000 # The API limits you to 200 replays per request but the library handles this for you
45
+ )
46
+
47
+ for replay in replays: # (replays is an iterable so you don't need to wait for all the replays to be collected)
48
+ ... # Do something with the replays
49
+ ```
50
+
51
+ ```python
52
+ # Get a specific replay with more detail than the iterator (including stats!)
53
+ replay = api.get_replay("2627e02a-aa46-4e13-b66b-b76a32069a07")
54
+ ```
55
+
56
+ ```python
57
+ # Get groups by the "RLCS Referee" account
58
+ groups = api.get_groups(creator="76561199225615730")
59
+
60
+ for group in groups:
61
+ # Download the group
62
+ api.download_group(
63
+ group_id=group["id"],
64
+ folder="/path/to/destination/"
65
+ recursive=True, # To download all the replays and retain the group structure with subfolders
66
+ )
67
+
68
+ # Get replays from the group
69
+ replays = get_group_replays(
70
+ group_id=group["id"],
71
+ deep=True, # To get detailed replay info
72
+ )
73
+ for replay in replays:
74
+ api.download_replay(replay_id=replay["id"], folder="/path/to/destination/") # You could also download like this
75
+ ```
@@ -1,7 +1,8 @@
1
1
  import os
2
2
  import time
3
3
  from datetime import datetime
4
- from typing import Optional, Iterator, Union, List
4
+ from pathlib import Path
5
+ from typing import Optional, Iterator, Union, List, BinaryIO
5
6
  from urllib.parse import parse_qs, urlparse
6
7
 
7
8
  from requests import sessions, Response, ConnectionError
@@ -18,12 +19,14 @@ class BallchasingApi:
18
19
  Class for communication with ballchasing.com API (https://ballchasing.com/doc/api)
19
20
  """
20
21
 
21
- def __init__(self,
22
- auth_key: str,
23
- sleep_time_on_rate_limit: Optional[float] = None,
24
- print_on_rate_limit: bool = False,
25
- base_url=None,
26
- do_initial_ping=True):
22
+ def __init__(
23
+ self,
24
+ auth_key: str,
25
+ sleep_time_on_rate_limit: Optional[float] = None,
26
+ print_on_rate_limit: bool = False,
27
+ base_url=None,
28
+ do_initial_ping=True
29
+ ):
27
30
  """
28
31
 
29
32
  :param auth_key: authentication key for API calls.
@@ -74,11 +77,12 @@ class BallchasingApi:
74
77
  self.ping()
75
78
  return self._ping_result.get("quota")
76
79
 
77
- def _request(self,
78
- url_or_endpoint: str,
79
- method: str,
80
- **params
81
- ) -> Response:
80
+ def _request(
81
+ self,
82
+ url_or_endpoint: str,
83
+ method: str,
84
+ **params
85
+ ) -> Response:
82
86
  """
83
87
  Helper method for all requests.
84
88
 
@@ -92,7 +96,8 @@ class BallchasingApi:
92
96
  headers = {"Authorization": self.auth_key}
93
97
  url = f"{self.base_url}{url_or_endpoint}" if url_or_endpoint.startswith("/") else url_or_endpoint
94
98
  max_retries = 8
95
- for retries in range(max_retries):
99
+ retries = 0
100
+ while True:
96
101
  try:
97
102
  r: Response = self._session.request(method=method, url=url, headers=headers, **params)
98
103
  if 200 <= r.status_code < 300:
@@ -108,13 +113,14 @@ class BallchasingApi:
108
113
  elif self.sleep_time_on_rate_limit:
109
114
  time.sleep(self.sleep_time_on_rate_limit)
110
115
  else:
111
- r.raise_for_status() # Raise an error for any other status code
116
+ r.raise_for_status() # Raise an error for any other status code'
112
117
  except ConnectionError as e:
113
118
  if retries >= max_retries - 1:
114
119
  raise e
115
120
  s = 2 ** retries
116
121
  print(f"Connection error, trying again in {s} seconds...")
117
122
  time.sleep(s)
123
+ retries += 1
118
124
 
119
125
  def ping(self) -> dict:
120
126
  """
@@ -130,28 +136,29 @@ class BallchasingApi:
130
136
  self._ping_result = result
131
137
  return result
132
138
 
133
- def get_replays(self,
134
- title: Optional[str] = None,
135
- player_name: Optional[Union[str, List[str]]] = None,
136
- player_id: Optional[Union[str, List[str]]] = None,
137
- playlist: Optional[Union[AnyPlaylist, List[AnyPlaylist]]] = None,
138
- season: Optional[Union[AnySeason, List[AnySeason]]] = None,
139
- match_result: Optional[Union[AnyMatchResult, List[AnyMatchResult]]] = None,
140
- min_rank: Optional[AnyRank] = None,
141
- max_rank: Optional[AnyRank] = None,
142
- pro: Optional[bool] = None,
143
- uploader: Optional[str] = None,
144
- group_id: Optional[Union[str, List[str]]] = None,
145
- map_id: Optional[Union[AnyMap, List[AnyMap]]] = None,
146
- created_before: Optional[Union[str, datetime]] = None,
147
- created_after: Optional[Union[str, datetime]] = None,
148
- replay_after: Optional[Union[str, datetime]] = None,
149
- replay_before: Optional[Union[str, datetime]] = None,
150
- count: int = 150,
151
- sort_by: Optional[AnyReplaySortBy] = None,
152
- sort_dir: AnySortDir = SortDir.DESCENDING,
153
- deep: bool = False
154
- ) -> Iterator[dict]:
139
+ def get_replays(
140
+ self,
141
+ title: Optional[str] = None,
142
+ player_name: Optional[Union[str, List[str]]] = None,
143
+ player_id: Optional[Union[str, List[str]]] = None,
144
+ playlist: Optional[Union[AnyPlaylist, List[AnyPlaylist]]] = None,
145
+ season: Optional[Union[AnySeason, List[AnySeason]]] = None,
146
+ match_result: Optional[Union[AnyMatchResult, List[AnyMatchResult]]] = None,
147
+ min_rank: Optional[AnyRank] = None,
148
+ max_rank: Optional[AnyRank] = None,
149
+ pro: Optional[bool] = None,
150
+ uploader: Optional[str] = None,
151
+ group_id: Optional[Union[str, List[str]]] = None,
152
+ map_id: Optional[Union[AnyMap, List[AnyMap]]] = None,
153
+ created_before: Optional[Union[str, datetime]] = None,
154
+ created_after: Optional[Union[str, datetime]] = None,
155
+ replay_after: Optional[Union[str, datetime]] = None,
156
+ replay_before: Optional[Union[str, datetime]] = None,
157
+ count: int = 150,
158
+ sort_by: Optional[AnyReplaySortBy] = None,
159
+ sort_dir: AnySortDir = SortDir.DESCENDING,
160
+ deep: bool = False
161
+ ) -> Iterator[dict]:
155
162
  """
156
163
  This endpoint lets you filter and retrieve replays. The implementation returns an iterator.
157
164
 
@@ -230,18 +237,23 @@ class BallchasingApi:
230
237
  """
231
238
  self._request(f"/replays/{replay_id}", "PATCH", json=params)
232
239
 
233
- def upload_replay(self,
234
- replay_file,
235
- visibility: Optional[AnyVisibility] = None,
236
- group: Optional[str] = None) -> dict:
240
+ def upload_replay(
241
+ self,
242
+ replay_file: Union[str, Path, BinaryIO],
243
+ visibility: Optional[AnyVisibility] = None,
244
+ group: Optional[str] = None
245
+ ) -> dict:
237
246
  """
238
247
  Use this API to upload a replay file to ballchasing.com.
239
248
 
240
- :param replay_file: replay file to upload.
249
+ :param replay_file: replay file to upload. Can be a file path (str or Path) or a file-like object.
241
250
  :param visibility: to set the visibility of the uploaded replay.
242
251
  :param group: to upload the replay to an existing group.
243
252
  :return: the result of the POST request.
244
253
  """
254
+ if isinstance(replay_file, (str, Path)):
255
+ with open(replay_file, "rb") as f:
256
+ return self.upload_replay(f, visibility, group)
245
257
  return self._request(f"/v2/upload", "POST", files={"file": replay_file},
246
258
  params={"group": group, "visibility": visibility}).json()
247
259
 
@@ -254,16 +266,17 @@ class BallchasingApi:
254
266
  """
255
267
  self._request(f"/replays/{replay_id}", "DELETE")
256
268
 
257
- def get_groups(self,
258
- name: Optional[str] = None,
259
- creator: Optional[str] = None,
260
- group: Optional[str] = None,
261
- created_before: Optional[Union[str, datetime]] = None,
262
- created_after: Optional[Union[str, datetime]] = None,
263
- count: int = 200,
264
- sort_by: AnyGroupSortBy = GroupSortBy.CREATED,
265
- sort_dir: AnySortDir = SortDir.DESCENDING
266
- ) -> Iterator[dict]:
269
+ def get_groups(
270
+ self,
271
+ name: Optional[str] = None,
272
+ creator: Optional[str] = None,
273
+ group: Optional[str] = None,
274
+ created_before: Optional[Union[str, datetime]] = None,
275
+ created_after: Optional[Union[str, datetime]] = None,
276
+ count: int = 200,
277
+ sort_by: AnyGroupSortBy = GroupSortBy.CREATED,
278
+ sort_dir: AnySortDir = SortDir.DESCENDING
279
+ ) -> Iterator[dict]:
267
280
  """
268
281
  This endpoint lets you filter and retrieve replay groups.
269
282
 
@@ -301,12 +314,13 @@ class BallchasingApi:
301
314
  left -= len(batch)
302
315
  params["after"] = parse_qs(urlparse(next_url).query)["after"][0]
303
316
 
304
- def create_group(self,
305
- name: str,
306
- player_identification: AnyPlayerIdentification,
307
- team_identification: AnyTeamIdentification,
308
- parent: Optional[str] = None
309
- ) -> dict:
317
+ def create_group(
318
+ self,
319
+ name: str,
320
+ player_identification: AnyPlayerIdentification,
321
+ team_identification: AnyTeamIdentification,
322
+ parent: Optional[str] = None
323
+ ) -> dict:
310
324
  """
311
325
  Use this API to create a new replay group.
312
326
 
@@ -1,4 +1,4 @@
1
- from typing import Literal, get_args, AnyStr, Union, Sequence, Optional
1
+ from typing import Literal, get_args, AnyStr, Union
2
2
 
3
3
  AnyPlaylist = Union[
4
4
  AnyStr,
@@ -41,6 +41,7 @@ AnyRank = Union[
41
41
  "platinum-1", "platinum-2", "platinum-3",
42
42
  "diamond-1", "diamond-2", "diamond-3",
43
43
  "champion-1", "champion-2", "champion-3",
44
+ "grand-champion", # Legacy. Seems to be interchangeable with "grand-champion-1"
44
45
  "grand-champion-1", "grand-champion-2", "grand-champion-3",
45
46
  "supersonic-legend"
46
47
  ],
@@ -48,9 +49,18 @@ AnyRank = Union[
48
49
 
49
50
 
50
51
  class Rank:
51
- ALL = (UNRANKED, BRONZE_1, BRONZE_2, BRONZE_3, SILVER_1, SILVER_2, SILVER_3, GOLD_1, GOLD_2, GOLD_3, PLATINUM_1,
52
- PLATINUM_2, PLATINUM_3, DIAMOND_1, DIAMOND_2, DIAMOND_3, CHAMPION_1, CHAMPION_2, CHAMPION_3,
53
- GRAND_CHAMPION_1, GRAND_CHAMPION_2, GRAND_CHAMPION_3, SUPERSONIC_LEGEND) = _get_literals(AnyRank)
52
+ ALL = (
53
+ UNRANKED,
54
+ BRONZE_1, BRONZE_2, BRONZE_3,
55
+ SILVER_1, SILVER_2, SILVER_3,
56
+ GOLD_1, GOLD_2, GOLD_3,
57
+ PLATINUM_1, PLATINUM_2, PLATINUM_3,
58
+ DIAMOND_1, DIAMOND_2, DIAMOND_3,
59
+ CHAMPION_1, CHAMPION_2, CHAMPION_3,
60
+ GRAND_CHAMPION_LEGACY,
61
+ GRAND_CHAMPION_1, GRAND_CHAMPION_2, GRAND_CHAMPION_3,
62
+ SUPERSONIC_LEGEND
63
+ ) = _get_literals(AnyRank)
54
64
  BRONZE = (BRONZE_1, BRONZE_2, BRONZE_3)
55
65
  SILVER = (SILVER_1, SILVER_2, SILVER_3)
56
66
  GOLD = (GOLD_1, GOLD_2, GOLD_3)
@@ -65,18 +75,21 @@ AnySeason = Union[
65
75
  Literal[
66
76
  "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14",
67
77
  "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9",
68
- "f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18"
78
+ "f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19"
69
79
  ]
70
80
  ]
71
81
 
72
82
 
73
83
  class Season:
74
- ALL = (SEASON_1, SEASON_2, SEASON_3, SEASON_4, SEASON_5, SEASON_6, SEASON_7, SEASON_8, SEASON_9, SEASON_10,
75
- SEASON_11, SEASON_12, SEASON_13, SEASON_14, SEASON_1_FTP, SEASON_2_FTP, SEASON_3_FTP, SEASON_4_FTP,
76
- SEASON_5_FTP, SEASON_6_FTP, SEASON_7_FTP, SEASON_8_FTP, SEASON_9_FTP, SEASON_10_FTP, SEASON_11_FTP,
77
- SEASON_12_FTP, SEASON_13_FTP, SEASON_14_FTP, SEASON_15_FTP, SEASON_16_FTP, SEASON_17_FTP, SEASON_18_FTP) \
78
- = _get_literals(AnySeason)
79
- PAY_TO_PLAY = ALL[:14]
84
+ ALL = (
85
+ SEASON_1_LEGACY, SEASON_2_LEGACY, SEASON_3_LEGACY, SEASON_4_LEGACY, SEASON_5_LEGACY, SEASON_6_LEGACY,
86
+ SEASON_7_LEGACY, SEASON_8_LEGACY, SEASON_9_LEGACY, SEASON_10_LEGACY, SEASON_11_LEGACY, SEASON_12_LEGACY,
87
+ SEASON_13_LEGACY, SEASON_14_LEGACY,
88
+ SEASON_1_FTP, SEASON_2_FTP, SEASON_3_FTP, SEASON_4_FTP, SEASON_5_FTP, SEASON_6_FTP, SEASON_7_FTP,
89
+ 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
91
+ ) = _get_literals(AnySeason)
92
+ LEGACY = ALL[:14]
80
93
  FREE_TO_PLAY = ALL[14:]
81
94
 
82
95
 
@@ -134,13 +147,15 @@ AnyMap = Union[AnyStr, Literal[
134
147
  "chn_stadium_day_p", "chn_stadium_p", "cs_day_p", "cs_hw_p", "cs_p", "eurostadium_dusk_p", "eurostadium_night_p",
135
148
  "eurostadium_p", "eurostadium_rainy_p", "eurostadium_snownight_p", "farm_grs_p", "farm_hw_p", "farm_night_p",
136
149
  "farm_p", "farm_upsidedown_p", "ff_dusk_p", "fni_stadium_p", "haunted_trainstation_p", "hoopsstadium_p",
137
- "hoopsstreet_p", "labs_circlepillars_p", "labs_corridor_p", "labs_cosmic_p", "labs_cosmic_v4_p",
138
- "labs_doublegoal_p", "labs_doublegoal_v2_p", "labs_octagon_02_p", "labs_octagon_p", "labs_pillarglass_p",
139
- "labs_pillarwings_p", "labs_underpass_p", "labs_underpass_v0_p", "labs_utopia_p", "music_p", "neotokyo_arcade_p",
140
- "neotokyo_hax_p", "neotokyo_p", "neotokyo_standard_p", "neotokyo_toon_p", "outlaw_oasis_p", "outlaw_p",
141
- "park_bman_p", "park_night_p", "park_p", "park_rainy_p", "park_snowy_p", "shattershot_p", "stadium_day_p",
142
- "stadium_foggy_p", "stadium_p", "stadium_race_day_p", "stadium_winter_p", "street_p", "throwbackhockey_p",
143
- "throwbackstadium_p", "trainstation_dawn_p", "trainstation_night_p", "trainstation_p", "trainstation_spooky_p",
150
+ "hoopsstreet_p", "ko_calavera_p", "ko_carbon_p", "ko_quadron_p", "labs_basin_p", "labs_circlepillars_p",
151
+ "labs_corridor_p", "labs_cosmic_p", "labs_cosmic_v4_p", "labs_doublegoal_p", "labs_doublegoal_v2_p",
152
+ "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", "labs_underpass_p",
154
+ "labs_underpass_v0_p", "labs_utopia_p", "music_p", "neotokyo_arcade_p", "neotokyo_hax_p", "neotokyo_p",
155
+ "neotokyo_standard_p", "neotokyo_toon_p", "outlaw_oasis_p", "outlaw_p", "park_bman_p", "park_night_p", "park_p",
156
+ "park_rainy_p", "park_snowy_p", "shattershot_p", "stadium_10a_p", "stadium_day_p", "stadium_foggy_p", "stadium_p",
157
+ "stadium_race_day_p", "stadium_winter_p", "street_p", "swoosh_p", "throwbackhockey_p", "throwbackstadium_p",
158
+ "trainstation_dawn_p", "trainstation_night_p", "trainstation_p", "trainstation_spooky_p", "uf_day_p",
144
159
  "underwater_grs_p", "underwater_p", "utopiastadium_dusk_p", "utopiastadium_lux_p", "utopiastadium_p",
145
160
  "utopiastadium_snow_p", "wasteland_grs_p", "wasteland_night_p", "wasteland_night_s_p", "wasteland_p",
146
161
  "wasteland_s_p", "woods_night_p", "woods_p"
@@ -153,15 +168,17 @@ class Map:
153
168
  CHN_STADIUM_DAY_P, CHN_STADIUM_P, CS_DAY_P, CS_HW_P, CS_P, EUROSTADIUM_DUSK_P, EUROSTADIUM_NIGHT_P,
154
169
  EUROSTADIUM_P, EUROSTADIUM_RAINY_P, EUROSTADIUM_SNOWNIGHT_P, FARM_GRS_P, FARM_HW_P, FARM_NIGHT_P, FARM_P,
155
170
  FARM_UPSIDEDOWN_P, FF_DUSK_P, FNI_STADIUM_P, HAUNTED_TRAINSTATION_P, HOOPSSTADIUM_P, HOOPSSTREET_P,
156
- LABS_CIRCLEPILLARS_P, LABS_CORRIDOR_P, LABS_COSMIC_P, LABS_COSMIC_V4_P, LABS_DOUBLEGOAL_P, LABS_DOUBLEGOAL_V2_P,
157
- LABS_OCTAGON_02_P, LABS_OCTAGON_P, LABS_PILLARGLASS_P, LABS_PILLARWINGS_P, LABS_UNDERPASS_P,
158
- LABS_UNDERPASS_V0_P, LABS_UTOPIA_P, MUSIC_P, NEOTOKYO_ARCADE_P, NEOTOKYO_HAX_P, NEOTOKYO_P, NEOTOKYO_STANDARD_P,
159
- NEOTOKYO_TOON_P, OUTLAW_OASIS_P, OUTLAW_P, PARK_BMAN_P, PARK_NIGHT_P, PARK_P, PARK_RAINY_P, PARK_SNOWY_P,
160
- SHATTERSHOT_P, STADIUM_DAY_P, STADIUM_FOGGY_P, STADIUM_P, STADIUM_RACE_DAY_P, STADIUM_WINTER_P, STREET_P,
161
- THROWBACKHOCKEY_P, THROWBACKSTADIUM_P, TRAINSTATION_DAWN_P, TRAINSTATION_NIGHT_P, TRAINSTATION_P,
162
- TRAINSTATION_SPOOKY_P, UNDERWATER_GRS_P, UNDERWATER_P, UTOPIASTADIUM_DUSK_P, UTOPIASTADIUM_LUX_P,
163
- UTOPIASTADIUM_P, UTOPIASTADIUM_SNOW_P, WASTELAND_GRS_P, WASTELAND_NIGHT_P, WASTELAND_NIGHT_S_P, WASTELAND_P,
164
- WASTELAND_S_P, WOODS_NIGHT_P, WOODS_P
171
+ KO_CALAVERA_P, KO_CARBON_P, KO_QUADRON_P, LABS_BASIN_P, LABS_CIRCLEPILLARS_P, LABS_CORRIDOR_P, LABS_COSMIC_P,
172
+ LABS_COSMIC_V4_P, LABS_DOUBLEGOAL_P, LABS_DOUBLEGOAL_V2_P, LABS_GALLEON_MAST_P, LABS_GALLEON_P,
173
+ LABS_HOLYFIELD_P, LABS_HOLYFIELD_SPACE_P, LABS_OCTAGON_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, MUSIC_P,
175
+ NEOTOKYO_ARCADE_P, NEOTOKYO_HAX_P, NEOTOKYO_P, NEOTOKYO_STANDARD_P, NEOTOKYO_TOON_P, OUTLAW_OASIS_P, OUTLAW_P,
176
+ PARK_BMAN_P, PARK_NIGHT_P, PARK_P, PARK_RAINY_P, PARK_SNOWY_P, SHATTERSHOT_P, STADIUM_10A_P, STADIUM_DAY_P,
177
+ STADIUM_FOGGY_P, STADIUM_P, STADIUM_RACE_DAY_P, STADIUM_WINTER_P, STREET_P, SWOOSH_P, THROWBACKHOCKEY_P,
178
+ THROWBACKSTADIUM_P, TRAINSTATION_DAWN_P, TRAINSTATION_NIGHT_P, TRAINSTATION_P, TRAINSTATION_SPOOKY_P, UF_DAY_P,
179
+ UNDERWATER_GRS_P, UNDERWATER_P, UTOPIASTADIUM_DUSK_P, UTOPIASTADIUM_LUX_P, UTOPIASTADIUM_P,
180
+ UTOPIASTADIUM_SNOW_P, WASTELAND_GRS_P, WASTELAND_NIGHT_P, WASTELAND_NIGHT_S_P, WASTELAND_P, WASTELAND_S_P,
181
+ WOODS_NIGHT_P, WOODS_P
165
182
  ) = _get_literals(AnyMap)
166
183
  NAMES = (
167
184
  "Starbase ARC (Aftermath)", "Starbase ARC", "Starbase ARC (Standard)", "Champions Field (NFL)",
@@ -170,33 +187,37 @@ class Map:
170
187
  "Mannfield (Dusk)", "Mannfield (Night)", "Mannfield", "Mannfield (Stormy)", "Mannfield (Snowy)",
171
188
  "Farmstead (Pitched)", "Farmstead (Spooky)", "Farmstead (Night)", "Farmstead", "Farmstead (The Upside Down)",
172
189
  "Estadio Vida (Dusk)", "Forbidden Temple (Fire & Ice)", "Urban Central (Haunted)", "Dunk House",
173
- "The Block (Dusk)", "Pillars", "Corridor", "Cosmic", "Cosmic", "Double Goal", "Double Goal", "Octagon",
174
- "Octagon", "Hourglass", "Colossus", "Underpass", "Underpass", "Utopia Retro", "Neon Fields",
190
+ "The Block (Dusk)", "Calavera", "Carbon", "Quadron", "Basin", "Pillars", "Corridor", "Cosmic", "Cosmic",
191
+ "Double Goal", "Double Goal", "Galleon Retro", "Galleon", "Loophole", "Holyfield", "Octagon", "Octagon",
192
+ "Hourglass", "Barricade", "Colossus", "Underpass", "Underpass", "Utopia Retro", "Neon Fields",
175
193
  "Neo Tokyo (Arcade)", "Neo Tokyo (Hacked)", "Neo Tokyo", "Neo Tokyo (Standard)", "Neo Tokyo (Comic)",
176
194
  "Deadeye Canyon (Oasis)", "Deadeye Canyon", "Beckwith Park (Night)", "Beckwith Park (Midnight)",
177
- "Beckwith Park", "Beckwith Park (Stormy)", "Beckwith Park (Snowy)", "Core 707", "DFH Stadium (Day)",
178
- "DFH Stadium (Stormy)", "DFH Stadium", "DFH Stadium (Circuit)", "DFH Stadium (Snowy)",
179
- "Sovereign Heights (Dusk)", "Throwback Stadium (Snowy)", "Throwback Stadium", "Urban Central (Dawn)",
180
- "Urban Central (Night)", "Urban Central", "Urban Central (Spooky)", "AquaDome (Salty Shallows)", "Aquadome",
195
+ "Beckwith Park", "Beckwith Park (Stormy)", "Beckwith Park (Snowy)", "Core 707",
196
+ "DFH Stadium (10th Anniversary)", "DFH Stadium (Day)", "DFH Stadium (Stormy)", "DFH Stadium",
197
+ "DFH Stadium (Circuit)", "DFH Stadium (Snowy)", "Sovereign Heights (Dusk)", "Champions Field (Nike FC)",
198
+ "Throwback Stadium (Snowy)", "Throwback Stadium", "Urban Central (Dawn)", "Urban Central (Night)",
199
+ "Urban Central", "Urban Central (Spooky)", "Futura Garden", "AquaDome (Salty Shallows)", "Aquadome",
181
200
  "Utopia Coliseum (Dusk)", "Utopia Coliseum (Gilded)", "Utopia Coliseum", "Utopia Coliseum (Snowy)",
182
201
  "Wasteland (Pitched)", "Wasteland (Night)", "Wasteland (Standard, Night)", "Wasteland", "Wasteland (Standard)",
183
- "Drift Woods (Night", "Drift Woods"
202
+ "Drift Woods (Night)", "Drift Woods"
184
203
  )
185
204
  CODE_TO_NAME = {code: name for code, name in zip(ALL, NAMES)}
186
205
  STANDARD_MAPS = (
187
- ARC_DARC_P, ARC_STANDARD_P, BEACH_NIGHT_GRS_P, BEACH_NIGHT_P, CHN_STADIUM_DAY_P, CHN_STADIUM_P,
188
- EUROSTADIUM_DUSK_P, EUROSTADIUM_NIGHT_P, EUROSTADIUM_RAINY_P, EUROSTADIUM_SNOWNIGHT_P, FARM_GRS_P, FARM_NIGHT_P,
189
- FARM_P, FF_DUSK_P, FNI_STADIUM_P, LABS_PILLARGLASS_P, MUSIC_P, NEOTOKYO_ARCADE_P, NEOTOKYO_HAX_P,
190
- NEOTOKYO_STANDARD_P, NEOTOKYO_TOON_P, OUTLAW_OASIS_P, OUTLAW_P, PARK_NIGHT_P, PARK_P, PARK_RAINY_P,
191
- PARK_SNOWY_P, STADIUM_DAY_P, STADIUM_FOGGY_P, STADIUM_P, STADIUM_RACE_DAY_P, STADIUM_WINTER_P, STREET_P,
192
- TRAINSTATION_DAWN_P, TRAINSTATION_NIGHT_P, TRAINSTATION_P, UNDERWATER_GRS_P, UNDERWATER_P, UTOPIASTADIUM_DUSK_P,
193
- UTOPIASTADIUM_LUX_P, UTOPIASTADIUM_SNOW_P, UTOPIASTADIUM_P, WASTELAND_GRS_P, WASTELAND_NIGHT_P,
194
- WASTELAND_NIGHT_S_P, WASTELAND_S_P, WOODS_NIGHT_P, WOODS_P
206
+ ARC_DARC_P, ARC_STANDARD_P, BEACH_NIGHT_GRS_P, BEACH_NIGHT_P, BEACH_P, CHN_STADIUM_DAY_P, CHN_STADIUM_P,
207
+ 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, MUSIC_P,
209
+ NEOTOKYO_ARCADE_P, NEOTOKYO_HAX_P, NEOTOKYO_STANDARD_P, OUTLAW_OASIS_P, OUTLAW_P, PARK_NIGHT_P, PARK_P,
210
+ PARK_RAINY_P, PARK_SNOWY_P, STADIUM_10A_P, STADIUM_DAY_P, STADIUM_FOGGY_P, STADIUM_P, STADIUM_RACE_DAY_P,
211
+ STADIUM_WINTER_P, STREET_P, TRAINSTATION_DAWN_P, TRAINSTATION_NIGHT_P, TRAINSTATION_P, UF_DAY_P,
212
+ UNDERWATER_GRS_P, UNDERWATER_P, UTOPIASTADIUM_DUSK_P, UTOPIASTADIUM_LUX_P, UTOPIASTADIUM_P,
213
+ UTOPIASTADIUM_SNOW_P, WASTELAND_GRS_P, WASTELAND_NIGHT_P, WASTELAND_NIGHT_S_P, WASTELAND_P, WASTELAND_S_P,
214
+ WOODS_NIGHT_P, WOODS_P
195
215
  )
196
216
  NON_STANDARD_MAPS = (
197
- ARC_P, BB_P, BEACH_P, BEACHVOLLEY, CS_DAY_P, CS_HW_P, CS_P, EUROSTADIUM_P, FARM_HW_P,
198
- FARM_UPSIDEDOWN_P, HAUNTED_TRAINSTATION_P, HOOPSSTADIUM_P, HOOPSSTREET_P, LABS_CIRCLEPILLARS_P,
199
- LABS_COSMIC_P, LABS_COSMIC_V4_P, LABS_DOUBLEGOAL_P, LABS_DOUBLEGOAL_V2_P, LABS_OCTAGON_02_P,
200
- LABS_OCTAGON_P, LABS_UNDERPASS_P, LABS_UNDERPASS_V0_P, LABS_UTOPIA_P, NEOTOKYO_P, PARK_BMAN_P,
201
- SHATTERSHOT_P, THROWBACKHOCKEY_P, THROWBACKSTADIUM_P, TRAINSTATION_SPOOKY_P, WASTELAND_P
217
+ 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, LABS_BASIN_P, LABS_CIRCLEPILLARS_P, LABS_CORRIDOR_P, LABS_COSMIC_P, LABS_COSMIC_V4_P,
219
+ 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, LABS_PILLARHEAT_P,
221
+ LABS_PILLARWINGS_P, LABS_UNDERPASS_P, LABS_UNDERPASS_V0_P, LABS_UTOPIA_P, NEOTOKYO_P, NEOTOKYO_TOON_P,
222
+ PARK_BMAN_P, SHATTERSHOT_P, SWOOSH_P, THROWBACKHOCKEY_P, THROWBACKSTADIUM_P, TRAINSTATION_SPOOKY_P
202
223
  )
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-ballchasing"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  authors = [
9
9
  { name = "Rolv-Arild Braaten", email = "rolv_arild@hotmail.com" },
10
10
  ]
@@ -0,0 +1,92 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-ballchasing
3
+ Version: 0.3.0
4
+ Summary: A Python wrapper around the Ballchasing API
5
+ Author-email: Rolv-Arild Braaten <rolv_arild@hotmail.com>
6
+ License: MIT License
7
+ Project-URL: Homepage, https://github.com/Rolv-Arild/python-ballchasing
8
+ Project-URL: Download, https://pypi.python.org/pypi/python-ballchasing
9
+ Classifier: Programming Language :: Python :: 3.8
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: requests
16
+ Dynamic: license-file
17
+
18
+ # Python Ballchasing
19
+ An easy-to-use and comprehensive Python wrapper for the [ballchasing.com API](https://ballchasing.com/doc/api).
20
+
21
+ ## Installation
22
+ You can install the library from PyPI using pip:
23
+ ```
24
+ pip install python-ballchasing
25
+ ```
26
+
27
+ ## Authentication
28
+ Before you can use the library, you need an API authentication key from ballchasing.com.
29
+ 1. Log in to [ballchasing.com](https://ballchasing.com/).
30
+ 2. Navigate to the Upload tab.
31
+ 3. Create an API key and copy it.
32
+
33
+ **Keep your API key secure and do not share it publicly.**
34
+
35
+ ## API
36
+ For detailed information, please refer to the docs inside the code. For the most part it follows the API spec closely, but there are some differences.
37
+
38
+ The API is exposed via the `BallchasingApi` class.
39
+
40
+ Making the client:
41
+ ```python
42
+ from ballchasing import BallchasingApi
43
+ api = BallchasingApi("Your token here")
44
+ ```
45
+ or equivalently
46
+ ```python
47
+ import ballchasing
48
+ api = ballchasing.Api("Your token here")
49
+ ```
50
+ By default this will also ping the API to make sure it's working.
51
+
52
+ ---
53
+ Some simple examples:
54
+ ```python
55
+ # Get lots of SSL replays
56
+
57
+ from ballchasing import Rank # there's also Playlist, Season, Map, and more
58
+
59
+ replays = api.get_replays(
60
+ min_rank=Rank.SUPERSONIC_LEGEND,
61
+ count=10_000 # The API limits you to 200 replays per request but the library handles this for you
62
+ )
63
+
64
+ for replay in replays: # (replays is an iterable so you don't need to wait for all the replays to be collected)
65
+ ... # Do something with the replays
66
+ ```
67
+
68
+ ```python
69
+ # Get a specific replay with more detail than the iterator (including stats!)
70
+ replay = api.get_replay("2627e02a-aa46-4e13-b66b-b76a32069a07")
71
+ ```
72
+
73
+ ```python
74
+ # Get groups by the "RLCS Referee" account
75
+ groups = api.get_groups(creator="76561199225615730")
76
+
77
+ for group in groups:
78
+ # Download the group
79
+ api.download_group(
80
+ group_id=group["id"],
81
+ folder="/path/to/destination/"
82
+ recursive=True, # To download all the replays and retain the group structure with subfolders
83
+ )
84
+
85
+ # Get replays from the group
86
+ replays = get_group_replays(
87
+ group_id=group["id"],
88
+ deep=True, # To get detailed replay info
89
+ )
90
+ for replay in replays:
91
+ api.download_replay(replay_id=replay["id"], folder="/path/to/destination/") # You could also download like this
92
+ ```
@@ -1,36 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: python-ballchasing
3
- Version: 0.2.0
4
- Summary: A Python wrapper around the Ballchasing API
5
- Author-email: Rolv-Arild Braaten <rolv_arild@hotmail.com>
6
- License: MIT License
7
- Project-URL: Homepage, https://github.com/Rolv-Arild/python-ballchasing
8
- Project-URL: Download, https://pypi.python.org/pypi/python-ballchasing
9
- Classifier: Programming Language :: Python :: 3.8
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Operating System :: OS Independent
12
- Requires-Python: >=3.9
13
- Description-Content-Type: text/markdown
14
- License-File: LICENSE
15
- Requires-Dist: requests
16
- Dynamic: license-file
17
-
18
- # Python Ballchasing
19
- Python wrapper for the ballchasing.com API.
20
-
21
- # Installation
22
- ```
23
- pip install python-ballchasing
24
- ```
25
-
26
- # API
27
- The API is exposed via the `ballchasing.Api` class.
28
-
29
- Simple example:
30
- ```python
31
- import ballchasing
32
- api = ballchasing.Api("Your token here")
33
-
34
- # Get a specific replay
35
- replay = api.get_replay("2627e02a-aa46-4e13-b66b-b76a32069a07")
36
- ```
@@ -1,19 +0,0 @@
1
- # Python Ballchasing
2
- Python wrapper for the ballchasing.com API.
3
-
4
- # Installation
5
- ```
6
- pip install python-ballchasing
7
- ```
8
-
9
- # API
10
- The API is exposed via the `ballchasing.Api` class.
11
-
12
- Simple example:
13
- ```python
14
- import ballchasing
15
- api = ballchasing.Api("Your token here")
16
-
17
- # Get a specific replay
18
- replay = api.get_replay("2627e02a-aa46-4e13-b66b-b76a32069a07")
19
- ```
@@ -1,36 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: python-ballchasing
3
- Version: 0.2.0
4
- Summary: A Python wrapper around the Ballchasing API
5
- Author-email: Rolv-Arild Braaten <rolv_arild@hotmail.com>
6
- License: MIT License
7
- Project-URL: Homepage, https://github.com/Rolv-Arild/python-ballchasing
8
- Project-URL: Download, https://pypi.python.org/pypi/python-ballchasing
9
- Classifier: Programming Language :: Python :: 3.8
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Operating System :: OS Independent
12
- Requires-Python: >=3.9
13
- Description-Content-Type: text/markdown
14
- License-File: LICENSE
15
- Requires-Dist: requests
16
- Dynamic: license-file
17
-
18
- # Python Ballchasing
19
- Python wrapper for the ballchasing.com API.
20
-
21
- # Installation
22
- ```
23
- pip install python-ballchasing
24
- ```
25
-
26
- # API
27
- The API is exposed via the `ballchasing.Api` class.
28
-
29
- Simple example:
30
- ```python
31
- import ballchasing
32
- api = ballchasing.Api("Your token here")
33
-
34
- # Get a specific replay
35
- replay = api.get_replay("2627e02a-aa46-4e13-b66b-b76a32069a07")
36
- ```