tradera-cli 0.1.4__tar.gz → 0.1.6__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.6}/PKG-INFO +1 -1
  2. {tradera_cli-0.1.4 → tradera_cli-0.1.6}/pyproject.toml +1 -1
  3. {tradera_cli-0.1.4 → tradera_cli-0.1.6}/tests/test_api.py +3 -1
  4. {tradera_cli-0.1.4 → tradera_cli-0.1.6}/tradera_cli/api.py +87 -3
  5. {tradera_cli-0.1.4 → tradera_cli-0.1.6/tradera_cli.egg-info}/PKG-INFO +1 -1
  6. {tradera_cli-0.1.4 → tradera_cli-0.1.6}/LICENSE +0 -0
  7. {tradera_cli-0.1.4 → tradera_cli-0.1.6}/README.md +0 -0
  8. {tradera_cli-0.1.4 → tradera_cli-0.1.6}/setup.cfg +0 -0
  9. {tradera_cli-0.1.4 → tradera_cli-0.1.6}/tests/test_cli.py +0 -0
  10. {tradera_cli-0.1.4 → tradera_cli-0.1.6}/tests/test_cli_entry.py +0 -0
  11. {tradera_cli-0.1.4 → tradera_cli-0.1.6}/tests/test_formatters.py +0 -0
  12. {tradera_cli-0.1.4 → tradera_cli-0.1.6}/tests/test_import.py +0 -0
  13. {tradera_cli-0.1.4 → tradera_cli-0.1.6}/tests/test_main_module.py +0 -0
  14. {tradera_cli-0.1.4 → tradera_cli-0.1.6}/tradera_cli/__init__.py +0 -0
  15. {tradera_cli-0.1.4 → tradera_cli-0.1.6}/tradera_cli/__main__.py +0 -0
  16. {tradera_cli-0.1.4 → tradera_cli-0.1.6}/tradera_cli/cli.py +0 -0
  17. {tradera_cli-0.1.4 → tradera_cli-0.1.6}/tradera_cli/formatters.py +0 -0
  18. {tradera_cli-0.1.4 → tradera_cli-0.1.6}/tradera_cli.egg-info/SOURCES.txt +0 -0
  19. {tradera_cli-0.1.4 → tradera_cli-0.1.6}/tradera_cli.egg-info/dependency_links.txt +0 -0
  20. {tradera_cli-0.1.4 → tradera_cli-0.1.6}/tradera_cli.egg-info/entry_points.txt +0 -0
  21. {tradera_cli-0.1.4 → tradera_cli-0.1.6}/tradera_cli.egg-info/requires.txt +0 -0
  22. {tradera_cli-0.1.4 → tradera_cli-0.1.6}/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.6
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.6"
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,44 @@ 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
+ availability = offer_data.get("availability", "")
99
+ status = "Unknown"
100
+ if "InStock" in availability:
101
+ status = "Ongoing"
102
+ elif "OutOfStock" in availability:
103
+ status = "Ended"
104
+
105
+ return {
106
+ "itemId": item.get("sku"),
107
+ "title": item.get("name"),
108
+ "shortDescription": item.get("name"),
109
+ "description": item.get("description"),
110
+ "price": offer_data.get("price"),
111
+ "buyNowPrice": offer_data.get("price"),
112
+ "fixedPrice": offer_data.get("price"),
113
+ "leadingBid": offer_data.get("price"),
114
+ "openingBid": offer_data.get("price"),
115
+ "currency": offer_data.get("priceCurrency", "SEK"),
116
+ "mainImage": item.get("image"),
117
+ "status": status,
118
+ }
119
+ except (ValueError, TypeError):
120
+ continue
121
+ return None
122
+
85
123
  def _ensure_client_token(self, force: bool = False) -> None:
86
124
  if not force and self.session.cookies.get("trd_at"):
87
125
  return
@@ -163,7 +201,32 @@ class TraderaClient:
163
201
  if not isinstance(html, str):
164
202
  raise TraderaApiError("Unexpected search page response")
165
203
 
166
- data = self._next_data_from_html(html, "search page response")
204
+ try:
205
+ data = self._next_data_from_html(html, "search page response")
206
+ except TraderaApiError:
207
+ # Fallback to scraping the HTML for search results
208
+ items = []
209
+ # Find all item links: /item/{catId}/{itemId}/{slug}
210
+ links = re.findall(r'href="/item/([0-9]+)/([0-9]+)/([^"]*)"', html)
211
+
212
+ if not links:
213
+ raise TraderaApiError("Could not parse search page response")
214
+
215
+ # To avoid duplicates
216
+ seen_ids = set()
217
+ for cat_id, item_id, slug in links:
218
+ if item_id not in seen_ids:
219
+ items.append({
220
+ "itemId": item_id,
221
+ "title": slug.replace("-", " ").replace("_", " ").capitalize(),
222
+ "url": f"/item/{cat_id}/{item_id}/{slug}",
223
+ "price": "Unknown",
224
+ "currency": "SEK",
225
+ "endDate": None,
226
+ })
227
+ seen_ids.add(item_id)
228
+
229
+ return {"items": items}
167
230
 
168
231
  discover = data.get("props", {}).get("pageProps", {}).get("initialState", {}).get("discover")
169
232
  if not isinstance(discover, dict):
@@ -184,7 +247,28 @@ class TraderaClient:
184
247
  if not isinstance(html, str):
185
248
  raise TraderaApiError(f"Unexpected item page response for item {item_id}")
186
249
 
187
- data = self._next_data_from_html(html, f"item page response for item {item_id}")
250
+ try:
251
+ data = self._next_data_from_html(html, f"item page response for item {item_id}")
252
+ except TraderaApiError:
253
+ # Fallback to JSON-LD extraction
254
+ json_ld_data = self._extract_json_ld_product(html)
255
+ if json_ld_data:
256
+ # Wrap in the structure expected by _item_from_page
257
+ data = {
258
+ "props": {
259
+ "pageProps": {
260
+ "initialState": {
261
+ "views": {
262
+ "viewItem": {
263
+ "itemDetails": json_ld_data
264
+ }
265
+ }
266
+ }
267
+ }
268
+ }
269
+ }
270
+ else:
271
+ raise
188
272
 
189
273
  view_item = (
190
274
  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.6
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