copsapi-wrapper 1.0.0__tar.gz → 1.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {copsapi_wrapper-1.0.0 → copsapi_wrapper-1.1.1}/CHANGELOG.md +11 -0
- {copsapi_wrapper-1.0.0 → copsapi_wrapper-1.1.1}/PKG-INFO +75 -18
- {copsapi_wrapper-1.0.0 → copsapi_wrapper-1.1.1}/README.md +74 -17
- {copsapi_wrapper-1.0.0 → copsapi_wrapper-1.1.1}/pyproject.toml +1 -1
- {copsapi_wrapper-1.0.0 → copsapi_wrapper-1.1.1}/src/copsapi/__init__.py +38 -0
- {copsapi_wrapper-1.0.0 → copsapi_wrapper-1.1.1}/src/copsapi/_async_client.py +632 -630
- {copsapi_wrapper-1.0.0 → copsapi_wrapper-1.1.1}/src/copsapi/_core.py +225 -216
- {copsapi_wrapper-1.0.0 → copsapi_wrapper-1.1.1}/src/copsapi/_sync_client.py +5 -3
- {copsapi_wrapper-1.0.0 → copsapi_wrapper-1.1.1}/src/copsapi/cli.py +242 -242
- copsapi_wrapper-1.1.1/src/copsapi/enums.py +179 -0
- {copsapi_wrapper-1.0.0 → copsapi_wrapper-1.1.1}/src/copsapi/helpers.py +175 -175
- {copsapi_wrapper-1.0.0 → copsapi_wrapper-1.1.1}/src/copsapi/models.py +365 -365
- {copsapi_wrapper-1.0.0 → copsapi_wrapper-1.1.1}/.gitignore +0 -0
- {copsapi_wrapper-1.0.0 → copsapi_wrapper-1.1.1}/LICENSE +0 -0
- {copsapi_wrapper-1.0.0 → copsapi_wrapper-1.1.1}/src/copsapi/__main__.py +0 -0
- {copsapi_wrapper-1.0.0 → copsapi_wrapper-1.1.1}/src/copsapi/_io_async.py +0 -0
- {copsapi_wrapper-1.0.0 → copsapi_wrapper-1.1.1}/src/copsapi/_io_sync.py +0 -0
- {copsapi_wrapper-1.0.0 → copsapi_wrapper-1.1.1}/src/copsapi/cache.py +0 -0
- {copsapi_wrapper-1.0.0 → copsapi_wrapper-1.1.1}/src/copsapi/catalog.py +0 -0
- {copsapi_wrapper-1.0.0 → copsapi_wrapper-1.1.1}/src/copsapi/errors.py +0 -0
- {copsapi_wrapper-1.0.0 → copsapi_wrapper-1.1.1}/src/copsapi/py.typed +0 -0
- {copsapi_wrapper-1.0.0 → copsapi_wrapper-1.1.1}/src/copsapi/quota.py +0 -0
- {copsapi_wrapper-1.0.0 → copsapi_wrapper-1.1.1}/src/copsapi/watcher.py +0 -0
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.1
|
|
4
|
+
|
|
5
|
+
- On Python 3.10, constants such as `GameType.CASUAL` passed to client methods are now sent to the API as their plain values (`2`), not as their names.
|
|
6
|
+
- The README and examples use the named constants throughout.
|
|
7
|
+
|
|
8
|
+
## 1.1.0
|
|
9
|
+
|
|
10
|
+
- Named constants for every code the API uses, such as `BanReason.HACKING`, `UserType.MODERATOR`, `MapId.CASTELLO`, `GameModeId.TOURNAMENT_MODE`, `LeaderboardType.KILLS` and `AutohostCommand.KICK`. Number codes are `IntEnum`s and text codes are `str` enums, so both compare equal to the plain values in the models.
|
|
11
|
+
- `InputDeviceFlag` is an `IntFlag`: `InputDeviceFlag(player.input_device_flags)` decodes the bitmask, and `has_input_device()` checks for one device.
|
|
12
|
+
- Every client method that takes a map, mode, game type, leaderboard type or command also accepts these constants. Nothing else changed, so existing code keeps working.
|
|
13
|
+
|
|
3
14
|
## 1.0.0
|
|
4
15
|
|
|
5
16
|
First release, covering the Critical Ops Public API v2.0.0:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: copsapi-wrapper
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.1
|
|
4
4
|
Summary: Typed, quota-aware Python client for the Critical Ops Public API v2, sync and async: profiles, leaderboards, match history, weapon stats, server status and Autohost with a live room watcher.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Matchmaking-Live/copsapi-wrapper-python
|
|
6
6
|
Project-URL: Repository, https://github.com/Matchmaking-Live/copsapi-wrapper-python
|
|
@@ -80,6 +80,7 @@ It needs Python 3.10 or newer, and its only dependency is [httpx](https://www.py
|
|
|
80
80
|
- [Errors](#errors)
|
|
81
81
|
- [Caching](#caching)
|
|
82
82
|
- [Game catalog](#game-catalog)
|
|
83
|
+
- [Named constants](#named-constants)
|
|
83
84
|
- [Helpers](#helpers)
|
|
84
85
|
- [Command line](#command-line)
|
|
85
86
|
- [Models](#models)
|
|
@@ -94,7 +95,7 @@ What a token can do depends on its permissions:
|
|
|
94
95
|
| Endpoints | Needs |
|
|
95
96
|
| --- | --- |
|
|
96
97
|
| Profiles, servers, match history, weapon stats, leaderboards | Any valid token |
|
|
97
|
-
| Autohost (`create`, `poll`, `control`, `host`, the watcher) | A token with the `Autohost` permission |
|
|
98
|
+
| Autohost (`create`, `poll`, `control`, `host`, the watcher) | A token with the `Autohost` permission (Developer Issued) |
|
|
98
99
|
|
|
99
100
|
There are three ways to pass the token:
|
|
100
101
|
|
|
@@ -128,14 +129,14 @@ If your token lacks `Autohost`, hosting calls raise `PermissionDeniedError` with
|
|
|
128
129
|
## Sync and async
|
|
129
130
|
|
|
130
131
|
```python
|
|
131
|
-
from copsapi import CopsClient, AsyncCopsClient
|
|
132
|
+
from copsapi import CopsClient, AsyncCopsClient, LeaderboardType
|
|
132
133
|
|
|
133
134
|
with CopsClient() as cops:
|
|
134
135
|
servers = cops.servers.online()
|
|
135
136
|
|
|
136
137
|
async with AsyncCopsClient() as cops:
|
|
137
138
|
servers = await cops.servers.online()
|
|
138
|
-
async for entry in cops.leaderboards.iterate(
|
|
139
|
+
async for entry in cops.leaderboards.iterate(LeaderboardType.KILLS, max_pages=3):
|
|
139
140
|
print(entry.position, entry.basic_info.name)
|
|
140
141
|
```
|
|
141
142
|
|
|
@@ -211,9 +212,11 @@ server = cops.servers.pick(region="EU")
|
|
|
211
212
|
## Match history
|
|
212
213
|
|
|
213
214
|
```python
|
|
215
|
+
from copsapi import GameType
|
|
216
|
+
|
|
214
217
|
history = cops.matches.history(user_id)
|
|
215
218
|
match = cops.matches.one(history[0].id)
|
|
216
|
-
recent = cops.matches.recent(user_id, limit=5, game_type=
|
|
219
|
+
recent = cops.matches.recent(user_id, limit=5, game_type=GameType.RANKED)
|
|
217
220
|
```
|
|
218
221
|
|
|
219
222
|
- `history` returns the player's recent matches, newest first. The history resets with each game update.
|
|
@@ -223,26 +226,31 @@ recent = cops.matches.recent(user_id, limit=5, game_type="ranked")
|
|
|
223
226
|
## Weapon stats
|
|
224
227
|
|
|
225
228
|
```python
|
|
226
|
-
|
|
227
|
-
|
|
229
|
+
from copsapi import GameType
|
|
230
|
+
|
|
231
|
+
ranked = cops.stats.weapons(user_id, GameType.RANKED)
|
|
232
|
+
casual = cops.stats.weapons(user_id, GameType.CASUAL)
|
|
233
|
+
best = cops.stats.top_weapons(user_id, game_type=GameType.RANKED, by="k", limit=5)
|
|
228
234
|
```
|
|
229
235
|
|
|
230
|
-
- The game type is `"ranked"`
|
|
236
|
+
- The game type is `GameType.RANKED` or `GameType.CASUAL`; the strings `"ranked"` and `"casual"` work too. Custom games count as casual.
|
|
231
237
|
- `top_weapons` sorts by kills (`k`), assists (`a`) or headshots (`h`). Each `WeaponStats` also has a `.headshot_rate`.
|
|
232
238
|
|
|
233
239
|
## Leaderboards
|
|
234
240
|
|
|
235
241
|
```python
|
|
236
|
-
|
|
237
|
-
elite = cops.leaderboards.page("mmr", rank=9)
|
|
242
|
+
from copsapi import LeaderboardType
|
|
238
243
|
|
|
239
|
-
|
|
244
|
+
page = cops.leaderboards.page(LeaderboardType.MMR, page=1)
|
|
245
|
+
elite = cops.leaderboards.page(LeaderboardType.MMR, rank=9)
|
|
246
|
+
|
|
247
|
+
for entry in cops.leaderboards.iterate(LeaderboardType.KILLS, max_pages=10):
|
|
240
248
|
print(entry.position, entry.basic_info.name, entry.score)
|
|
241
249
|
|
|
242
|
-
top50 = cops.leaderboards.top(
|
|
250
|
+
top50 = cops.leaderboards.top(LeaderboardType.WINS, 50)
|
|
243
251
|
```
|
|
244
252
|
|
|
245
|
-
- The types are `
|
|
253
|
+
- The types are `LeaderboardType.MMR`, `KILLS`, `DEATHS`, `ASSISTS`, `MATCHES`, `WINS`, `LOSSES`, `XP` and `MVP`; the plain strings (`"mmr"`, `"kills"` and so on) work too. Pages hold 10 entries.
|
|
246
254
|
- `page` returns a `LeaderboardPage(type, page, rank, size, pages, entries)`. `rank` (0 to 9) filters to one rank.
|
|
247
255
|
- Players move as matches end, so `iterate` skips anyone already yielded on an earlier page.
|
|
248
256
|
|
|
@@ -253,25 +261,27 @@ This needs a token with the `Autohost` permission. To check before offering host
|
|
|
253
261
|
### One call: host, wait for the code, add owners
|
|
254
262
|
|
|
255
263
|
```python
|
|
264
|
+
from copsapi import GameModeId, MapId
|
|
265
|
+
|
|
256
266
|
server = cops.servers.pick(region="EU")
|
|
257
267
|
room = cops.autohost.host(
|
|
258
268
|
server_id=server.id,
|
|
259
|
-
mode=
|
|
260
|
-
map=
|
|
269
|
+
mode=GameModeId.TOURNAMENT_MODE,
|
|
270
|
+
map=MapId.CASTELLO,
|
|
261
271
|
room_name="Scrim #42",
|
|
262
272
|
owners=[captain_user_id],
|
|
263
273
|
)
|
|
264
274
|
print(f"Join with code {room.code}, password {room.password}")
|
|
265
275
|
```
|
|
266
276
|
|
|
267
|
-
- `mode` and `map`
|
|
277
|
+
- `mode` and `map` take a `GameModeId` and a `MapId`. Names (`"Tournament Mode"`, `"castello"`) and raw ids work too. Combinations the API doesn't allow raise `BadRequestError` before the request is sent; pass `validate=False` if Critical Force added a map after this release.
|
|
268
278
|
- A random 6-character password is generated unless you pass `password="..."`. Pass `password=False` for an open room.
|
|
269
279
|
- `host` raises `GameRejectedError` if the game server refuses the game (it's full or in maintenance, or the token already hosts 10 games there). It raises `GameGoneError`, or `ConnectionFailedError` with code `timeout`, if the room never appears.
|
|
270
280
|
|
|
271
281
|
### Step by step
|
|
272
282
|
|
|
273
283
|
```python
|
|
274
|
-
game = cops.autohost.create(server_id=6, mode=
|
|
284
|
+
game = cops.autohost.create(server_id=6, mode=GameModeId.DEFUSE, map=MapId.RAID, room_name="Final", password="abc")
|
|
275
285
|
ready = cops.autohost.wait_for_room(game.id)
|
|
276
286
|
games = cops.autohost.poll([game.id, other_id])
|
|
277
287
|
one = cops.autohost.get(game.id)
|
|
@@ -284,12 +294,14 @@ one = cops.autohost.get(game.id)
|
|
|
284
294
|
### Room commands
|
|
285
295
|
|
|
286
296
|
```python
|
|
297
|
+
from copsapi import AutohostCommand
|
|
298
|
+
|
|
287
299
|
cops.autohost.kick(game_id, user_id)
|
|
288
300
|
cops.autohost.add_owner(game_id, user_id)
|
|
289
301
|
cops.autohost.remove_owner(game_id, user_id)
|
|
290
302
|
cops.autohost.restart(game_id)
|
|
291
303
|
cops.autohost.close(game_id)
|
|
292
|
-
cops.autohost.control(game_id,
|
|
304
|
+
cops.autohost.control(game_id, AutohostCommand.KICK, user_id)
|
|
293
305
|
```
|
|
294
306
|
|
|
295
307
|
- `kick` removes the player and keeps them out for an hour.
|
|
@@ -507,6 +519,51 @@ The package also exports these label tables:
|
|
|
507
519
|
- `BAN_REASONS`, `USER_TYPES`, `ACCOUNT_AGES`, `PARTY_INVITE_PERMISSIONS` and `CLAN_RANKS`, which map API codes to readable text.
|
|
508
520
|
- `LEADERBOARD_TYPES`, `MATCH_STATES`, `GAME_TYPES`, `INPUT_DEVICES` and `AUTOHOST_COMMANDS`.
|
|
509
521
|
|
|
522
|
+
## Named constants
|
|
523
|
+
|
|
524
|
+
Every code the API uses has a named constant, so you don't have to remember what `2` or `15847` means:
|
|
525
|
+
|
|
526
|
+
```python
|
|
527
|
+
from copsapi import BanReason, UserType, MapId, GameModeId, LeaderboardType, AutohostCommand, InputDeviceFlag
|
|
528
|
+
|
|
529
|
+
if profile.ban and profile.ban.reason == BanReason.HACKING:
|
|
530
|
+
flag_for_review(profile)
|
|
531
|
+
if player.user_type == UserType.MODERATOR:
|
|
532
|
+
print("a moderator is in the room")
|
|
533
|
+
|
|
534
|
+
cops.autohost.create(server_id=server_id, map=MapId.CASTELLO, mode=GameModeId.TOURNAMENT_MODE, room_name="Final")
|
|
535
|
+
cops.autohost.control(game_id, AutohostCommand.KICK, user_id)
|
|
536
|
+
page = cops.leaderboards.page(LeaderboardType.KILLS)
|
|
537
|
+
|
|
538
|
+
devices = InputDeviceFlag(player.input_device_flags)
|
|
539
|
+
if InputDeviceFlag.TOUCH in devices:
|
|
540
|
+
print("plays on a touch screen")
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
| Constant | Kind | Members |
|
|
544
|
+
| --- | --- | --- |
|
|
545
|
+
| `UserType` | `IntEnum` | `SPECTATOR`, `USER`, `ASSISTANT_MOD`, `MODERATOR`, `VETERAN_MOD`, `DEVELOPER` |
|
|
546
|
+
| `BanType` | `IntEnum` | `PERMANENT`, `TEMPORARY` |
|
|
547
|
+
| `BanReason` | `IntEnum` | `UNKNOWN`, `FRAUD_ATTEMPT`, `HACKING`, `GRIEFING`, `VERBAL_ABUSE`, `IDLE`, `SPAM`, `REPORT_ABUSE`, `ACCOUNT_TRADING`, `GLITCHING`, `GHOSTING`, `PARTYING_WITH_HACKER`, `UNDER_INVESTIGATION`, `MARKETPLACE_ABUSE` |
|
|
548
|
+
| `ClanMemberRank` | `IntEnum` | `MEMBER`, `OFFICER`, `CO_LEADER`, `LEADER` |
|
|
549
|
+
| `PartyInvitePermission` | `IntEnum` | `ANYBODY`, `ONLY_FRIENDS_AND_CLAN_MEMBERS`, `NOBODY` |
|
|
550
|
+
| `GameType` | `IntEnum` | `RANKED`, `CASUAL` |
|
|
551
|
+
| `MatchResult` | `IntEnum` | `WIN`, `LOSS`, `DRAW` |
|
|
552
|
+
| `InputDeviceFlag` | `IntFlag` | `KEYBOARD_MOUSE`, `TOUCH`, `GAMEPAD` |
|
|
553
|
+
| `MapId` | `IntEnum` | Every map, such as `CANALS` or `CASTELLO` |
|
|
554
|
+
| `GameModeId` | `IntEnum` | Every hostable mode, such as `DEFUSE`, `TEAM_DEATHMATCH` or `TOURNAMENT_MODE` |
|
|
555
|
+
| `AccountAge` | `str` enum | `UNDER_A_WEEK` (`"1w"`) to `FOUR_YEARS_OR_MORE` (`"5y+"`) |
|
|
556
|
+
| `WinCondition` | `str` enum | `TEAM_ELIMINATED`, `TEAM_KILLS`, `TIME_UP`, `SURRENDERED` |
|
|
557
|
+
| `LeaveReason` | `str` enum | `LEFT`, `TIMEOUT`, `KICKED`, `BANNED`, `PING_LIMIT`, `AFK` |
|
|
558
|
+
| `MatchState` | `str` enum | `WAITING_FOR_PLAYERS`, `STARTING`, `FREEZETIME`, `ON_GOING`, `BOMB_PLANTED`, `WIN`, `TEAM_FLIP`, `GAME_END`, `CHANGING_MAP` |
|
|
559
|
+
| `AutohostCommand` | `str` enum | `KICK`, `RESTART`, `ROOM_OWNER_ADD`, `ROOM_OWNER_REMOVE`, `CLOSE` |
|
|
560
|
+
| `LeaderboardType` | `str` enum | `MMR`, `KILLS`, `DEATHS`, `ASSISTS`, `MATCHES`, `WINS`, `LOSSES`, `XP`, `MVP` |
|
|
561
|
+
| `GameStatus` | `str` enum | `OPEN`, `ENDED` |
|
|
562
|
+
|
|
563
|
+
The models keep plain `int` and `str` values, and every constant compares equal to them: `BanReason.HACKING == 2` and `LeaveReason.AFK == "AFK"` are both true. Every client method that takes a map, mode, game type, leaderboard type or command also accepts the constants.
|
|
564
|
+
|
|
565
|
+
Some values can grow as Critical Force updates the game: maps, game modes, win conditions, leave reasons and match states. The fields stay plain values, so a new map or reason never raises an error while parsing a response. If you convert a value yourself, for example `MapId(game.room.map_id)`, handle the `ValueError` you get for a value added after this release.
|
|
566
|
+
|
|
510
567
|
## Helpers
|
|
511
568
|
|
|
512
569
|
| Helper | Returns |
|
|
@@ -45,6 +45,7 @@ It needs Python 3.10 or newer, and its only dependency is [httpx](https://www.py
|
|
|
45
45
|
- [Errors](#errors)
|
|
46
46
|
- [Caching](#caching)
|
|
47
47
|
- [Game catalog](#game-catalog)
|
|
48
|
+
- [Named constants](#named-constants)
|
|
48
49
|
- [Helpers](#helpers)
|
|
49
50
|
- [Command line](#command-line)
|
|
50
51
|
- [Models](#models)
|
|
@@ -59,7 +60,7 @@ What a token can do depends on its permissions:
|
|
|
59
60
|
| Endpoints | Needs |
|
|
60
61
|
| --- | --- |
|
|
61
62
|
| Profiles, servers, match history, weapon stats, leaderboards | Any valid token |
|
|
62
|
-
| Autohost (`create`, `poll`, `control`, `host`, the watcher) | A token with the `Autohost` permission |
|
|
63
|
+
| Autohost (`create`, `poll`, `control`, `host`, the watcher) | A token with the `Autohost` permission (Developer Issued) |
|
|
63
64
|
|
|
64
65
|
There are three ways to pass the token:
|
|
65
66
|
|
|
@@ -93,14 +94,14 @@ If your token lacks `Autohost`, hosting calls raise `PermissionDeniedError` with
|
|
|
93
94
|
## Sync and async
|
|
94
95
|
|
|
95
96
|
```python
|
|
96
|
-
from copsapi import CopsClient, AsyncCopsClient
|
|
97
|
+
from copsapi import CopsClient, AsyncCopsClient, LeaderboardType
|
|
97
98
|
|
|
98
99
|
with CopsClient() as cops:
|
|
99
100
|
servers = cops.servers.online()
|
|
100
101
|
|
|
101
102
|
async with AsyncCopsClient() as cops:
|
|
102
103
|
servers = await cops.servers.online()
|
|
103
|
-
async for entry in cops.leaderboards.iterate(
|
|
104
|
+
async for entry in cops.leaderboards.iterate(LeaderboardType.KILLS, max_pages=3):
|
|
104
105
|
print(entry.position, entry.basic_info.name)
|
|
105
106
|
```
|
|
106
107
|
|
|
@@ -176,9 +177,11 @@ server = cops.servers.pick(region="EU")
|
|
|
176
177
|
## Match history
|
|
177
178
|
|
|
178
179
|
```python
|
|
180
|
+
from copsapi import GameType
|
|
181
|
+
|
|
179
182
|
history = cops.matches.history(user_id)
|
|
180
183
|
match = cops.matches.one(history[0].id)
|
|
181
|
-
recent = cops.matches.recent(user_id, limit=5, game_type=
|
|
184
|
+
recent = cops.matches.recent(user_id, limit=5, game_type=GameType.RANKED)
|
|
182
185
|
```
|
|
183
186
|
|
|
184
187
|
- `history` returns the player's recent matches, newest first. The history resets with each game update.
|
|
@@ -188,26 +191,31 @@ recent = cops.matches.recent(user_id, limit=5, game_type="ranked")
|
|
|
188
191
|
## Weapon stats
|
|
189
192
|
|
|
190
193
|
```python
|
|
191
|
-
|
|
192
|
-
|
|
194
|
+
from copsapi import GameType
|
|
195
|
+
|
|
196
|
+
ranked = cops.stats.weapons(user_id, GameType.RANKED)
|
|
197
|
+
casual = cops.stats.weapons(user_id, GameType.CASUAL)
|
|
198
|
+
best = cops.stats.top_weapons(user_id, game_type=GameType.RANKED, by="k", limit=5)
|
|
193
199
|
```
|
|
194
200
|
|
|
195
|
-
- The game type is `"ranked"`
|
|
201
|
+
- The game type is `GameType.RANKED` or `GameType.CASUAL`; the strings `"ranked"` and `"casual"` work too. Custom games count as casual.
|
|
196
202
|
- `top_weapons` sorts by kills (`k`), assists (`a`) or headshots (`h`). Each `WeaponStats` also has a `.headshot_rate`.
|
|
197
203
|
|
|
198
204
|
## Leaderboards
|
|
199
205
|
|
|
200
206
|
```python
|
|
201
|
-
|
|
202
|
-
elite = cops.leaderboards.page("mmr", rank=9)
|
|
207
|
+
from copsapi import LeaderboardType
|
|
203
208
|
|
|
204
|
-
|
|
209
|
+
page = cops.leaderboards.page(LeaderboardType.MMR, page=1)
|
|
210
|
+
elite = cops.leaderboards.page(LeaderboardType.MMR, rank=9)
|
|
211
|
+
|
|
212
|
+
for entry in cops.leaderboards.iterate(LeaderboardType.KILLS, max_pages=10):
|
|
205
213
|
print(entry.position, entry.basic_info.name, entry.score)
|
|
206
214
|
|
|
207
|
-
top50 = cops.leaderboards.top(
|
|
215
|
+
top50 = cops.leaderboards.top(LeaderboardType.WINS, 50)
|
|
208
216
|
```
|
|
209
217
|
|
|
210
|
-
- The types are `
|
|
218
|
+
- The types are `LeaderboardType.MMR`, `KILLS`, `DEATHS`, `ASSISTS`, `MATCHES`, `WINS`, `LOSSES`, `XP` and `MVP`; the plain strings (`"mmr"`, `"kills"` and so on) work too. Pages hold 10 entries.
|
|
211
219
|
- `page` returns a `LeaderboardPage(type, page, rank, size, pages, entries)`. `rank` (0 to 9) filters to one rank.
|
|
212
220
|
- Players move as matches end, so `iterate` skips anyone already yielded on an earlier page.
|
|
213
221
|
|
|
@@ -218,25 +226,27 @@ This needs a token with the `Autohost` permission. To check before offering host
|
|
|
218
226
|
### One call: host, wait for the code, add owners
|
|
219
227
|
|
|
220
228
|
```python
|
|
229
|
+
from copsapi import GameModeId, MapId
|
|
230
|
+
|
|
221
231
|
server = cops.servers.pick(region="EU")
|
|
222
232
|
room = cops.autohost.host(
|
|
223
233
|
server_id=server.id,
|
|
224
|
-
mode=
|
|
225
|
-
map=
|
|
234
|
+
mode=GameModeId.TOURNAMENT_MODE,
|
|
235
|
+
map=MapId.CASTELLO,
|
|
226
236
|
room_name="Scrim #42",
|
|
227
237
|
owners=[captain_user_id],
|
|
228
238
|
)
|
|
229
239
|
print(f"Join with code {room.code}, password {room.password}")
|
|
230
240
|
```
|
|
231
241
|
|
|
232
|
-
- `mode` and `map`
|
|
242
|
+
- `mode` and `map` take a `GameModeId` and a `MapId`. Names (`"Tournament Mode"`, `"castello"`) and raw ids work too. Combinations the API doesn't allow raise `BadRequestError` before the request is sent; pass `validate=False` if Critical Force added a map after this release.
|
|
233
243
|
- A random 6-character password is generated unless you pass `password="..."`. Pass `password=False` for an open room.
|
|
234
244
|
- `host` raises `GameRejectedError` if the game server refuses the game (it's full or in maintenance, or the token already hosts 10 games there). It raises `GameGoneError`, or `ConnectionFailedError` with code `timeout`, if the room never appears.
|
|
235
245
|
|
|
236
246
|
### Step by step
|
|
237
247
|
|
|
238
248
|
```python
|
|
239
|
-
game = cops.autohost.create(server_id=6, mode=
|
|
249
|
+
game = cops.autohost.create(server_id=6, mode=GameModeId.DEFUSE, map=MapId.RAID, room_name="Final", password="abc")
|
|
240
250
|
ready = cops.autohost.wait_for_room(game.id)
|
|
241
251
|
games = cops.autohost.poll([game.id, other_id])
|
|
242
252
|
one = cops.autohost.get(game.id)
|
|
@@ -249,12 +259,14 @@ one = cops.autohost.get(game.id)
|
|
|
249
259
|
### Room commands
|
|
250
260
|
|
|
251
261
|
```python
|
|
262
|
+
from copsapi import AutohostCommand
|
|
263
|
+
|
|
252
264
|
cops.autohost.kick(game_id, user_id)
|
|
253
265
|
cops.autohost.add_owner(game_id, user_id)
|
|
254
266
|
cops.autohost.remove_owner(game_id, user_id)
|
|
255
267
|
cops.autohost.restart(game_id)
|
|
256
268
|
cops.autohost.close(game_id)
|
|
257
|
-
cops.autohost.control(game_id,
|
|
269
|
+
cops.autohost.control(game_id, AutohostCommand.KICK, user_id)
|
|
258
270
|
```
|
|
259
271
|
|
|
260
272
|
- `kick` removes the player and keeps them out for an hour.
|
|
@@ -472,6 +484,51 @@ The package also exports these label tables:
|
|
|
472
484
|
- `BAN_REASONS`, `USER_TYPES`, `ACCOUNT_AGES`, `PARTY_INVITE_PERMISSIONS` and `CLAN_RANKS`, which map API codes to readable text.
|
|
473
485
|
- `LEADERBOARD_TYPES`, `MATCH_STATES`, `GAME_TYPES`, `INPUT_DEVICES` and `AUTOHOST_COMMANDS`.
|
|
474
486
|
|
|
487
|
+
## Named constants
|
|
488
|
+
|
|
489
|
+
Every code the API uses has a named constant, so you don't have to remember what `2` or `15847` means:
|
|
490
|
+
|
|
491
|
+
```python
|
|
492
|
+
from copsapi import BanReason, UserType, MapId, GameModeId, LeaderboardType, AutohostCommand, InputDeviceFlag
|
|
493
|
+
|
|
494
|
+
if profile.ban and profile.ban.reason == BanReason.HACKING:
|
|
495
|
+
flag_for_review(profile)
|
|
496
|
+
if player.user_type == UserType.MODERATOR:
|
|
497
|
+
print("a moderator is in the room")
|
|
498
|
+
|
|
499
|
+
cops.autohost.create(server_id=server_id, map=MapId.CASTELLO, mode=GameModeId.TOURNAMENT_MODE, room_name="Final")
|
|
500
|
+
cops.autohost.control(game_id, AutohostCommand.KICK, user_id)
|
|
501
|
+
page = cops.leaderboards.page(LeaderboardType.KILLS)
|
|
502
|
+
|
|
503
|
+
devices = InputDeviceFlag(player.input_device_flags)
|
|
504
|
+
if InputDeviceFlag.TOUCH in devices:
|
|
505
|
+
print("plays on a touch screen")
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
| Constant | Kind | Members |
|
|
509
|
+
| --- | --- | --- |
|
|
510
|
+
| `UserType` | `IntEnum` | `SPECTATOR`, `USER`, `ASSISTANT_MOD`, `MODERATOR`, `VETERAN_MOD`, `DEVELOPER` |
|
|
511
|
+
| `BanType` | `IntEnum` | `PERMANENT`, `TEMPORARY` |
|
|
512
|
+
| `BanReason` | `IntEnum` | `UNKNOWN`, `FRAUD_ATTEMPT`, `HACKING`, `GRIEFING`, `VERBAL_ABUSE`, `IDLE`, `SPAM`, `REPORT_ABUSE`, `ACCOUNT_TRADING`, `GLITCHING`, `GHOSTING`, `PARTYING_WITH_HACKER`, `UNDER_INVESTIGATION`, `MARKETPLACE_ABUSE` |
|
|
513
|
+
| `ClanMemberRank` | `IntEnum` | `MEMBER`, `OFFICER`, `CO_LEADER`, `LEADER` |
|
|
514
|
+
| `PartyInvitePermission` | `IntEnum` | `ANYBODY`, `ONLY_FRIENDS_AND_CLAN_MEMBERS`, `NOBODY` |
|
|
515
|
+
| `GameType` | `IntEnum` | `RANKED`, `CASUAL` |
|
|
516
|
+
| `MatchResult` | `IntEnum` | `WIN`, `LOSS`, `DRAW` |
|
|
517
|
+
| `InputDeviceFlag` | `IntFlag` | `KEYBOARD_MOUSE`, `TOUCH`, `GAMEPAD` |
|
|
518
|
+
| `MapId` | `IntEnum` | Every map, such as `CANALS` or `CASTELLO` |
|
|
519
|
+
| `GameModeId` | `IntEnum` | Every hostable mode, such as `DEFUSE`, `TEAM_DEATHMATCH` or `TOURNAMENT_MODE` |
|
|
520
|
+
| `AccountAge` | `str` enum | `UNDER_A_WEEK` (`"1w"`) to `FOUR_YEARS_OR_MORE` (`"5y+"`) |
|
|
521
|
+
| `WinCondition` | `str` enum | `TEAM_ELIMINATED`, `TEAM_KILLS`, `TIME_UP`, `SURRENDERED` |
|
|
522
|
+
| `LeaveReason` | `str` enum | `LEFT`, `TIMEOUT`, `KICKED`, `BANNED`, `PING_LIMIT`, `AFK` |
|
|
523
|
+
| `MatchState` | `str` enum | `WAITING_FOR_PLAYERS`, `STARTING`, `FREEZETIME`, `ON_GOING`, `BOMB_PLANTED`, `WIN`, `TEAM_FLIP`, `GAME_END`, `CHANGING_MAP` |
|
|
524
|
+
| `AutohostCommand` | `str` enum | `KICK`, `RESTART`, `ROOM_OWNER_ADD`, `ROOM_OWNER_REMOVE`, `CLOSE` |
|
|
525
|
+
| `LeaderboardType` | `str` enum | `MMR`, `KILLS`, `DEATHS`, `ASSISTS`, `MATCHES`, `WINS`, `LOSSES`, `XP`, `MVP` |
|
|
526
|
+
| `GameStatus` | `str` enum | `OPEN`, `ENDED` |
|
|
527
|
+
|
|
528
|
+
The models keep plain `int` and `str` values, and every constant compares equal to them: `BanReason.HACKING == 2` and `LeaveReason.AFK == "AFK"` are both true. Every client method that takes a map, mode, game type, leaderboard type or command also accepts the constants.
|
|
529
|
+
|
|
530
|
+
Some values can grow as Critical Force updates the game: maps, game modes, win conditions, leave reasons and match states. The fields stay plain values, so a new map or reason never raises an error while parsing a response. If you convert a value yourself, for example `MapId(game.room.map_id)`, handle the `ValueError` you get for a value added after this release.
|
|
531
|
+
|
|
475
532
|
## Helpers
|
|
476
533
|
|
|
477
534
|
| Helper | Returns |
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "copsapi-wrapper"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.1.1"
|
|
8
8
|
description = "Typed, quota-aware Python client for the Critical Ops Public API v2, sync and async: profiles, leaderboards, match history, weapon stats, server status and Autohost with a live room watcher."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -39,6 +39,26 @@ from .catalog import (
|
|
|
39
39
|
maps_for,
|
|
40
40
|
modes_for,
|
|
41
41
|
)
|
|
42
|
+
from .enums import (
|
|
43
|
+
AccountAge,
|
|
44
|
+
AutohostCommand,
|
|
45
|
+
BanReason,
|
|
46
|
+
BanType,
|
|
47
|
+
ClanMemberRank,
|
|
48
|
+
GameModeId,
|
|
49
|
+
GameStatus,
|
|
50
|
+
GameType,
|
|
51
|
+
InputDeviceFlag,
|
|
52
|
+
LeaderboardType,
|
|
53
|
+
LeaveReason,
|
|
54
|
+
MapId,
|
|
55
|
+
MatchResult,
|
|
56
|
+
MatchState,
|
|
57
|
+
PartyInvitePermission,
|
|
58
|
+
UserType,
|
|
59
|
+
WinCondition,
|
|
60
|
+
has_input_device,
|
|
61
|
+
)
|
|
42
62
|
from .errors import (
|
|
43
63
|
AuthenticationError,
|
|
44
64
|
BadRequestError,
|
|
@@ -103,6 +123,24 @@ from .watcher import EVENTS, AsyncAutohostWatcher, AutohostWatcher, JoinInfo
|
|
|
103
123
|
__version__ = VERSION
|
|
104
124
|
|
|
105
125
|
__all__ = [
|
|
126
|
+
"AccountAge",
|
|
127
|
+
"AutohostCommand",
|
|
128
|
+
"BanReason",
|
|
129
|
+
"BanType",
|
|
130
|
+
"ClanMemberRank",
|
|
131
|
+
"GameModeId",
|
|
132
|
+
"GameStatus",
|
|
133
|
+
"GameType",
|
|
134
|
+
"InputDeviceFlag",
|
|
135
|
+
"LeaderboardType",
|
|
136
|
+
"LeaveReason",
|
|
137
|
+
"MapId",
|
|
138
|
+
"MatchResult",
|
|
139
|
+
"MatchState",
|
|
140
|
+
"PartyInvitePermission",
|
|
141
|
+
"UserType",
|
|
142
|
+
"WinCondition",
|
|
143
|
+
"has_input_device",
|
|
106
144
|
"ACCOUNT_AGES",
|
|
107
145
|
"AUTOHOST_COMMANDS",
|
|
108
146
|
"AsyncAutohostApi",
|