__copyright__ = "Copyright (c) 2024-2025 Alex Laird"
__license__ = "MIT"
import os
from typing import Optional, TYPE_CHECKING
from urllib.parse import urlencode, urlparse
if TYPE_CHECKING:
from amazonorders.conf import AmazonOrdersConfig
#: ``Accept-Language`` values for English-locale Amazon sites, keyed by the TLD suffix that
#: follows ``amazon.``. Looked up dynamically from the user-supplied domain; unknown TLDs keep
#: the base ``en-US`` value. This map only governs the ``Accept-Language`` header — it is not
#: a list of supported sites and does not affect any other authentication behavior.
_REGION_LANGUAGES = {
"ca": "en-CA,en;q=0.9,en-US;q=0.8",
"co.uk": "en-GB,en;q=0.9,en-US;q=0.8",
"com.au": "en-AU,en;q=0.9,en-US;q=0.8",
"in": "en-IN,en;q=0.9,en-US;q=0.8",
"sg": "en-SG,en;q=0.9,en-US;q=0.8",
}
#: ``CURRENCY_SYMBOL`` values for English-locale Amazon sites where the storefront actually
#: prefixes prices with a non-``$`` symbol. amazon.com.au and amazon.ca render prices as
#: plain ``$`` (single-currency context), so they keep the default and are intentionally
#: omitted here. Skipped when ``AMAZON_CURRENCY_SYMBOL`` is set.
_REGION_CURRENCIES = {
"co.uk": "£",
"in": "₹",
"sg": "S$",
}
def _normalize_base_url(value: str) -> str:
value = value.strip().rstrip("/")
if value.startswith(("http://", "https://")):
return value
if value.startswith("www."):
return f"https://{value}"
return f"https://www.{value}"
[docs]
class Constants:
"""
A class containing useful constants. Extend and override with ``constants_class`` in the config:
.. code-block:: python
from amazonorders.conf import AmazonOrdersConfig
config = AmazonOrdersConfig(data={"constants_class": "my_module.MyConstants"})
URLs and the URL-shaped headers (``Origin``, ``Host``, ``Referer``) are derived from the active
Amazon domain. ``Accept-Language`` and ``CURRENCY_SYMBOL`` are adjusted for a small set of
English-locale TLDs (``CURRENCY_SYMBOL`` only when ``AMAZON_CURRENCY_SYMBOL`` is unset). The
domain is resolved in this precedence order:
1. The ``domain`` key on :class:`~amazonorders.conf.AmazonOrdersConfig`.
2. The ``AMAZON_BASE_URL`` environment variable.
3. The default, ``amazon.com``.
Only the English, ``.com`` site is officially supported. Other domains may work, but values like
``openid.assoc_handle`` are not adjusted automatically — subclass and set ``constants_class`` to
override them if a non-``.com`` site requires it.
"""
##########################################################################
# General URL (defaults; overridden in ``__init__`` when a domain is set)
##########################################################################
BASE_URL = "https://www.amazon.com"
##########################################################################
# URLs for AmazonSession
##########################################################################
SIGN_IN_URL = f"{BASE_URL}/ap/signin"
SIGN_IN_QUERY_PARAMS = {"openid.pape.max_auth_age": "0",
"openid.return_to": f"{BASE_URL}/?ref_=nav_custrec_signin",
"openid.identity": "http://specs.openid.net/auth/2.0/identifier_select",
"openid.assoc_handle": "usflex",
"openid.mode": "checkid_setup",
"openid.claimed_id": "http://specs.openid.net/auth/2.0/identifier_select",
"openid.ns": "http://specs.openid.net/auth/2.0"}
SIGN_IN_CLAIM_URL = f"{BASE_URL}/ax/claim"
SIGN_OUT_URL = f"{BASE_URL}/gp/flex/sign-out.html"
##########################################################################
# URLs for Orders
##########################################################################
ORDER_HISTORY_URL = f"{BASE_URL}/your-orders/orders"
ORDER_DETAILS_URL = f"{BASE_URL}/gp/your-account/order-details"
HISTORY_FILTER_QUERY_PARAM = "timeFilter"
##########################################################################
# URLs for Transactions
##########################################################################
TRANSACTION_HISTORY_ROUTE = "/cpe/yourpayments/transactions"
TRANSACTION_HISTORY_URL = f"{BASE_URL}{TRANSACTION_HISTORY_ROUTE}"
##########################################################################
# Headers
##########################################################################
BASE_HEADERS = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,"
"application/signed-exchange;v=b3;q=0.7",
"Accept-Encoding": "gzip, deflate, br, zstd",
"Accept-Language": "en-US,en;q=0.9",
"Cache-Control": "max-age=0",
"Device-Memory": "8",
"Downlink": "10",
"Dpr": "2",
"Ect": "4g",
"Origin": BASE_URL,
"Host": urlparse(BASE_URL).netloc,
"Priority": "u=0, i",
"Referer": f"{SIGN_IN_URL}?{urlencode(SIGN_IN_QUERY_PARAMS)}",
"Rtt": "0",
"Sec-Ch-Device-Memory": "8",
"Sec-Ch-Dpr": "2",
"Sec-Ch-Ua": "Chromium\";v=\"140\", \"Not=A?Brand\";v=\"24\", \"Google Chrome\";v=\"140",
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Ch-Ua-Platform": "macOS",
"Sec-Ch-Ua-Platform-Version": "15.6.1",
"Sec-Ch-Viewport-Width": "1512",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6_1) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/140.0.0.0 Safari/537.36",
"Viewport-Width": "1512"
}
##########################################################################
# Authentication
##########################################################################
COOKIES_SET_WHEN_AUTHENTICATED = ["x-main"]
JS_ROBOT_TEXT_REGEX = r"[.\s\S]*verify that you're not a robot[.\s\S]*Enable JavaScript[.\s\S]*"
##########################################################################
# Currency
##########################################################################
CURRENCY_SYMBOL = os.environ.get("AMAZON_CURRENCY_SYMBOL", "$")
def __init__(self,
config: Optional["AmazonOrdersConfig"] = None) -> None:
domain = None
if config is not None:
domain = config._data.get("domain")
if not domain:
domain = os.environ.get("AMAZON_BASE_URL")
if domain:
self._apply_domain(domain)
[docs]
def _apply_domain(self,
domain: str) -> None:
"""
Override the URL-derived attributes for the given Amazon domain.
:param domain: The Amazon domain (e.g. ``amazon.com.au``) or full URL (e.g. ``https://www.amazon.com.au``).
"""
base_url = _normalize_base_url(domain)
# Read non-URL fields from the class so subclass-level overrides (assoc_handle,
# Accept-Language, etc.) are preserved; only rewrite the URL-shaped values.
sign_in_query_params = dict(type(self).SIGN_IN_QUERY_PARAMS)
sign_in_query_params["openid.return_to"] = f"{base_url}/?ref_=nav_custrec_signin"
sign_in_url = f"{base_url}/ap/signin"
self.BASE_URL = base_url
self.SIGN_IN_URL = sign_in_url
self.SIGN_IN_QUERY_PARAMS = sign_in_query_params
self.SIGN_IN_CLAIM_URL = f"{base_url}/ax/claim"
self.SIGN_OUT_URL = f"{base_url}/gp/flex/sign-out.html"
self.ORDER_HISTORY_URL = f"{base_url}/your-orders/orders"
self.ORDER_DETAILS_URL = f"{base_url}/gp/your-account/order-details"
self.TRANSACTION_HISTORY_URL = f"{base_url}{self.TRANSACTION_HISTORY_ROUTE}"
host = urlparse(base_url).netloc.lower().split(":")[0]
if host.startswith("www."):
host = host[len("www."):]
tld = host[len("amazon."):] if host.startswith("amazon.") else ""
headers = dict(type(self).BASE_HEADERS)
headers["Origin"] = base_url
headers["Host"] = urlparse(base_url).netloc
headers["Referer"] = f"{sign_in_url}?{urlencode(sign_in_query_params)}"
if tld in _REGION_LANGUAGES:
headers["Accept-Language"] = _REGION_LANGUAGES[tld]
self.BASE_HEADERS = headers
if not os.environ.get("AMAZON_CURRENCY_SYMBOL") and tld in _REGION_CURRENCIES:
self.CURRENCY_SYMBOL = _REGION_CURRENCIES[tld]
def format_currency(self,
amount: float) -> str:
formatted_amt = "{currency_symbol}{amount:,.2f}".format(currency_symbol=self.CURRENCY_SYMBOL,
amount=abs(amount))
if round(amount, 2) < 0:
return f"-{formatted_amt}"
return formatted_amt