wok-api 1.2.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.
wok_api-1.2.0/PKG-INFO ADDED
@@ -0,0 +1,99 @@
1
+ Metadata-Version: 2.4
2
+ Name: wok-api
3
+ Version: 1.2.0
4
+ Summary: Dependency-free synchronous client for the WOK Steam API
5
+ Author: WOK API
6
+ License-Expression: LicenseRef-Proprietary
7
+ Project-URL: Homepage, https://woksteamapi.com
8
+ Project-URL: Documentation, https://woksteamapi.com/docs
9
+ Keywords: wok-api,steam,steam-api,steam-inventory-api,steamwebapi,cs2,inventory,faceit
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+
22
+ # WOK API client for Python
23
+
24
+ Dependency-free synchronous client for the
25
+ [WOK Steam API](https://woksteamapi.com/docs). It is distributed as the single
26
+ `wok_api` module and uses only the Python standard library at runtime.
27
+
28
+ ## Requirements
29
+
30
+ - Python 3.10 or newer
31
+ - A WOK API key
32
+
33
+ For development, the Free Developer plan provides 1,000 requests per calendar
34
+ month, a 100-request daily guard and 30 requests per minute. Create a key at
35
+ <https://woksteamapi.com/pricing>.
36
+
37
+ Keep the API key in a server-side environment variable or secret store. Do not
38
+ commit it to source control or send it to browser clients.
39
+
40
+ ## Install
41
+
42
+ ```sh
43
+ python -m pip install wok-api
44
+ ```
45
+
46
+ ## Usage
47
+
48
+ ```python
49
+ import os
50
+
51
+ from wok_api import WokApi, WokApiError
52
+
53
+ client = WokApi(os.environ["WOK_API_KEY"])
54
+ steam_id = os.environ["STEAM_ID"]
55
+
56
+ try:
57
+ inventory = client.inventory(
58
+ steam_id,
59
+ game="cs2",
60
+ top=25,
61
+ )
62
+ print(inventory)
63
+ except WokApiError as error:
64
+ print(error.status, error, error.retry_after)
65
+ ```
66
+
67
+ The client also exposes methods for profiles, friends, catalog prices, price
68
+ history, CS2 inspection, crawl jobs, FACEIT data, and service health. See the
69
+ [API reference](https://woksteamapi.com/docs) for request and response shapes.
70
+
71
+ Render a modern CS2 inspect certificate as PNG bytes:
72
+
73
+ ```python
74
+ png = client.cs2_screenshot(
75
+ certificate=os.environ["CS2_INSPECT_CERTIFICATE"],
76
+ theme="wok",
77
+ item_name="AK-47 | Redline (Field-Tested)",
78
+ )
79
+ with open("wok-cs2-inspect.png", "wb") as output:
80
+ output.write(png)
81
+ ```
82
+
83
+ `WokApiError` provides `status`, `payload`, `retry_after`, `rate_limit`,
84
+ `rate_remaining`, and `rate_reset`. Wait at least `retry_after` seconds after
85
+ HTTP 429. Network failures use status `0`.
86
+
87
+ The default timeout is 35 seconds so the 25-second synchronous inventory batch
88
+ can finish. Do not configure `/v1/inventories` with a timeout below 30 seconds.
89
+
90
+ For testing or a compatible deployment, pass `base_url`, `timeout`, or a custom
91
+ `transport` to the constructor.
92
+
93
+ ## Build the package
94
+
95
+ ```sh
96
+ python -m build
97
+ ```
98
+
99
+ The command creates both an sdist and a wheel under `dist/`.
@@ -0,0 +1,78 @@
1
+ # WOK API client for Python
2
+
3
+ Dependency-free synchronous client for the
4
+ [WOK Steam API](https://woksteamapi.com/docs). It is distributed as the single
5
+ `wok_api` module and uses only the Python standard library at runtime.
6
+
7
+ ## Requirements
8
+
9
+ - Python 3.10 or newer
10
+ - A WOK API key
11
+
12
+ For development, the Free Developer plan provides 1,000 requests per calendar
13
+ month, a 100-request daily guard and 30 requests per minute. Create a key at
14
+ <https://woksteamapi.com/pricing>.
15
+
16
+ Keep the API key in a server-side environment variable or secret store. Do not
17
+ commit it to source control or send it to browser clients.
18
+
19
+ ## Install
20
+
21
+ ```sh
22
+ python -m pip install wok-api
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ```python
28
+ import os
29
+
30
+ from wok_api import WokApi, WokApiError
31
+
32
+ client = WokApi(os.environ["WOK_API_KEY"])
33
+ steam_id = os.environ["STEAM_ID"]
34
+
35
+ try:
36
+ inventory = client.inventory(
37
+ steam_id,
38
+ game="cs2",
39
+ top=25,
40
+ )
41
+ print(inventory)
42
+ except WokApiError as error:
43
+ print(error.status, error, error.retry_after)
44
+ ```
45
+
46
+ The client also exposes methods for profiles, friends, catalog prices, price
47
+ history, CS2 inspection, crawl jobs, FACEIT data, and service health. See the
48
+ [API reference](https://woksteamapi.com/docs) for request and response shapes.
49
+
50
+ Render a modern CS2 inspect certificate as PNG bytes:
51
+
52
+ ```python
53
+ png = client.cs2_screenshot(
54
+ certificate=os.environ["CS2_INSPECT_CERTIFICATE"],
55
+ theme="wok",
56
+ item_name="AK-47 | Redline (Field-Tested)",
57
+ )
58
+ with open("wok-cs2-inspect.png", "wb") as output:
59
+ output.write(png)
60
+ ```
61
+
62
+ `WokApiError` provides `status`, `payload`, `retry_after`, `rate_limit`,
63
+ `rate_remaining`, and `rate_reset`. Wait at least `retry_after` seconds after
64
+ HTTP 429. Network failures use status `0`.
65
+
66
+ The default timeout is 35 seconds so the 25-second synchronous inventory batch
67
+ can finish. Do not configure `/v1/inventories` with a timeout below 30 seconds.
68
+
69
+ For testing or a compatible deployment, pass `base_url`, `timeout`, or a custom
70
+ `transport` to the constructor.
71
+
72
+ ## Build the package
73
+
74
+ ```sh
75
+ python -m build
76
+ ```
77
+
78
+ The command creates both an sdist and a wheel under `dist/`.
@@ -0,0 +1,44 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "wok-api"
7
+ version = "1.2.0"
8
+ description = "Dependency-free synchronous client for the WOK Steam API"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "LicenseRef-Proprietary"
12
+ authors = [
13
+ { name = "WOK API" },
14
+ ]
15
+ keywords = [
16
+ "wok-api",
17
+ "steam",
18
+ "steam-api",
19
+ "steam-inventory-api",
20
+ "steamwebapi",
21
+ "cs2",
22
+ "inventory",
23
+ "faceit",
24
+ ]
25
+ classifiers = [
26
+ "Development Status :: 4 - Beta",
27
+ "Intended Audience :: Developers",
28
+ "Operating System :: OS Independent",
29
+ "Programming Language :: Python :: 3",
30
+ "Programming Language :: Python :: 3 :: Only",
31
+ "Programming Language :: Python :: 3.10",
32
+ "Programming Language :: Python :: 3.11",
33
+ "Programming Language :: Python :: 3.12",
34
+ "Programming Language :: Python :: 3.13",
35
+ ]
36
+ dependencies = []
37
+
38
+ [project.urls]
39
+ Homepage = "https://woksteamapi.com"
40
+ Documentation = "https://woksteamapi.com/docs"
41
+
42
+ [tool.setuptools]
43
+ py-modules = ["wok_api"]
44
+ include-package-data = false
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,99 @@
1
+ Metadata-Version: 2.4
2
+ Name: wok-api
3
+ Version: 1.2.0
4
+ Summary: Dependency-free synchronous client for the WOK Steam API
5
+ Author: WOK API
6
+ License-Expression: LicenseRef-Proprietary
7
+ Project-URL: Homepage, https://woksteamapi.com
8
+ Project-URL: Documentation, https://woksteamapi.com/docs
9
+ Keywords: wok-api,steam,steam-api,steam-inventory-api,steamwebapi,cs2,inventory,faceit
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+
22
+ # WOK API client for Python
23
+
24
+ Dependency-free synchronous client for the
25
+ [WOK Steam API](https://woksteamapi.com/docs). It is distributed as the single
26
+ `wok_api` module and uses only the Python standard library at runtime.
27
+
28
+ ## Requirements
29
+
30
+ - Python 3.10 or newer
31
+ - A WOK API key
32
+
33
+ For development, the Free Developer plan provides 1,000 requests per calendar
34
+ month, a 100-request daily guard and 30 requests per minute. Create a key at
35
+ <https://woksteamapi.com/pricing>.
36
+
37
+ Keep the API key in a server-side environment variable or secret store. Do not
38
+ commit it to source control or send it to browser clients.
39
+
40
+ ## Install
41
+
42
+ ```sh
43
+ python -m pip install wok-api
44
+ ```
45
+
46
+ ## Usage
47
+
48
+ ```python
49
+ import os
50
+
51
+ from wok_api import WokApi, WokApiError
52
+
53
+ client = WokApi(os.environ["WOK_API_KEY"])
54
+ steam_id = os.environ["STEAM_ID"]
55
+
56
+ try:
57
+ inventory = client.inventory(
58
+ steam_id,
59
+ game="cs2",
60
+ top=25,
61
+ )
62
+ print(inventory)
63
+ except WokApiError as error:
64
+ print(error.status, error, error.retry_after)
65
+ ```
66
+
67
+ The client also exposes methods for profiles, friends, catalog prices, price
68
+ history, CS2 inspection, crawl jobs, FACEIT data, and service health. See the
69
+ [API reference](https://woksteamapi.com/docs) for request and response shapes.
70
+
71
+ Render a modern CS2 inspect certificate as PNG bytes:
72
+
73
+ ```python
74
+ png = client.cs2_screenshot(
75
+ certificate=os.environ["CS2_INSPECT_CERTIFICATE"],
76
+ theme="wok",
77
+ item_name="AK-47 | Redline (Field-Tested)",
78
+ )
79
+ with open("wok-cs2-inspect.png", "wb") as output:
80
+ output.write(png)
81
+ ```
82
+
83
+ `WokApiError` provides `status`, `payload`, `retry_after`, `rate_limit`,
84
+ `rate_remaining`, and `rate_reset`. Wait at least `retry_after` seconds after
85
+ HTTP 429. Network failures use status `0`.
86
+
87
+ The default timeout is 35 seconds so the 25-second synchronous inventory batch
88
+ can finish. Do not configure `/v1/inventories` with a timeout below 30 seconds.
89
+
90
+ For testing or a compatible deployment, pass `base_url`, `timeout`, or a custom
91
+ `transport` to the constructor.
92
+
93
+ ## Build the package
94
+
95
+ ```sh
96
+ python -m build
97
+ ```
98
+
99
+ The command creates both an sdist and a wheel under `dist/`.
@@ -0,0 +1,7 @@
1
+ README.md
2
+ pyproject.toml
3
+ wok_api.py
4
+ wok_api.egg-info/PKG-INFO
5
+ wok_api.egg-info/SOURCES.txt
6
+ wok_api.egg-info/dependency_links.txt
7
+ wok_api.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ wok_api
@@ -0,0 +1,325 @@
1
+ """Небольшой синхронный клиент WOK без сторонних зависимостей."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Any, Callable, Iterable
7
+ from urllib.error import HTTPError, URLError
8
+ from urllib.parse import quote, urlencode, urlparse
9
+ from urllib.request import Request, urlopen
10
+
11
+
12
+ class WokApiError(RuntimeError):
13
+ """Ошибка API с сохранёнными кодом ответа и безопасным JSON-телом."""
14
+
15
+ def __init__(self, status: int, message: str, *,
16
+ payload: Any = None, retry_after: str | None = None,
17
+ rate_limit: str | None = None,
18
+ rate_remaining: str | None = None,
19
+ rate_reset: str | None = None):
20
+ super().__init__(message)
21
+ self.status = status
22
+ self.payload = payload
23
+ self.retry_after = retry_after
24
+ self.rate_limit = rate_limit
25
+ self.rate_remaining = rate_remaining
26
+ self.rate_reset = rate_reset
27
+
28
+
29
+ Transport = Callable[[Request, float], Any]
30
+
31
+
32
+ class WokApi:
33
+ """Клиент публичных маршрутов WOK Steam API."""
34
+
35
+ def __init__(self, api_key: str, *,
36
+ base_url: str = "https://woksteamapi.com",
37
+ timeout: float = 35.0,
38
+ transport: Transport | None = None):
39
+ parsed = urlparse(base_url)
40
+ if parsed.scheme not in {"http", "https"} or not parsed.netloc:
41
+ raise ValueError("base_url должен быть абсолютным HTTP(S) URL")
42
+ if not isinstance(api_key, str) or not api_key.strip():
43
+ raise ValueError("api_key не должен быть пустым")
44
+ if timeout <= 0:
45
+ raise ValueError("timeout должен быть больше нуля")
46
+ self.base_url = base_url.rstrip("/")
47
+ self.timeout = float(timeout)
48
+ self._api_key = api_key.strip()
49
+ self._transport = transport or self._open
50
+
51
+ @staticmethod
52
+ def _open(request: Request, timeout: float):
53
+ return urlopen(request, timeout=timeout)
54
+
55
+ @staticmethod
56
+ def _decode(raw: bytes) -> Any:
57
+ if not raw:
58
+ return None
59
+ try:
60
+ return json.loads(raw.decode("utf-8"))
61
+ except (UnicodeDecodeError, json.JSONDecodeError):
62
+ return {"error": "invalid_json_response"}
63
+
64
+ @staticmethod
65
+ def _error_message(payload: Any) -> str:
66
+ """Извлечь сообщение и из простого, и из структурного error."""
67
+ if not isinstance(payload, dict):
68
+ return "API request failed"
69
+ message = payload.get("message")
70
+ if isinstance(message, str) and message:
71
+ return message
72
+ error = payload.get("error")
73
+ if isinstance(error, str) and error:
74
+ return error
75
+ if isinstance(error, dict):
76
+ for field in ("message", "code"):
77
+ value = error.get(field)
78
+ if isinstance(value, str) and value:
79
+ return value
80
+ return "API request failed"
81
+
82
+ def _request(self, method: str, path: str, *,
83
+ query: dict[str, Any] | None = None,
84
+ body: dict[str, Any] | None = None,
85
+ authenticated: bool = True) -> Any:
86
+ if not path.startswith("/"):
87
+ raise ValueError("path должен начинаться с /")
88
+ clean_query = {
89
+ key: value for key, value in (query or {}).items()
90
+ if value is not None and value != ""
91
+ }
92
+ suffix = f"?{urlencode(clean_query, doseq=True)}" if clean_query else ""
93
+ raw_body = (json.dumps(body, separators=(",", ":")).encode("utf-8")
94
+ if body is not None else None)
95
+ headers = {
96
+ "Accept": "application/json",
97
+ "User-Agent": "wok-api-python/1.2.0",
98
+ }
99
+ if authenticated:
100
+ headers["Authorization"] = f"Bearer {self._api_key}"
101
+ if raw_body is not None:
102
+ headers["Content-Type"] = "application/json"
103
+ request = Request(
104
+ f"{self.base_url}{path}{suffix}", data=raw_body,
105
+ headers=headers, method=method,
106
+ )
107
+ try:
108
+ with self._transport(request, self.timeout) as response:
109
+ return self._decode(response.read())
110
+ except HTTPError as exc:
111
+ payload = self._decode(exc.read())
112
+ raise WokApiError(
113
+ int(exc.code), self._error_message(payload), payload=payload,
114
+ retry_after=exc.headers.get("Retry-After"),
115
+ rate_limit=exc.headers.get("X-RateLimit-Limit"),
116
+ rate_remaining=exc.headers.get("X-RateLimit-Remaining"),
117
+ rate_reset=exc.headers.get("X-RateLimit-Reset"),
118
+ ) from exc
119
+ except URLError as exc:
120
+ raise WokApiError(0, "Network request failed") from exc
121
+
122
+ def _request_bytes(self, method: str, path: str, *,
123
+ body: dict[str, Any]) -> bytes:
124
+ """Request a bounded binary API response while preserving JSON errors."""
125
+ raw_body = json.dumps(body, separators=(",", ":")).encode("utf-8")
126
+ request = Request(
127
+ f"{self.base_url}{path}", data=raw_body,
128
+ headers={
129
+ "Accept": "image/png",
130
+ "Authorization": f"Bearer {self._api_key}",
131
+ "Content-Type": "application/json",
132
+ "User-Agent": "wok-api-python/1.2.0",
133
+ },
134
+ method=method,
135
+ )
136
+ try:
137
+ with self._transport(request, self.timeout) as response:
138
+ return response.read()
139
+ except HTTPError as exc:
140
+ payload = self._decode(exc.read())
141
+ raise WokApiError(
142
+ int(exc.code), self._error_message(payload), payload=payload,
143
+ retry_after=exc.headers.get("Retry-After"),
144
+ rate_limit=exc.headers.get("X-RateLimit-Limit"),
145
+ rate_remaining=exc.headers.get("X-RateLimit-Remaining"),
146
+ rate_reset=exc.headers.get("X-RateLimit-Reset"),
147
+ ) from exc
148
+ except URLError as exc:
149
+ raise WokApiError(0, "Network request failed") from exc
150
+
151
+ @staticmethod
152
+ def _values(values: Iterable[str], name: str) -> list[str]:
153
+ if isinstance(values, (str, bytes)):
154
+ raise ValueError(f"{name} должен быть списком")
155
+ result = [str(value) for value in values]
156
+ if not result or any(not value for value in result):
157
+ raise ValueError(f"{name} не должен быть пустым")
158
+ return result
159
+
160
+ def inventory(self, steam_id: str, *, game: str = "cs2",
161
+ no_cache: bool = False, top: int = 10,
162
+ include_inspect: bool = False,
163
+ refresh_prices: bool = False,
164
+ price_source: str | None = None,
165
+ strict: bool = False) -> dict[str, Any]:
166
+ return self._request("GET", "/v1/inventory", query={
167
+ "steam_id": steam_id, "game": game,
168
+ "no_cache": int(no_cache), "top": top,
169
+ "include_inspect": int(include_inspect),
170
+ "refresh_prices": 1 if refresh_prices else None,
171
+ "price_source": price_source,
172
+ "strict": int(strict),
173
+ })
174
+
175
+ def refresh_inventory_prices(self, steam_id: str, *, game: str = "cs2",
176
+ top: int = 10,
177
+ include_inspect: bool = False,
178
+ price_source: str | None = None,
179
+ strict: bool = False) -> dict[str, Any]:
180
+ """Refresh prices from the local cache without fetching Steam inventory."""
181
+ body = {
182
+ "steam_id": steam_id,
183
+ "game": game,
184
+ "top": int(top),
185
+ "include_inspect": bool(include_inspect),
186
+ "strict": bool(strict),
187
+ }
188
+ if price_source is not None:
189
+ body["price_source"] = price_source
190
+ return self._request("POST", "/v1/inventory/refresh-prices", body=body)
191
+
192
+ def inventories(self, steamids: Iterable[str], *,
193
+ games: Iterable[str] = ("cs2",),
194
+ no_cache: bool = False, top: int = 10,
195
+ include_inspect: bool = False) -> dict[str, Any]:
196
+ return self._request("POST", "/v1/inventories", body={
197
+ "steamids": self._values(steamids, "steamids"),
198
+ "games": self._values(games, "games"),
199
+ "no_cache": bool(no_cache), "top": int(top),
200
+ "include_inspect": bool(include_inspect),
201
+ })
202
+
203
+ def compatibility_inventory(self, steam_id: str, *, game: str = "cs2",
204
+ no_cache: bool = False,
205
+ grouped: bool = True,
206
+ refresh_prices: bool = False,
207
+ price_source: str | None = None,
208
+ strict: bool = False) -> list[dict[str, Any]]:
209
+ return self._request("GET", "/steam/api/inventory", query={
210
+ "steam_id": steam_id, "game": game, "currency": "USD",
211
+ "no_cache": int(no_cache), "group": int(grouped),
212
+ "refresh_prices": 1 if refresh_prices else None,
213
+ "price_source": price_source,
214
+ "strict": int(strict),
215
+ })
216
+
217
+ def compatibility_profile(self, steam_id: str) -> dict[str, Any]:
218
+ return self._request("GET", "/steam/api/profile", query={
219
+ "steam_id": steam_id,
220
+ })
221
+
222
+ def profile(self, steam_id: str) -> dict[str, Any]:
223
+ return self._request("GET", "/v1/profile", query={"steam_id": steam_id})
224
+
225
+ def profiles(self, steamids: Iterable[str]) -> dict[str, dict[str, Any]]:
226
+ return self._request("POST", "/v1/profiles", body={
227
+ "steamids": self._values(steamids, "steamids"),
228
+ })
229
+
230
+ def player_security(self, steam_id: str) -> dict[str, Any]:
231
+ return self._request("GET", "/v1/player-security", query={
232
+ "steam_id": steam_id,
233
+ })
234
+
235
+ def friends(self, steam_id: str, *, limit: int = 100,
236
+ cursor: str | None = None) -> dict[str, Any]:
237
+ return self._request("GET", "/v1/friends", query={
238
+ "steam_id": steam_id, "limit": limit, "cursor": cursor,
239
+ })
240
+
241
+ def price(self, game: str, name: str) -> dict[str, Any]:
242
+ return self._request("GET", "/v1/price", query={
243
+ "game": game, "name": name,
244
+ })
245
+
246
+ def prices(self, game: str, names: Iterable[str]) -> dict[str, Any]:
247
+ return self._request("POST", "/v1/prices", body={
248
+ "game": game, "names": self._values(names, "names"),
249
+ })
250
+
251
+ def items(self, *, game: str = "cs2", query: str | None = None,
252
+ source: str | None = None, limit: int = 50,
253
+ cursor: str | None = None) -> dict[str, Any]:
254
+ return self._request("GET", "/v1/items", query={
255
+ "game": game, "q": query, "source": source,
256
+ "limit": limit, "cursor": cursor,
257
+ })
258
+
259
+ def price_history(self, game: str, name: str, *, days: int = 30,
260
+ source: str | None = None) -> dict[str, Any]:
261
+ return self._request("GET", "/v1/price-history", query={
262
+ "game": game, "name": name, "days": days, "source": source,
263
+ })
264
+
265
+ def inspect_cs2(self, inspect_link: str) -> dict[str, Any]:
266
+ """float_value читать по float, paint_seed и paint_index по их флагам."""
267
+ return self._request("POST", "/v1/cs2/inspect", body={
268
+ "inspect_link": inspect_link,
269
+ })
270
+
271
+ def cs2_screenshot(self, inspect_link: str | None = None, *,
272
+ certificate: str | None = None,
273
+ theme: str = "wok",
274
+ item_name: str | None = None,
275
+ download: bool = False) -> bytes:
276
+ """Render the modern inspect certificate locally on WOK as PNG."""
277
+ if bool(inspect_link) == bool(certificate):
278
+ raise ValueError(
279
+ "передайте ровно одно поле: inspect_link или certificate")
280
+ body: dict[str, Any] = {
281
+ "theme": theme,
282
+ "download": bool(download),
283
+ }
284
+ if inspect_link:
285
+ body["inspect_link"] = inspect_link
286
+ if certificate:
287
+ body["certificate"] = certificate
288
+ if item_name:
289
+ body["item_name"] = item_name
290
+ return self._request_bytes("POST", "/v1/cs2/screenshot", body=body)
291
+
292
+ def create_crawl(self, steamids: Iterable[str], *,
293
+ games: Iterable[str] = ("cs2",)) -> dict[str, Any]:
294
+ return self._request("POST", "/v1/crawl", body={
295
+ "steamids": self._values(steamids, "steamids"),
296
+ "games": self._values(games, "games"),
297
+ })
298
+
299
+ def crawl_status(self, batch_id: str) -> dict[str, Any]:
300
+ return self._request("GET", f"/v1/crawl/{quote(batch_id, safe='')}")
301
+
302
+ def faceit_player(self, steam_id: str, *, game: str = "cs2") -> dict[str, Any]:
303
+ return self._request("GET", "/faceit/data/v4/players", query={
304
+ "game": game, "game_player_id": steam_id,
305
+ })
306
+
307
+ def faceit_stats(self, player_id: str, *, game: str = "cs2") -> dict[str, Any]:
308
+ player = quote(player_id, safe="")
309
+ game_name = quote(game, safe="")
310
+ return self._request(
311
+ "GET", f"/faceit/data/v4/players/{player}/stats/{game_name}")
312
+
313
+ def faceit_history(self, player_id: str, *, game: str = "cs2",
314
+ limit: int = 5) -> dict[str, Any]:
315
+ player = quote(player_id, safe="")
316
+ return self._request(
317
+ "GET", f"/faceit/data/v4/players/{player}/history",
318
+ query={"game": game, "limit": limit},
319
+ )
320
+
321
+ def health(self) -> dict[str, Any]:
322
+ return self._request("GET", "/healthz", authenticated=False)
323
+
324
+
325
+ __all__ = ["WokApi", "WokApiError"]