infinitode.py 1.2.0__tar.gz → 1.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.
- {infinitode_py-1.2.0/infinitode.py.egg-info → infinitode_py-1.3.0}/PKG-INFO +46 -1
- infinitode_py-1.2.0/PKG-INFO → infinitode_py-1.3.0/README.md +289 -265
- {infinitode_py-1.2.0 → infinitode_py-1.3.0}/infinitode/__init__.py +41 -39
- {infinitode_py-1.2.0 → infinitode_py-1.3.0}/infinitode/core.py +668 -634
- infinitode_py-1.3.0/infinitode/daily_quest.py +27 -0
- {infinitode_py-1.2.0 → infinitode_py-1.3.0}/infinitode/errors.py +39 -39
- infinitode_py-1.3.0/infinitode/stats/__init__.py +9 -0
- infinitode_py-1.3.0/infinitode/stats/_parsers/__init__.py +1 -0
- infinitode_py-1.3.0/infinitode/stats/_parsers/common.py +303 -0
- infinitode_py-1.3.0/infinitode/stats/_parsers/index.py +56 -0
- infinitode_py-1.3.0/infinitode/stats/_parsers/replay.py +49 -0
- infinitode_py-1.3.0/infinitode/stats/_parsers/statistics.py +119 -0
- infinitode_py-1.3.0/infinitode/stats/_parsers/summary.py +30 -0
- infinitode_py-1.3.0/infinitode/stats/_parsers/timelines.py +146 -0
- infinitode_py-1.3.0/infinitode/stats/client.py +93 -0
- infinitode_py-1.3.0/infinitode/stats/keys.py +101 -0
- infinitode_py-1.3.0/infinitode/stats/models/__init__.py +53 -0
- infinitode_py-1.3.0/infinitode/stats/models/common.py +104 -0
- infinitode_py-1.3.0/infinitode/stats/models/reports.py +163 -0
- infinitode_py-1.3.0/infinitode/stats/query.py +50 -0
- infinitode_py-1.3.0/infinitode/stats/views.py +232 -0
- infinitode_py-1.2.0/README.md → infinitode_py-1.3.0/infinitode.py.egg-info/PKG-INFO +64 -0
- infinitode_py-1.3.0/infinitode.py.egg-info/SOURCES.txt +32 -0
- {infinitode_py-1.2.0 → infinitode_py-1.3.0}/setup.py +27 -26
- infinitode_py-1.2.0/infinitode.py.egg-info/SOURCES.txt +0 -17
- infinitode_py-1.2.0/tests/test_core.py +0 -235
- {infinitode_py-1.2.0 → infinitode_py-1.3.0}/infinitode/badge.py +0 -0
- {infinitode_py-1.2.0 → infinitode_py-1.3.0}/infinitode/leaderboard.py +0 -0
- {infinitode_py-1.2.0 → infinitode_py-1.3.0}/infinitode/player.py +0 -0
- {infinitode_py-1.2.0 → infinitode_py-1.3.0}/infinitode/py.typed +0 -0
- {infinitode_py-1.2.0 → infinitode_py-1.3.0}/infinitode/score.py +0 -0
- {infinitode_py-1.2.0 → infinitode_py-1.3.0}/infinitode/utils.py +0 -0
- {infinitode_py-1.2.0 → infinitode_py-1.3.0}/infinitode.py.egg-info/dependency_links.txt +0 -0
- {infinitode_py-1.2.0 → infinitode_py-1.3.0}/infinitode.py.egg-info/requires.txt +0 -0
- {infinitode_py-1.2.0 → infinitode_py-1.3.0}/infinitode.py.egg-info/top_level.txt +0 -0
- {infinitode_py-1.2.0 → infinitode_py-1.3.0}/setup.cfg +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: infinitode.py
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: A python wrapper for the Infinitode 2 API.
|
|
5
5
|
Home-page: https://github.com/Sprylos/infinitode.py
|
|
6
6
|
Author: Sprylos
|
|
7
7
|
License: MIT
|
|
8
|
+
Requires-Python: >=3.10
|
|
8
9
|
Description-Content-Type: text/markdown
|
|
9
10
|
Requires-Dist: aiohttp
|
|
10
11
|
Requires-Dist: lxml
|
|
@@ -15,6 +16,7 @@ Dynamic: description-content-type
|
|
|
15
16
|
Dynamic: home-page
|
|
16
17
|
Dynamic: license
|
|
17
18
|
Dynamic: requires-dist
|
|
19
|
+
Dynamic: requires-python
|
|
18
20
|
Dynamic: summary
|
|
19
21
|
|
|
20
22
|
# Infinitode.py
|
|
@@ -126,6 +128,15 @@ Retrieve the top 200 players of today's daily quest or a specified date.
|
|
|
126
128
|
dq_leaderboard = await API.daily_quest_leaderboards("2024-12-05")
|
|
127
129
|
```
|
|
128
130
|
|
|
131
|
+
Retrieve metadata for the currently active Daily Quest. The reset timestamp is
|
|
132
|
+
also available as `reset_timestamp`, and `mapname` is derived from `quest_id`.
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
dq = await API.daily_quest_info()
|
|
136
|
+
print(dq.date, dq.quest_id, dq.mapname)
|
|
137
|
+
print(dq.end_timestamp, dq.reset_timestamp, dq.data_hash)
|
|
138
|
+
```
|
|
139
|
+
|
|
129
140
|
---
|
|
130
141
|
|
|
131
142
|
### Working with Leaderboards
|
|
@@ -227,6 +238,40 @@ updated.
|
|
|
227
238
|
|
|
228
239
|
---
|
|
229
240
|
|
|
241
|
+
### Replay statistics
|
|
242
|
+
|
|
243
|
+
`infinitode.stats` provides an independent async client for the public stats
|
|
244
|
+
website. Python 3.10 or later is required.
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
from infinitode.stats import StatsClient, ReplayQuery
|
|
248
|
+
|
|
249
|
+
async def inspect_stats():
|
|
250
|
+
async with StatsClient() as stats:
|
|
251
|
+
query = ReplayQuery(report_count=10, game_modes=("BASIC_LEVELS",))
|
|
252
|
+
browser = await stats.replays(query)
|
|
253
|
+
print(browser.available_filters.maps)
|
|
254
|
+
|
|
255
|
+
aggregate = await stats.summary(query)
|
|
256
|
+
print(aggregate.statistics.general.towers_built)
|
|
257
|
+
|
|
258
|
+
replay = await stats.replay("R-RLX6-YSQL-LGNNCV")
|
|
259
|
+
print(replay.info.mapname, replay.overview.duration)
|
|
260
|
+
print(replay.statistics.towers.tower("GAUSS").damage)
|
|
261
|
+
print(replay.statistics.resources.resource("SCALAR").gained)
|
|
262
|
+
|
|
263
|
+
score = replay.statistics.score_sources
|
|
264
|
+
if score is not None:
|
|
265
|
+
for entry in score.entries:
|
|
266
|
+
print(entry.entity.key, entry.value, entry.share)
|
|
267
|
+
for timeline in score.timelines or ():
|
|
268
|
+
for point in timeline.points:
|
|
269
|
+
print(timeline.entity.key, point.elapsed, point.value)
|
|
270
|
+
|
|
271
|
+
for issue in replay.metadata.issues:
|
|
272
|
+
print(issue.code, issue.section, issue.entity_key, issue.sample_index)
|
|
273
|
+
```
|
|
274
|
+
|
|
230
275
|
### Beta Scores
|
|
231
276
|
|
|
232
277
|
Almost all API calls support an additional `beta` boolean parameter. This will make a request to the beta servers instead. No guarantees for it working.
|
|
@@ -1,265 +1,289 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
###
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
####
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
`
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
1
|
+
# Infinitode.py
|
|
2
|
+
|
|
3
|
+
An asynchronous Python wrapper for the Infinitode-2 API using `async`-`await` syntax.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Install the library via pip:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install infinitode.py
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Showcase
|
|
18
|
+
|
|
19
|
+
### Setting Up a Session
|
|
20
|
+
|
|
21
|
+
Create a session to communicate with Rainy's API. Use the `with` statement to ensure the session closes properly.
|
|
22
|
+
Alternatively you can can simply call `Session.close()` once you don't need it anymore.
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
import asyncio
|
|
26
|
+
import infinitode
|
|
27
|
+
|
|
28
|
+
async def main():
|
|
29
|
+
async with infinitode.Session() as API:
|
|
30
|
+
# Your API calls go here
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
asyncio.run(main())
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
### Fetching Leaderboard Data
|
|
39
|
+
|
|
40
|
+
Parameters:
|
|
41
|
+
- `mapname`: The map identifier (e.g., "5.1" or "4.b1" or "zecred").
|
|
42
|
+
- `playerid` (sometimes optional): The player's unique identifier.
|
|
43
|
+
- `mode` (optional): One of `'score'`, `'waves'`. Defaults to `'score'`.
|
|
44
|
+
- `difficulty` (optional): One of `'EASY'`, `'NORMAL'`, `'ENDLESS_I'`. Defaults to `'NORMAL'`.
|
|
45
|
+
|
|
46
|
+
The immutable `infinitode.SUPPORTED_MAPS`, `infinitode.SUPPORTED_MODES`, and
|
|
47
|
+
`infinitode.SUPPORTED_DIFFICULTIES` tuples contain the values accepted by the
|
|
48
|
+
current library. `infinitode.GAME_API_VERSION` exposes the game API version used
|
|
49
|
+
for JSON API calls.
|
|
50
|
+
|
|
51
|
+
#### Player Score on a Specific Map
|
|
52
|
+
|
|
53
|
+
Retrieve the score of a specific player on a particular map.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
score_5_1 = await API.leaderboards_rank(
|
|
57
|
+
mapname="5.1",
|
|
58
|
+
playerid="U-E9BP-FSN9-H6ENMQ",
|
|
59
|
+
mode="score",
|
|
60
|
+
difficulty="NORMAL"
|
|
61
|
+
)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
#### Top 200 Leaderboard on a Specific Map
|
|
65
|
+
|
|
66
|
+
Retrieve the leaderboard of the top 200 wave scores on 5.1.
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
leaderboard_5_1 = await API.leaderboards(
|
|
70
|
+
mapname=5.1,
|
|
71
|
+
mode="waves"
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
#### Runtime Leaderboard
|
|
76
|
+
|
|
77
|
+
Fetch the leaderboard displayed during gameplay. This provides additional percentile information.
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
runtime_5_1 = await API.runtime_leaderboards(
|
|
81
|
+
mapname=5.1,
|
|
82
|
+
playerid="U-E9BP-FSN9-H6ENMQ",
|
|
83
|
+
difficulty="ENDLESS_I"
|
|
84
|
+
)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
#### Skill Point Leaderboard
|
|
90
|
+
|
|
91
|
+
Get the top 3 skill point owners. Optionally specify a `playerid`.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
sp_leaderboard = await API.skill_point_leaderboard(
|
|
95
|
+
playerid="U-E9BP-FSN9-H6ENMQ"
|
|
96
|
+
)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
#### Daily Quest Leaderboard
|
|
101
|
+
|
|
102
|
+
Retrieve the top 200 players of today's daily quest or a specified date.
|
|
103
|
+
|
|
104
|
+
- `date`: `YYYY-MM-DD` (string) or `datetime.datetime`.
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
dq_leaderboard = await API.daily_quest_leaderboards("2024-12-05")
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Retrieve metadata for the currently active Daily Quest. The reset timestamp is
|
|
111
|
+
also available as `reset_timestamp`, and `mapname` is derived from `quest_id`.
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
dq = await API.daily_quest_info()
|
|
115
|
+
print(dq.date, dq.quest_id, dq.mapname)
|
|
116
|
+
print(dq.end_timestamp, dq.reset_timestamp, dq.data_hash)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
### Working with Leaderboards
|
|
122
|
+
|
|
123
|
+
A leaderboard is a sequence of scores with additional utility functions.
|
|
124
|
+
|
|
125
|
+
#### Access Score and Leaderboard Attributes
|
|
126
|
+
|
|
127
|
+
Leaderboards always have these attributes:
|
|
128
|
+
- `mapname`, `mode`, `difficulty`, `total`
|
|
129
|
+
|
|
130
|
+
And optionally these:
|
|
131
|
+
- `date`, `season`, `player` (will return a score object)
|
|
132
|
+
|
|
133
|
+
Scores always have these attributes:
|
|
134
|
+
- `playerid`, `rank`, `score`, `mapname`, `mode`, `difficulty`
|
|
135
|
+
|
|
136
|
+
And optionally these:
|
|
137
|
+
- `has_pfp`, `level`, `nickname`, `pinned_badge`, `position`, `top`, `total`, `player` (will return a Player object if fetched beforehand)
|
|
138
|
+
|
|
139
|
+
#### Example for printing attributes, scores, and leaderboards
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
for score in leaderboard_5_1:
|
|
143
|
+
print(score.nickname)
|
|
144
|
+
score.print_score() # Helper method
|
|
145
|
+
|
|
146
|
+
leaderboard_5_1.print_scores()
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### Slice a Leaderboard
|
|
150
|
+
|
|
151
|
+
Retrieve a subset of the leaderboard:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
top_1 = leaderboard_5_1[0]
|
|
155
|
+
top_10 = leaderboard_5_1[0:10]
|
|
156
|
+
print(len(top_10))
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
### Player Information
|
|
162
|
+
|
|
163
|
+
#### Fetch Player Data
|
|
164
|
+
|
|
165
|
+
Retrieve detailed player information.
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
player = await API.player(playerid="U-E9BP-FSN9-H6ENMQ")
|
|
169
|
+
# Or look up a case-insensitive, exact nickname:
|
|
170
|
+
player = await API.player(nickname="EuphoRowan")
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Attributes include:
|
|
174
|
+
- `playerid`, `nickname`, `level`, `xp`, `season_level`, `total_score`, and many more.
|
|
175
|
+
|
|
176
|
+
#### Get Player Scores
|
|
177
|
+
|
|
178
|
+
Use the `score` method to access specific map scores.
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
player.score(5.1).print_score()
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
#### Fetch Daily Quest and Skill Point Scores
|
|
185
|
+
|
|
186
|
+
Use additional API calls to retrieve daily quest and skill point scores:
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
await player.fetch_daily_quest(API)
|
|
190
|
+
await player.fetch_skill_point(API)
|
|
191
|
+
|
|
192
|
+
print(player.daily_quest.rank, player.skill_point.score)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
An invalid argument raises `infinitode.errors.BadArgument`, and an exact lookup
|
|
196
|
+
with no matching player raises its `PlayerNotFound` subclass. HTTP and JSON API
|
|
197
|
+
failures raise `APIError`; unexpected changes to parsed HTML raise `ParseError`.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
### Experimental features
|
|
202
|
+
|
|
203
|
+
`Session.search_players()` performs an unauthenticated, case-insensitive
|
|
204
|
+
substring search of player nicknames:
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
players = await API.search_players("eupho", limit=20)
|
|
208
|
+
for result in players:
|
|
209
|
+
print(result.playerid, result.nickname, result.level, result.has_avatar)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Each `PlayerSummary` contains only the verified `playerid`, `nickname`, `level`,
|
|
213
|
+
and `has_avatar` fields. The upstream page returns at most 100 results and does
|
|
214
|
+
not offer pagination. This feature parses an HTML page rather than a stable JSON
|
|
215
|
+
API, so upstream markup changes may cause `ParseError` until the parser is
|
|
216
|
+
updated.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
### Replay statistics
|
|
221
|
+
|
|
222
|
+
`infinitode.stats` provides an independent async client for the public stats
|
|
223
|
+
website. Python 3.10 or later is required.
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
from infinitode.stats import StatsClient, ReplayQuery
|
|
227
|
+
|
|
228
|
+
async def inspect_stats():
|
|
229
|
+
async with StatsClient() as stats:
|
|
230
|
+
query = ReplayQuery(report_count=10, game_modes=("BASIC_LEVELS",))
|
|
231
|
+
browser = await stats.replays(query)
|
|
232
|
+
print(browser.available_filters.maps)
|
|
233
|
+
|
|
234
|
+
aggregate = await stats.summary(query)
|
|
235
|
+
print(aggregate.statistics.general.towers_built)
|
|
236
|
+
|
|
237
|
+
replay = await stats.replay("R-RLX6-YSQL-LGNNCV")
|
|
238
|
+
print(replay.info.mapname, replay.overview.duration)
|
|
239
|
+
print(replay.statistics.towers.tower("GAUSS").damage)
|
|
240
|
+
print(replay.statistics.resources.resource("SCALAR").gained)
|
|
241
|
+
|
|
242
|
+
score = replay.statistics.score_sources
|
|
243
|
+
if score is not None:
|
|
244
|
+
for entry in score.entries:
|
|
245
|
+
print(entry.entity.key, entry.value, entry.share)
|
|
246
|
+
for timeline in score.timelines or ():
|
|
247
|
+
for point in timeline.points:
|
|
248
|
+
print(timeline.entity.key, point.elapsed, point.value)
|
|
249
|
+
|
|
250
|
+
for issue in replay.metadata.issues:
|
|
251
|
+
print(issue.code, issue.section, issue.entity_key, issue.sample_index)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Beta Scores
|
|
255
|
+
|
|
256
|
+
Almost all API calls support an additional `beta` boolean parameter. This will make a request to the beta servers instead. No guarantees for it working.
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
leaderboard_5_1 = await API.leaderboards(mapname=5.1, mode='waves', beta=True)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
### Logging
|
|
264
|
+
|
|
265
|
+
Set up logging to monitor requests and responses.
|
|
266
|
+
|
|
267
|
+
- `INFO`: Logs every request.
|
|
268
|
+
- `DEBUG`: Logs server responses.
|
|
269
|
+
|
|
270
|
+
```python
|
|
271
|
+
import infinitode
|
|
272
|
+
import asyncio
|
|
273
|
+
import logging
|
|
274
|
+
|
|
275
|
+
logging.basicConfig(level=logging.INFO)
|
|
276
|
+
|
|
277
|
+
async def main():
|
|
278
|
+
async with infinitode.Session() as API:
|
|
279
|
+
await API.leaderboards("6.3")
|
|
280
|
+
|
|
281
|
+
asyncio.run(main())
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
### Notes
|
|
287
|
+
|
|
288
|
+
- Some data (e.g., seasonal leaderboard, players) require additional parsing and will take longer to process.
|
|
289
|
+
- Do not abuse this API wrapper for any kind of malicious action.
|