tradera-cli 0.1.4__tar.gz → 0.1.5__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.
Files changed (22) hide show
  1. {tradera_cli-0.1.4/tradera_cli.egg-info → tradera_cli-0.1.5}/PKG-INFO +1 -1
  2. {tradera_cli-0.1.4 → tradera_cli-0.1.5}/pyproject.toml +1 -1
  3. {tradera_cli-0.1.4 → tradera_cli-0.1.5}/tests/test_api.py +3 -1
  4. {tradera_cli-0.1.4 → tradera_cli-0.1.5}/tradera_cli/api.py +75 -3
  5. {tradera_cli-0.1.4 → tradera_cli-0.1.5/tradera_cli.egg-info}/PKG-INFO +1 -1
  6. {tradera_cli-0.1.4 → tradera_cli-0.1.5}/LICENSE +0 -0
  7. {tradera_cli-0.1.4 → tradera_cli-0.1.5}/README.md +0 -0
  8. {tradera_cli-0.1.4 → tradera_cli-0.1.5}/setup.cfg +0 -0
  9. {tradera_cli-0.1.4 → tradera_cli-0.1.5}/tests/test_cli.py +0 -0
  10. {tradera_cli-0.1.4 → tradera_cli-0.1.5}/tests/test_cli_entry.py +0 -0
  11. {tradera_cli-0.1.4 → tradera_cli-0.1.5}/tests/test_formatters.py +0 -0
  12. {tradera_cli-0.1.4 → tradera_cli-0.1.5}/tests/test_import.py +0 -0
  13. {tradera_cli-0.1.4 → tradera_cli-0.1.5}/tests/test_main_module.py +0 -0
  14. {tradera_cli-0.1.4 → tradera_cli-0.1.5}/tradera_cli/__init__.py +0 -0
  15. {tradera_cli-0.1.4 → tradera_cli-0.1.5}/tradera_cli/__main__.py +0 -0
  16. {tradera_cli-0.1.4 → tradera_cli-0.1.5}/tradera_cli/cli.py +0 -0
  17. {tradera_cli-0.1.4 → tradera_cli-0.1.5}/tradera_cli/formatters.py +0 -0
  18. {tradera_cli-0.1.4 → tradera_cli-0.1.5}/tradera_cli.egg-info/SOURCES.txt +0 -0
  19. {tradera_cli-0.1.4 → tradera_cli-0.1.5}/tradera_cli.egg-info/dependency_links.txt +0 -0
  20. {tradera_cli-0.1.4 → tradera_cli-0.1.5}/tradera_cli.egg-info/entry_points.txt +0 -0
  21. {tradera_cli-0.1.4 → tradera_cli-0.1.5}/tradera_cli.egg-info/requires.txt +0 -0
  22. {tradera_cli-0.1.4 → tradera_cli-0.1.5}/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.4
3
+ Version: 0.1.5
4
4
  Summary: CLI for Tradera web endpoints
5
5
  Author: Johnny W
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tradera-cli"
7
- version = "0.1.4"
7
+ version = "0.1.5"
8
8
  description = "CLI for Tradera web endpoints"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -425,12 +425,14 @@ def test_search_uses_search_page_for_search_type() -> None:
425
425
 
426
426
  def test_search_page_raises_when_next_data_is_missing() -> None:
427
427
  client = TraderaClient()
428
- client._request = lambda *_args, **_kwargs: "<html></html>" # type: ignore[method-assign]
428
+ # Use a response that has neither __NEXT_DATA__ nor any item links
429
+ client._request = lambda *_args, **_kwargs: "<html><body>No data here</body></html>" # type: ignore[method-assign]
429
430
 
430
431
  with pytest.raises(TraderaApiError, match="Could not parse search page response"):
431
432
  client.search("kamera", item_status="Unsold")
432
433
 
433
434
 
435
+
434
436
  def test_item_returns_dict_or_raises() -> None:
435
437
  client = TraderaClient()
436
438
  next_data = {
@@ -69,7 +69,7 @@ class TraderaClient:
69
69
  return response.text
70
70
 
71
71
  def _next_data_from_html(self, html: str, context: str) -> dict[str, Any]:
72
- match = re.search(r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>', html)
72
+ match = re.search(r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>', html, re.DOTALL)
73
73
  if not match:
74
74
  raise TraderaApiError(f"Could not parse {context}")
75
75
 
@@ -82,6 +82,32 @@ class TraderaClient:
82
82
  raise TraderaApiError(f"Unexpected {context}")
83
83
  return data
84
84
 
85
+ def _extract_json_ld_product(self, html: str) -> dict[str, Any] | None:
86
+ # Find all JSON-LD script blocks
87
+ scripts = re.findall(r'<script type="application/ld\+json">(.*?)</script>', html, re.DOTALL)
88
+ for script_content in scripts:
89
+ try:
90
+ data = json.loads(script_content)
91
+ # JSON-LD can be a list of objects or a single object
92
+ items = data if isinstance(data, list) else [data]
93
+ for item in items:
94
+ if isinstance(item, dict) and item.get("@type") == "Product":
95
+ offers = item.get("offers")
96
+ offer_data = offers if isinstance(offers, dict) else (offers[0] if isinstance(offers, list) and offers else {})
97
+
98
+ return {
99
+ "itemId": item.get("sku"),
100
+ "title": item.get("name"),
101
+ "shortDescription": item.get("name"),
102
+ "description": item.get("description"),
103
+ "price": offer_data.get("price"),
104
+ "currency": offer_data.get("priceCurrency", "SEK"),
105
+ "mainImage": item.get("image"),
106
+ }
107
+ except (ValueError, TypeError):
108
+ continue
109
+ return None
110
+
85
111
  def _ensure_client_token(self, force: bool = False) -> None:
86
112
  if not force and self.session.cookies.get("trd_at"):
87
113
  return
@@ -163,7 +189,32 @@ class TraderaClient:
163
189
  if not isinstance(html, str):
164
190
  raise TraderaApiError("Unexpected search page response")
165
191
 
166
- data = self._next_data_from_html(html, "search page response")
192
+ try:
193
+ data = self._next_data_from_html(html, "search page response")
194
+ except TraderaApiError:
195
+ # Fallback to scraping the HTML for search results
196
+ items = []
197
+ # Find all item links: /item/{catId}/{itemId}/{slug}
198
+ links = re.findall(r'href="/item/([0-9]+)/([0-9]+)/([^"]*)"', html)
199
+
200
+ if not links:
201
+ raise TraderaApiError("Could not parse search page response")
202
+
203
+ # To avoid duplicates
204
+ seen_ids = set()
205
+ for cat_id, item_id, slug in links:
206
+ if item_id not in seen_ids:
207
+ items.append({
208
+ "itemId": item_id,
209
+ "title": slug.replace("-", " ").replace("_", " ").capitalize(),
210
+ "url": f"/item/{cat_id}/{item_id}/{slug}",
211
+ "price": "Unknown",
212
+ "currency": "SEK",
213
+ "endDate": None,
214
+ })
215
+ seen_ids.add(item_id)
216
+
217
+ return {"items": items}
167
218
 
168
219
  discover = data.get("props", {}).get("pageProps", {}).get("initialState", {}).get("discover")
169
220
  if not isinstance(discover, dict):
@@ -184,7 +235,28 @@ class TraderaClient:
184
235
  if not isinstance(html, str):
185
236
  raise TraderaApiError(f"Unexpected item page response for item {item_id}")
186
237
 
187
- data = self._next_data_from_html(html, f"item page response for item {item_id}")
238
+ try:
239
+ data = self._next_data_from_html(html, f"item page response for item {item_id}")
240
+ except TraderaApiError:
241
+ # Fallback to JSON-LD extraction
242
+ json_ld_data = self._extract_json_ld_product(html)
243
+ if json_ld_data:
244
+ # Wrap in the structure expected by _item_from_page
245
+ data = {
246
+ "props": {
247
+ "pageProps": {
248
+ "initialState": {
249
+ "views": {
250
+ "viewItem": {
251
+ "itemDetails": json_ld_data
252
+ }
253
+ }
254
+ }
255
+ }
256
+ }
257
+ }
258
+ else:
259
+ raise
188
260
 
189
261
  view_item = (
190
262
  data.get("props", {})
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tradera-cli
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: CLI for Tradera web endpoints
5
5
  Author: Johnny W
6
6
  License-Expression: MIT
File without changes
File without changes
File without changes