tradera-cli 0.1.0__tar.gz → 0.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.
- {tradera_cli-0.1.0/tradera_cli.egg-info → tradera_cli-0.1.1}/PKG-INFO +14 -1
- {tradera_cli-0.1.0 → tradera_cli-0.1.1}/README.md +13 -0
- {tradera_cli-0.1.0 → tradera_cli-0.1.1}/pyproject.toml +1 -1
- {tradera_cli-0.1.0 → tradera_cli-0.1.1}/tests/test_api.py +198 -0
- {tradera_cli-0.1.0 → tradera_cli-0.1.1}/tests/test_cli.py +16 -0
- {tradera_cli-0.1.0 → tradera_cli-0.1.1}/tradera_cli/__init__.py +1 -1
- {tradera_cli-0.1.0 → tradera_cli-0.1.1}/tradera_cli/api.py +77 -1
- {tradera_cli-0.1.0 → tradera_cli-0.1.1}/tradera_cli/cli.py +16 -0
- {tradera_cli-0.1.0 → tradera_cli-0.1.1/tradera_cli.egg-info}/PKG-INFO +14 -1
- {tradera_cli-0.1.0 → tradera_cli-0.1.1}/LICENSE +0 -0
- {tradera_cli-0.1.0 → tradera_cli-0.1.1}/setup.cfg +0 -0
- {tradera_cli-0.1.0 → tradera_cli-0.1.1}/tests/test_cli_entry.py +0 -0
- {tradera_cli-0.1.0 → tradera_cli-0.1.1}/tests/test_formatters.py +0 -0
- {tradera_cli-0.1.0 → tradera_cli-0.1.1}/tests/test_import.py +0 -0
- {tradera_cli-0.1.0 → tradera_cli-0.1.1}/tests/test_main_module.py +0 -0
- {tradera_cli-0.1.0 → tradera_cli-0.1.1}/tradera_cli/__main__.py +0 -0
- {tradera_cli-0.1.0 → tradera_cli-0.1.1}/tradera_cli/formatters.py +0 -0
- {tradera_cli-0.1.0 → tradera_cli-0.1.1}/tradera_cli.egg-info/SOURCES.txt +0 -0
- {tradera_cli-0.1.0 → tradera_cli-0.1.1}/tradera_cli.egg-info/dependency_links.txt +0 -0
- {tradera_cli-0.1.0 → tradera_cli-0.1.1}/tradera_cli.egg-info/entry_points.txt +0 -0
- {tradera_cli-0.1.0 → tradera_cli-0.1.1}/tradera_cli.egg-info/requires.txt +0 -0
- {tradera_cli-0.1.0 → tradera_cli-0.1.1}/tradera_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tradera-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: CLI for Tradera web endpoints
|
|
5
5
|
Author: Johnny W
|
|
6
6
|
License-Expression: MIT
|
|
@@ -69,6 +69,11 @@ tradera search "iphone"
|
|
|
69
69
|
tradera search "pokemon" --page 2 --page-size 20
|
|
70
70
|
tradera search "kamera" --sort AddedOn --format json
|
|
71
71
|
tradera search "klocka" --country SE --lang sv --format jsonl
|
|
72
|
+
tradera search "lego" --item-status Sold --format json
|
|
73
|
+
tradera search "lego" --condition "Oanvänt" --format json
|
|
74
|
+
tradera search "lego" --item-type FixedPrice --from-price 100 --to-price 500
|
|
75
|
+
tradera search "lego" --allowed-buyer-regions eu --counties Stockholm Uppsala
|
|
76
|
+
tradera search "lego city" --search-type ExactSearch
|
|
72
77
|
```
|
|
73
78
|
|
|
74
79
|
### Get item details
|
|
@@ -105,6 +110,14 @@ Search command (`tradera search`) supports:
|
|
|
105
110
|
| `--sort` | Sort mode (default: `Relevance`) |
|
|
106
111
|
| `--lang` | Language code (default: `sv`) |
|
|
107
112
|
| `--country` | Shipping country code (default: `SE`) |
|
|
113
|
+
| `--item-status` | Filter by item status: `Active`, `Sold`, `Unsold` |
|
|
114
|
+
| `--condition` | Filter by condition: `Oanvänt`, `Mycket gott skick`, `Gott skick`, `Okej skick`, `Defekt` |
|
|
115
|
+
| `--item-type` | Filter by listing type: `All`, `Auction`, `FixedPrice`, `ContactOnly` |
|
|
116
|
+
| `--from-price` | Minimum price |
|
|
117
|
+
| `--to-price` | Maximum price |
|
|
118
|
+
| `--allowed-buyer-regions` | Buyer region filter: `sweden`, `eu`, `international` |
|
|
119
|
+
| `--counties` | Pickup counties, passed as one or more values, for example `Stockholm Uppsala` |
|
|
120
|
+
| `--search-type` | Search mode. Current supported value: `ExactSearch` |
|
|
108
121
|
| `--no-translate` | Disable automatic translation preference |
|
|
109
122
|
| `--format` | Output format: `table`, `json`, `jsonl` |
|
|
110
123
|
|
|
@@ -39,6 +39,11 @@ tradera search "iphone"
|
|
|
39
39
|
tradera search "pokemon" --page 2 --page-size 20
|
|
40
40
|
tradera search "kamera" --sort AddedOn --format json
|
|
41
41
|
tradera search "klocka" --country SE --lang sv --format jsonl
|
|
42
|
+
tradera search "lego" --item-status Sold --format json
|
|
43
|
+
tradera search "lego" --condition "Oanvänt" --format json
|
|
44
|
+
tradera search "lego" --item-type FixedPrice --from-price 100 --to-price 500
|
|
45
|
+
tradera search "lego" --allowed-buyer-regions eu --counties Stockholm Uppsala
|
|
46
|
+
tradera search "lego city" --search-type ExactSearch
|
|
42
47
|
```
|
|
43
48
|
|
|
44
49
|
### Get item details
|
|
@@ -75,6 +80,14 @@ Search command (`tradera search`) supports:
|
|
|
75
80
|
| `--sort` | Sort mode (default: `Relevance`) |
|
|
76
81
|
| `--lang` | Language code (default: `sv`) |
|
|
77
82
|
| `--country` | Shipping country code (default: `SE`) |
|
|
83
|
+
| `--item-status` | Filter by item status: `Active`, `Sold`, `Unsold` |
|
|
84
|
+
| `--condition` | Filter by condition: `Oanvänt`, `Mycket gott skick`, `Gott skick`, `Okej skick`, `Defekt` |
|
|
85
|
+
| `--item-type` | Filter by listing type: `All`, `Auction`, `FixedPrice`, `ContactOnly` |
|
|
86
|
+
| `--from-price` | Minimum price |
|
|
87
|
+
| `--to-price` | Maximum price |
|
|
88
|
+
| `--allowed-buyer-regions` | Buyer region filter: `sweden`, `eu`, `international` |
|
|
89
|
+
| `--counties` | Pickup counties, passed as one or more values, for example `Stockholm Uppsala` |
|
|
90
|
+
| `--search-type` | Search mode. Current supported value: `ExactSearch` |
|
|
78
91
|
| `--no-translate` | Disable automatic translation preference |
|
|
79
92
|
| `--format` | Output format: `table`, `json`, `jsonl` |
|
|
80
93
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
4
|
+
from urllib.parse import parse_qs, urlparse
|
|
5
|
+
|
|
3
6
|
import requests
|
|
4
7
|
import pytest
|
|
5
8
|
|
|
@@ -212,6 +215,201 @@ def test_search_builds_payload() -> None:
|
|
|
212
215
|
assert payload["sortBy"] == "EndDate"
|
|
213
216
|
|
|
214
217
|
|
|
218
|
+
def test_search_uses_search_page_for_sold_items() -> None:
|
|
219
|
+
client = TraderaClient()
|
|
220
|
+
captured: dict[str, object] = {}
|
|
221
|
+
next_data = {
|
|
222
|
+
"props": {
|
|
223
|
+
"pageProps": {
|
|
224
|
+
"initialState": {
|
|
225
|
+
"discover": {
|
|
226
|
+
"items": [{"itemId": 42, "isActive": False}],
|
|
227
|
+
"filters": {"itemStatus": {"selectedValue": "Sold"}},
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
def fake_request(method: str, path: str, **_kwargs: object) -> str:
|
|
235
|
+
captured["method"] = method
|
|
236
|
+
captured["path"] = path
|
|
237
|
+
return (
|
|
238
|
+
'<script id="__NEXT_DATA__" type="application/json">'
|
|
239
|
+
f"{json.dumps(next_data)}"
|
|
240
|
+
"</script>"
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
client._request = fake_request # type: ignore[method-assign]
|
|
244
|
+
|
|
245
|
+
result = client.search("kamera", page=3, sort_by="EndDate", item_status="Sold")
|
|
246
|
+
|
|
247
|
+
assert result["items"][0]["itemId"] == 42
|
|
248
|
+
assert captured["method"] == "GET"
|
|
249
|
+
parsed = urlparse(str(captured["path"]))
|
|
250
|
+
assert parsed.path == "/search"
|
|
251
|
+
assert parse_qs(parsed.query) == {
|
|
252
|
+
"q": ["kamera"],
|
|
253
|
+
"paging": ["3"],
|
|
254
|
+
"sortBy": ["EndDate"],
|
|
255
|
+
"languageCodeIso2": ["sv"],
|
|
256
|
+
"itemStatus": ["Sold"],
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def test_search_uses_search_page_for_condition_filter() -> None:
|
|
261
|
+
client = TraderaClient()
|
|
262
|
+
captured: dict[str, object] = {}
|
|
263
|
+
next_data = {
|
|
264
|
+
"props": {
|
|
265
|
+
"pageProps": {
|
|
266
|
+
"initialState": {
|
|
267
|
+
"discover": {
|
|
268
|
+
"items": [{"itemId": 99}],
|
|
269
|
+
"attributeFilters": [{"parameter": "af-condition", "selectedValues": ["Oanvänt"]}],
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
def fake_request(method: str, path: str, **_kwargs: object) -> str:
|
|
277
|
+
captured["method"] = method
|
|
278
|
+
captured["path"] = path
|
|
279
|
+
return (
|
|
280
|
+
'<script id="__NEXT_DATA__" type="application/json">'
|
|
281
|
+
f"{json.dumps(next_data, ensure_ascii=False)}"
|
|
282
|
+
"</script>"
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
client._request = fake_request # type: ignore[method-assign]
|
|
286
|
+
|
|
287
|
+
result = client.search("kamera", condition="Oanvänt")
|
|
288
|
+
|
|
289
|
+
assert result["items"][0]["itemId"] == 99
|
|
290
|
+
assert captured["method"] == "GET"
|
|
291
|
+
parsed = urlparse(str(captured["path"]))
|
|
292
|
+
assert parsed.path == "/search"
|
|
293
|
+
assert parse_qs(parsed.query) == {
|
|
294
|
+
"q": ["kamera"],
|
|
295
|
+
"paging": ["1"],
|
|
296
|
+
"sortBy": ["Relevance"],
|
|
297
|
+
"languageCodeIso2": ["sv"],
|
|
298
|
+
"af-condition": ["Oanvänt"],
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def test_search_uses_search_page_for_additional_page_filters() -> None:
|
|
303
|
+
client = TraderaClient()
|
|
304
|
+
captured: dict[str, object] = {}
|
|
305
|
+
next_data = {
|
|
306
|
+
"props": {
|
|
307
|
+
"pageProps": {
|
|
308
|
+
"initialState": {
|
|
309
|
+
"discover": {
|
|
310
|
+
"items": [{"itemId": 77}],
|
|
311
|
+
"filters": {
|
|
312
|
+
"itemType": {"selectedValue": "FixedPrice"},
|
|
313
|
+
"allowedBuyerRegions": {"selectedValue": "eu;international"},
|
|
314
|
+
},
|
|
315
|
+
"filterPrice": {
|
|
316
|
+
"fromPrice": {"parameter": "fromPrice", "value": 100},
|
|
317
|
+
"toPrice": {"parameter": "toPrice", "value": 500},
|
|
318
|
+
},
|
|
319
|
+
"filterCounties": {"selectedValues": ["Stockholm", "Uppsala"]},
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
def fake_request(method: str, path: str, **_kwargs: object) -> str:
|
|
327
|
+
captured["method"] = method
|
|
328
|
+
captured["path"] = path
|
|
329
|
+
return (
|
|
330
|
+
'<script id="__NEXT_DATA__" type="application/json">'
|
|
331
|
+
f"{json.dumps(next_data, ensure_ascii=False)}"
|
|
332
|
+
"</script>"
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
client._request = fake_request # type: ignore[method-assign]
|
|
336
|
+
|
|
337
|
+
result = client.search(
|
|
338
|
+
"kamera",
|
|
339
|
+
item_type="FixedPrice",
|
|
340
|
+
from_price=100,
|
|
341
|
+
to_price=500,
|
|
342
|
+
allowed_buyer_regions="eu",
|
|
343
|
+
counties=["Stockholm", "Uppsala"],
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
assert result["items"][0]["itemId"] == 77
|
|
347
|
+
assert captured["method"] == "GET"
|
|
348
|
+
parsed = urlparse(str(captured["path"]))
|
|
349
|
+
assert parsed.path == "/search"
|
|
350
|
+
assert parse_qs(parsed.query) == {
|
|
351
|
+
"q": ["kamera"],
|
|
352
|
+
"paging": ["1"],
|
|
353
|
+
"sortBy": ["Relevance"],
|
|
354
|
+
"languageCodeIso2": ["sv"],
|
|
355
|
+
"itemType": ["FixedPrice"],
|
|
356
|
+
"fromPrice": ["100"],
|
|
357
|
+
"toPrice": ["500"],
|
|
358
|
+
"allowedBuyerRegions": ["eu;international"],
|
|
359
|
+
"counties": ["Stockholm;Uppsala"],
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def test_search_uses_search_page_for_search_type() -> None:
|
|
364
|
+
client = TraderaClient()
|
|
365
|
+
captured: dict[str, object] = {}
|
|
366
|
+
next_data = {
|
|
367
|
+
"props": {
|
|
368
|
+
"pageProps": {
|
|
369
|
+
"initialState": {
|
|
370
|
+
"discover": {
|
|
371
|
+
"items": [{"itemId": 88}],
|
|
372
|
+
"queryParams": {"q": "kamera", "searchType": "ExactSearch"},
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
def fake_request(method: str, path: str, **_kwargs: object) -> str:
|
|
380
|
+
captured["method"] = method
|
|
381
|
+
captured["path"] = path
|
|
382
|
+
return (
|
|
383
|
+
'<script id="__NEXT_DATA__" type="application/json">'
|
|
384
|
+
f"{json.dumps(next_data)}"
|
|
385
|
+
"</script>"
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
client._request = fake_request # type: ignore[method-assign]
|
|
389
|
+
|
|
390
|
+
result = client.search("kamera", search_type="ExactSearch")
|
|
391
|
+
|
|
392
|
+
assert result["items"][0]["itemId"] == 88
|
|
393
|
+
assert captured["method"] == "GET"
|
|
394
|
+
parsed = urlparse(str(captured["path"]))
|
|
395
|
+
assert parsed.path == "/search"
|
|
396
|
+
assert parse_qs(parsed.query) == {
|
|
397
|
+
"q": ["kamera"],
|
|
398
|
+
"paging": ["1"],
|
|
399
|
+
"sortBy": ["Relevance"],
|
|
400
|
+
"languageCodeIso2": ["sv"],
|
|
401
|
+
"searchType": ["ExactSearch"],
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def test_search_page_raises_when_next_data_is_missing() -> None:
|
|
406
|
+
client = TraderaClient()
|
|
407
|
+
client._request = lambda *_args, **_kwargs: "<html></html>" # type: ignore[method-assign]
|
|
408
|
+
|
|
409
|
+
with pytest.raises(TraderaApiError, match="Could not parse search page response"):
|
|
410
|
+
client.search("kamera", item_status="Unsold")
|
|
411
|
+
|
|
412
|
+
|
|
215
413
|
def test_item_returns_dict_or_raises() -> None:
|
|
216
414
|
client = TraderaClient()
|
|
217
415
|
client._request = lambda *_args, **_kwargs: {"itemId": 123} # type: ignore[method-assign]
|
|
@@ -64,6 +64,14 @@ def test_cmd_search_and_no_translate(monkeypatch: pytest.MonkeyPatch, capsys: py
|
|
|
64
64
|
sort="Relevance",
|
|
65
65
|
lang="sv",
|
|
66
66
|
country="SE",
|
|
67
|
+
item_status="Sold",
|
|
68
|
+
condition="Oanvänt",
|
|
69
|
+
item_type="FixedPrice",
|
|
70
|
+
from_price=100,
|
|
71
|
+
to_price=500,
|
|
72
|
+
allowed_buyer_regions="eu",
|
|
73
|
+
counties=["Stockholm", "Uppsala"],
|
|
74
|
+
search_type="ExactSearch",
|
|
67
75
|
no_translate=True,
|
|
68
76
|
format="table",
|
|
69
77
|
)
|
|
@@ -72,6 +80,14 @@ def test_cmd_search_and_no_translate(monkeypatch: pytest.MonkeyPatch, capsys: py
|
|
|
72
80
|
|
|
73
81
|
assert code == 0
|
|
74
82
|
assert calls["automatic_translation_preferred"] is False
|
|
83
|
+
assert calls["item_status"] == "Sold"
|
|
84
|
+
assert calls["condition"] == "Oanvänt"
|
|
85
|
+
assert calls["item_type"] == "FixedPrice"
|
|
86
|
+
assert calls["from_price"] == 100
|
|
87
|
+
assert calls["to_price"] == 500
|
|
88
|
+
assert calls["allowed_buyer_regions"] == "eu"
|
|
89
|
+
assert calls["counties"] == ["Stockholm", "Uppsala"]
|
|
90
|
+
assert calls["search_type"] == "ExactSearch"
|
|
75
91
|
out = capsys.readouterr().out
|
|
76
92
|
assert "itemId" in out
|
|
77
93
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "0.1.
|
|
2
|
+
__version__ = "0.1.1"
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
import re
|
|
4
5
|
from dataclasses import dataclass
|
|
5
6
|
from typing import Any
|
|
7
|
+
from urllib.parse import urlencode
|
|
6
8
|
|
|
7
9
|
import requests
|
|
8
10
|
from requests import RequestException
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
BASE_URL = "https://www.tradera.com"
|
|
14
|
+
ALLOWED_BUYER_REGIONS = {
|
|
15
|
+
"sweden": "sweden;eu;international",
|
|
16
|
+
"eu": "eu;international",
|
|
17
|
+
"international": "international",
|
|
18
|
+
}
|
|
12
19
|
|
|
13
20
|
|
|
14
21
|
class TraderaApiError(RuntimeError):
|
|
@@ -25,7 +32,7 @@ class TraderaClient:
|
|
|
25
32
|
self.session.headers.update(
|
|
26
33
|
{
|
|
27
34
|
"accept": "application/json, text/plain, */*",
|
|
28
|
-
"user-agent": "tradera-cli/0.1.
|
|
35
|
+
"user-agent": "tradera-cli/0.1.1",
|
|
29
36
|
}
|
|
30
37
|
)
|
|
31
38
|
|
|
@@ -84,7 +91,43 @@ class TraderaClient:
|
|
|
84
91
|
language_code_iso2: str = "sv",
|
|
85
92
|
shipping_country_code_iso2: str = "SE",
|
|
86
93
|
automatic_translation_preferred: bool = True,
|
|
94
|
+
item_status: str | None = None,
|
|
95
|
+
condition: str | None = None,
|
|
96
|
+
item_type: str | None = None,
|
|
97
|
+
from_price: int | None = None,
|
|
98
|
+
to_price: int | None = None,
|
|
99
|
+
allowed_buyer_regions: str | None = None,
|
|
100
|
+
counties: list[str] | None = None,
|
|
101
|
+
search_type: str | None = None,
|
|
87
102
|
) -> dict[str, Any]:
|
|
103
|
+
normalized_item_status = item_status or "Active"
|
|
104
|
+
page_filters: dict[str, str] = {}
|
|
105
|
+
if search_type:
|
|
106
|
+
page_filters["searchType"] = search_type
|
|
107
|
+
if normalized_item_status in {"Sold", "Unsold"}:
|
|
108
|
+
page_filters["itemStatus"] = normalized_item_status
|
|
109
|
+
if condition:
|
|
110
|
+
page_filters["af-condition"] = condition
|
|
111
|
+
if item_type and item_type != "All":
|
|
112
|
+
page_filters["itemType"] = item_type
|
|
113
|
+
if from_price is not None:
|
|
114
|
+
page_filters["fromPrice"] = str(from_price)
|
|
115
|
+
if to_price is not None:
|
|
116
|
+
page_filters["toPrice"] = str(to_price)
|
|
117
|
+
if allowed_buyer_regions:
|
|
118
|
+
page_filters["allowedBuyerRegions"] = ALLOWED_BUYER_REGIONS[allowed_buyer_regions]
|
|
119
|
+
if counties:
|
|
120
|
+
page_filters["counties"] = ";".join(counties)
|
|
121
|
+
|
|
122
|
+
if page_filters:
|
|
123
|
+
return self._search_page(
|
|
124
|
+
query=query,
|
|
125
|
+
page=page,
|
|
126
|
+
sort_by=sort_by,
|
|
127
|
+
language_code_iso2=language_code_iso2,
|
|
128
|
+
filters=page_filters,
|
|
129
|
+
)
|
|
130
|
+
|
|
88
131
|
payload: dict[str, Any] = {
|
|
89
132
|
"isCsaSearchQuery": False,
|
|
90
133
|
"query": query,
|
|
@@ -118,6 +161,39 @@ class TraderaClient:
|
|
|
118
161
|
}
|
|
119
162
|
return self._request("POST", "/api/webapi/discover/web/independent-search", json=payload)
|
|
120
163
|
|
|
164
|
+
def _search_page(
|
|
165
|
+
self,
|
|
166
|
+
query: str,
|
|
167
|
+
page: int,
|
|
168
|
+
sort_by: str,
|
|
169
|
+
language_code_iso2: str,
|
|
170
|
+
filters: dict[str, str],
|
|
171
|
+
) -> dict[str, Any]:
|
|
172
|
+
params = {
|
|
173
|
+
"q": query,
|
|
174
|
+
"paging": page,
|
|
175
|
+
"sortBy": sort_by,
|
|
176
|
+
"languageCodeIso2": language_code_iso2.lower(),
|
|
177
|
+
}
|
|
178
|
+
params.update(filters)
|
|
179
|
+
html = self._request("GET", f"/search?{urlencode(params)}")
|
|
180
|
+
if not isinstance(html, str):
|
|
181
|
+
raise TraderaApiError("Unexpected search page response")
|
|
182
|
+
|
|
183
|
+
match = re.search(r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>', html)
|
|
184
|
+
if not match:
|
|
185
|
+
raise TraderaApiError("Could not parse search page response")
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
data = json.loads(match.group(1))
|
|
189
|
+
except ValueError as exc:
|
|
190
|
+
raise TraderaApiError("Invalid search page data") from exc
|
|
191
|
+
|
|
192
|
+
discover = data.get("props", {}).get("pageProps", {}).get("initialState", {}).get("discover")
|
|
193
|
+
if not isinstance(discover, dict):
|
|
194
|
+
raise TraderaApiError("Unexpected search page data")
|
|
195
|
+
return discover
|
|
196
|
+
|
|
121
197
|
def item(self, item_id: int) -> dict[str, Any]:
|
|
122
198
|
data = self._request("GET", f"/ajax/item/{item_id}")
|
|
123
199
|
if not isinstance(data, dict):
|
|
@@ -34,6 +34,14 @@ def cmd_search(args: argparse.Namespace) -> int:
|
|
|
34
34
|
language_code_iso2=args.lang,
|
|
35
35
|
shipping_country_code_iso2=args.country,
|
|
36
36
|
automatic_translation_preferred=not args.no_translate,
|
|
37
|
+
item_status=args.item_status,
|
|
38
|
+
condition=args.condition,
|
|
39
|
+
item_type=args.item_type,
|
|
40
|
+
from_price=args.from_price,
|
|
41
|
+
to_price=args.to_price,
|
|
42
|
+
allowed_buyer_regions=args.allowed_buyer_regions,
|
|
43
|
+
counties=args.counties,
|
|
44
|
+
search_type=args.search_type,
|
|
37
45
|
)
|
|
38
46
|
rows = normalize_search_rows(data)
|
|
39
47
|
_print_output(
|
|
@@ -82,6 +90,14 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
82
90
|
search.add_argument("--sort", default="Relevance")
|
|
83
91
|
search.add_argument("--lang", default="sv")
|
|
84
92
|
search.add_argument("--country", default="SE")
|
|
93
|
+
search.add_argument("--item-status", choices=["Active", "Sold", "Unsold"])
|
|
94
|
+
search.add_argument("--condition", choices=["Oanvänt", "Mycket gott skick", "Gott skick", "Okej skick", "Defekt"])
|
|
95
|
+
search.add_argument("--item-type", choices=["All", "Auction", "FixedPrice", "ContactOnly"])
|
|
96
|
+
search.add_argument("--from-price", type=int)
|
|
97
|
+
search.add_argument("--to-price", type=int)
|
|
98
|
+
search.add_argument("--allowed-buyer-regions", choices=["sweden", "eu", "international"])
|
|
99
|
+
search.add_argument("--counties", nargs="+")
|
|
100
|
+
search.add_argument("--search-type", choices=["ExactSearch"])
|
|
85
101
|
search.add_argument("--no-translate", action="store_true")
|
|
86
102
|
search.add_argument("--format", choices=["table", "json", "jsonl"], default="table")
|
|
87
103
|
search.set_defaults(func=cmd_search)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tradera-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: CLI for Tradera web endpoints
|
|
5
5
|
Author: Johnny W
|
|
6
6
|
License-Expression: MIT
|
|
@@ -69,6 +69,11 @@ tradera search "iphone"
|
|
|
69
69
|
tradera search "pokemon" --page 2 --page-size 20
|
|
70
70
|
tradera search "kamera" --sort AddedOn --format json
|
|
71
71
|
tradera search "klocka" --country SE --lang sv --format jsonl
|
|
72
|
+
tradera search "lego" --item-status Sold --format json
|
|
73
|
+
tradera search "lego" --condition "Oanvänt" --format json
|
|
74
|
+
tradera search "lego" --item-type FixedPrice --from-price 100 --to-price 500
|
|
75
|
+
tradera search "lego" --allowed-buyer-regions eu --counties Stockholm Uppsala
|
|
76
|
+
tradera search "lego city" --search-type ExactSearch
|
|
72
77
|
```
|
|
73
78
|
|
|
74
79
|
### Get item details
|
|
@@ -105,6 +110,14 @@ Search command (`tradera search`) supports:
|
|
|
105
110
|
| `--sort` | Sort mode (default: `Relevance`) |
|
|
106
111
|
| `--lang` | Language code (default: `sv`) |
|
|
107
112
|
| `--country` | Shipping country code (default: `SE`) |
|
|
113
|
+
| `--item-status` | Filter by item status: `Active`, `Sold`, `Unsold` |
|
|
114
|
+
| `--condition` | Filter by condition: `Oanvänt`, `Mycket gott skick`, `Gott skick`, `Okej skick`, `Defekt` |
|
|
115
|
+
| `--item-type` | Filter by listing type: `All`, `Auction`, `FixedPrice`, `ContactOnly` |
|
|
116
|
+
| `--from-price` | Minimum price |
|
|
117
|
+
| `--to-price` | Maximum price |
|
|
118
|
+
| `--allowed-buyer-regions` | Buyer region filter: `sweden`, `eu`, `international` |
|
|
119
|
+
| `--counties` | Pickup counties, passed as one or more values, for example `Stockholm Uppsala` |
|
|
120
|
+
| `--search-type` | Search mode. Current supported value: `ExactSearch` |
|
|
108
121
|
| `--no-translate` | Disable automatic translation preference |
|
|
109
122
|
| `--format` | Output format: `table`, `json`, `jsonl` |
|
|
110
123
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|