amazon-orders 4.4.4__tar.gz → 4.4.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 (42) hide show
  1. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/CHANGELOG.md +14 -1
  2. {amazon_orders-4.4.4/amazon_orders.egg-info → amazon_orders-4.4.6}/PKG-INFO +2 -2
  3. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/README.md +1 -1
  4. {amazon_orders-4.4.4 → amazon_orders-4.4.6/amazon_orders.egg-info}/PKG-INFO +2 -2
  5. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/__init__.py +1 -1
  6. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/cli.py +2 -2
  7. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/contrib/browser/playwright.py +58 -13
  8. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/pyproject.toml +1 -1
  9. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/LICENSE +0 -0
  10. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/MANIFEST.in +0 -0
  11. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazon_orders.egg-info/SOURCES.txt +0 -0
  12. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazon_orders.egg-info/dependency_links.txt +0 -0
  13. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazon_orders.egg-info/entry_points.txt +0 -0
  14. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazon_orders.egg-info/requires.txt +0 -0
  15. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazon_orders.egg-info/top_level.txt +0 -0
  16. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/banner.txt +0 -0
  17. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/conf.py +0 -0
  18. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/constants.py +0 -0
  19. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/contrib/__init__.py +0 -0
  20. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/contrib/browser/__init__.py +0 -0
  21. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/contrib/waf/__init__.py +0 -0
  22. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/contrib/waf/anticaptcha.py +0 -0
  23. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/contrib/waf/base.py +0 -0
  24. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/contrib/waf/capsolver.py +0 -0
  25. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/contrib/waf/twocaptcha.py +0 -0
  26. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/entity/__init__.py +0 -0
  27. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/entity/item.py +0 -0
  28. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/entity/order.py +0 -0
  29. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/entity/parsable.py +0 -0
  30. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/entity/recipient.py +0 -0
  31. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/entity/seller.py +0 -0
  32. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/entity/shipment.py +0 -0
  33. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/entity/transaction.py +0 -0
  34. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/exception.py +0 -0
  35. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/forms.py +0 -0
  36. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/orders.py +0 -0
  37. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/selectors.py +0 -0
  38. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/session.py +0 -0
  39. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/transactions.py +0 -0
  40. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/amazonorders/util.py +0 -0
  41. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/setup.cfg +0 -0
  42. {amazon_orders-4.4.4 → amazon_orders-4.4.6}/tests/testcase.py +0 -0
@@ -4,7 +4,20 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
- ## [Unreleased](https://github.com/alexdlaird/amazon-orders/compare/4.4.4...HEAD)
7
+ ## [Unreleased](https://github.com/alexdlaird/amazon-orders/compare/4.4.6...HEAD)
8
+
9
+ ## [4.4.6](https://github.com/alexdlaird/amazon-orders/compare/4.4.5...4.4.6) - 2026-07-20
10
+
11
+ ### Fixed
12
+
13
+ - `PlaywrightManualWafForm` alongside `PlaywrightAcicForm` now opens a visible browser to solve ACIC-embedded challenges manually.
14
+
15
+ ## [4.4.5](https://github.com/alexdlaird/amazon-orders/compare/4.4.4...4.4.5) - 2026-07-15
16
+
17
+ ### Fixed
18
+
19
+ - CLI `history` crash when using `--last-30-days` or `--last-3-months`.
20
+ - CLI `history --start-index` now validates non-numeric input.
8
21
 
9
22
  ## [4.4.4](https://github.com/alexdlaird/amazon-orders/compare/4.4.3...4.4.4) - 2026-07-07
10
23
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amazon-orders
3
- Version: 4.4.4
3
+ Version: 4.4.6
4
4
  Summary: A Python library (and CLI) for Amazon order history
5
5
  Author-email: Alex Laird <contact@alexlaird.com>
6
6
  Maintainer-email: Alex Laird <contact@alexlaird.com>
@@ -72,7 +72,7 @@ Requires-Dist: sphinx-sitemap; extra == "docs"
72
72
  Requires-Dist: sphinxext-opengraph; extra == "docs"
73
73
  Dynamic: license-file
74
74
 
75
- <p align="center"><img alt="amazon-orders - A Python library (and CLI) for Amazon order history" src="https://amazon-orders.readthedocs.io/_images/logo.png" /></p>
75
+ <p align="center"><img alt="amazon-orders - A Python library (and CLI) for Amazon order history" src="https://amazon-orders.readthedocs.io/en/latest/_images/logo.png" /></p>
76
76
 
77
77
  [![Version](https://img.shields.io/pypi/v/amazon-orders)](https://pypi.org/project/amazon-orders)
78
78
  [![Python Versions](https://img.shields.io/pypi/pyversions/amazon-orders.svg)](https://pypi.org/project/amazon-orders)
@@ -1,4 +1,4 @@
1
- <p align="center"><img alt="amazon-orders - A Python library (and CLI) for Amazon order history" src="https://amazon-orders.readthedocs.io/_images/logo.png" /></p>
1
+ <p align="center"><img alt="amazon-orders - A Python library (and CLI) for Amazon order history" src="https://amazon-orders.readthedocs.io/en/latest/_images/logo.png" /></p>
2
2
 
3
3
  [![Version](https://img.shields.io/pypi/v/amazon-orders)](https://pypi.org/project/amazon-orders)
4
4
  [![Python Versions](https://img.shields.io/pypi/pyversions/amazon-orders.svg)](https://pypi.org/project/amazon-orders)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amazon-orders
3
- Version: 4.4.4
3
+ Version: 4.4.6
4
4
  Summary: A Python library (and CLI) for Amazon order history
5
5
  Author-email: Alex Laird <contact@alexlaird.com>
6
6
  Maintainer-email: Alex Laird <contact@alexlaird.com>
@@ -72,7 +72,7 @@ Requires-Dist: sphinx-sitemap; extra == "docs"
72
72
  Requires-Dist: sphinxext-opengraph; extra == "docs"
73
73
  Dynamic: license-file
74
74
 
75
- <p align="center"><img alt="amazon-orders - A Python library (and CLI) for Amazon order history" src="https://amazon-orders.readthedocs.io/_images/logo.png" /></p>
75
+ <p align="center"><img alt="amazon-orders - A Python library (and CLI) for Amazon order history" src="https://amazon-orders.readthedocs.io/en/latest/_images/logo.png" /></p>
76
76
 
77
77
  [![Version](https://img.shields.io/pypi/v/amazon-orders)](https://pypi.org/project/amazon-orders)
78
78
  [![Python Versions](https://img.shields.io/pypi/pyversions/amazon-orders.svg)](https://pypi.org/project/amazon-orders)
@@ -1,3 +1,3 @@
1
1
  __copyright__ = "Copyright (c) 2024-2025 Alex Laird"
2
2
  __license__ = "MIT"
3
- __version__ = "4.4.4"
3
+ __version__ = "4.4.6"
@@ -119,7 +119,7 @@ def amazon_orders_cli(ctx: Context,
119
119
  help="Get Order history for the last 30 days.")
120
120
  @click.option("--last-3-months", "last_3_months", is_flag=True, default=False,
121
121
  help="Get Order history for the past 3 months.")
122
- @click.option("--start-index",
122
+ @click.option("--start-index", type=int,
123
123
  help="The index of the Order from which to start fetching in the history.")
124
124
  @click.option("--single-page", is_flag=True, default=False,
125
125
  help="Only one page should be fetched.")
@@ -147,7 +147,7 @@ def history(ctx: Context,
147
147
  order_filter = kwargs["order_filter"]
148
148
 
149
149
  exclusive_flags = [year, last_3_months, last_30_days]
150
- if not all(not item for item in exclusive_flags) and sum(exclusive_flags) == 1:
150
+ if sum(1 for item in exclusive_flags if item) > 1:
151
151
  ctx.fail("Only one of --last-30-days, --last-3-months, or --year may be used at a time.")
152
152
 
153
153
  # Determine time filter
@@ -19,6 +19,7 @@ from amazonorders.forms import AuthForm
19
19
  from amazonorders.util import AmazonSessionResponse
20
20
 
21
21
  if TYPE_CHECKING:
22
+ from amazonorders.contrib.waf.base import AwsWafForm
22
23
  from amazonorders.session import AmazonSession
23
24
 
24
25
  logger = logging.getLogger(__name__)
@@ -44,6 +45,10 @@ class PlaywrightAuthForm(AuthForm):
44
45
  #: Whether to launch the browser in headless mode. Defaults to ``True``.
45
46
  #: Set to ``False`` in subclasses that require user interaction.
46
47
  self.headless: bool = True
48
+ #: Whether this form solves challenges by handing off to a human in a visible
49
+ #: browser window (as opposed to an automated third-party solver). Forms with
50
+ #: this set act as the free, manual solver for an embedded ACIC challenge.
51
+ self.manual: bool = False
47
52
 
48
53
  def fill_form(self,
49
54
  additional_attrs: Optional[Dict[str, Any]] = None) -> None:
@@ -197,10 +202,16 @@ class PlaywrightAuthForm(AuthForm):
197
202
  class PlaywrightAcicForm(PlaywrightAuthForm):
198
203
  """
199
204
  Handles Amazon's ACIC (Amazon Challenge and Identity Component) JavaScript challenge
200
- by running it in a headless browser. If an embedded AWS WAF challenge is present on
201
- the ACIC page, it will be solved automatically using the first
205
+ by running it in a headless browser. If an embedded AWS WAF challenge or visual grid
206
+ Puzzle is present on the ACIC page, it will be solved automatically using the first
202
207
  :class:`~amazonorders.contrib.waf.base.AwsWafForm` found in ``auth_forms_classes``.
203
208
 
209
+ If no automated solver is registered but a manual solver
210
+ (:class:`~amazonorders.contrib.browser.playwright.PlaywrightManualWafForm`) is, a
211
+ **visible** browser window is opened instead so the user can solve the embedded
212
+ challenge themselves, for free. An automated solver takes precedence when both are
213
+ registered.
214
+
204
215
  Detects the challenge via the ``#aa-challenge-page-captcha-container`` element and
205
216
  waits for navigation away from ``/ax/aaut/verify/ap/challenge``.
206
217
 
@@ -224,9 +235,50 @@ class PlaywrightAcicForm(PlaywrightAuthForm):
224
235
  :return: ``True`` if an ACIC challenge was detected, ``False`` otherwise.
225
236
  """
226
237
  self.amazon_session = amazon_session
227
- return bool(parsed.select_one(self.config.selectors.ACIC_CHALLENGE_SELECTOR))
238
+ detected = bool(parsed.select_one(self.config.selectors.ACIC_CHALLENGE_SELECTOR))
239
+ if detected:
240
+ self.headless = not self._manual_mode()
241
+ return detected
242
+
243
+ def _manual_mode(self) -> bool:
244
+ """
245
+ Return ``True`` if the embedded challenge should be handed off to a human in a
246
+ visible browser window rather than an automated solver. This is the case when a
247
+ manual solver form is registered and no automated :class:`AwsWafForm` is; an
248
+ automated solver takes precedence when both are present, since it is
249
+ non-interactive.
250
+ """
251
+ return self._find_manual_solver() is not None and self._find_waf_solver() is None
252
+
253
+ def _find_waf_solver(self) -> Optional["AwsWafForm"]:
254
+ if not self.amazon_session:
255
+ return None # pragma: no cover
256
+ from amazonorders.contrib.waf.base import AwsWafForm
257
+ return next(
258
+ (f for f in self.amazon_session.auth_forms if isinstance(f, AwsWafForm)),
259
+ None,
260
+ )
261
+
262
+ def _find_manual_solver(self) -> Optional["PlaywrightAuthForm"]:
263
+ if not self.amazon_session:
264
+ return None # pragma: no cover
265
+ return next(
266
+ (f for f in self.amazon_session.auth_forms
267
+ if isinstance(f, PlaywrightAuthForm) and f.manual),
268
+ None,
269
+ )
228
270
 
229
271
  def _on_challenge_page(self, page: Any, context: Any, output_dir: Optional[str]) -> None:
272
+ if self._manual_mode():
273
+ message = (
274
+ "Info: A browser window has opened — solve the challenge, then return "
275
+ "here when done."
276
+ )
277
+ logger.info(message)
278
+ if self.amazon_session:
279
+ self.amazon_session.io.echo(message)
280
+ return
281
+
230
282
  max_solves = self.config.max_auth_attempts
231
283
  solves = 0
232
284
  while solves < max_solves:
@@ -281,11 +333,7 @@ class PlaywrightAcicForm(PlaywrightAuthForm):
281
333
  if not self.amazon_session:
282
334
  return False # pragma: no cover
283
335
 
284
- from amazonorders.contrib.waf.base import AwsWafForm
285
- waf_form = next(
286
- (f for f in self.amazon_session.auth_forms if isinstance(f, AwsWafForm)),
287
- None,
288
- )
336
+ waf_form = self._find_waf_solver()
289
337
  if not waf_form:
290
338
  logger.debug("No AwsWafForm configured, skipping WAF challenge.")
291
339
  return False
@@ -339,11 +387,7 @@ class PlaywrightAcicForm(PlaywrightAuthForm):
339
387
  if not self.amazon_session:
340
388
  return False # pragma: no cover
341
389
 
342
- from amazonorders.contrib.waf.base import AwsWafForm
343
- waf_form = next(
344
- (f for f in self.amazon_session.auth_forms if isinstance(f, AwsWafForm)),
345
- None,
346
- )
390
+ waf_form = self._find_waf_solver()
347
391
  if not waf_form:
348
392
  logger.debug("No AwsWafForm configured, skipping Puzzle.")
349
393
  return False
@@ -502,6 +546,7 @@ class PlaywrightManualWafForm(PlaywrightAuthForm):
502
546
  config: AmazonOrdersConfig) -> None:
503
547
  super().__init__(config)
504
548
  self.headless = False
549
+ self.manual = True
505
550
 
506
551
  def select_form(self,
507
552
  amazon_session: "AmazonSession",
@@ -137,7 +137,7 @@ directory = "build/coverage"
137
137
  [tool.flake8]
138
138
  max-line-length = 119
139
139
  statistics = true
140
- exclude = "scripts/*,docs/*,venv/*,build/*,dist/*,.egg-info/*,.mypy_cache/*"
140
+ exclude = "scripts/*,docs/*,venv/*,build/*,dist/*,.egg-info/*,.mypy_cache/*,.claude/*"
141
141
 
142
142
  [[tool.mypy.overrides]]
143
143
  module = "amazoncaptcha.*"
File without changes
File without changes
File without changes