amazon-subscriptions 1.0.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.
Files changed (69) hide show
  1. amazon_subscriptions-1.0.1/.github/workflows/automerge-schedule.yml +43 -0
  2. amazon_subscriptions-1.0.1/.github/workflows/lint.yml +87 -0
  3. amazon_subscriptions-1.0.1/.github/workflows/mr-test.yml +73 -0
  4. amazon_subscriptions-1.0.1/.github/workflows/pr-conventional-title.yml +23 -0
  5. amazon_subscriptions-1.0.1/.github/workflows/publish.yml +39 -0
  6. amazon_subscriptions-1.0.1/.github/workflows/release-please.yml +21 -0
  7. amazon_subscriptions-1.0.1/.gitignore +16 -0
  8. amazon_subscriptions-1.0.1/.markdownlint-cli2.yaml +13 -0
  9. amazon_subscriptions-1.0.1/.release-please-manifest.json +3 -0
  10. amazon_subscriptions-1.0.1/.yamllint.yml +17 -0
  11. amazon_subscriptions-1.0.1/CHANGELOG.md +39 -0
  12. amazon_subscriptions-1.0.1/CONTRIBUTING.md +189 -0
  13. amazon_subscriptions-1.0.1/LICENSE +21 -0
  14. amazon_subscriptions-1.0.1/PKG-INFO +314 -0
  15. amazon_subscriptions-1.0.1/README.md +301 -0
  16. amazon_subscriptions-1.0.1/amazon_subscriptions/__init__.py +0 -0
  17. amazon_subscriptions-1.0.1/amazon_subscriptions/cli.py +180 -0
  18. amazon_subscriptions-1.0.1/amazon_subscriptions/client.py +485 -0
  19. amazon_subscriptions-1.0.1/amazon_subscriptions/dates.py +63 -0
  20. amazon_subscriptions-1.0.1/amazon_subscriptions/exceptions.py +21 -0
  21. amazon_subscriptions-1.0.1/amazon_subscriptions/locales/__init__.py +68 -0
  22. amazon_subscriptions-1.0.1/amazon_subscriptions/locales/base.py +189 -0
  23. amazon_subscriptions-1.0.1/amazon_subscriptions/locales/en.py +65 -0
  24. amazon_subscriptions-1.0.1/amazon_subscriptions/models.py +163 -0
  25. amazon_subscriptions-1.0.1/amazon_subscriptions/parse/__init__.py +28 -0
  26. amazon_subscriptions-1.0.1/amazon_subscriptions/parse/acp.py +41 -0
  27. amazon_subscriptions-1.0.1/amazon_subscriptions/parse/deliveries.py +199 -0
  28. amazon_subscriptions-1.0.1/amazon_subscriptions/parse/detail.py +27 -0
  29. amazon_subscriptions-1.0.1/amazon_subscriptions/parse/matching.py +100 -0
  30. amazon_subscriptions-1.0.1/amazon_subscriptions/parse/product.py +202 -0
  31. amazon_subscriptions-1.0.1/amazon_subscriptions/parse/selectors.py +64 -0
  32. amazon_subscriptions-1.0.1/amazon_subscriptions/parse/subscriptions.py +85 -0
  33. amazon_subscriptions-1.0.1/amazon_subscriptions/parse/util.py +37 -0
  34. amazon_subscriptions-1.0.1/amazon_subscriptions/serialize.py +29 -0
  35. amazon_subscriptions-1.0.1/lychee.toml +17 -0
  36. amazon_subscriptions-1.0.1/mise.toml +28 -0
  37. amazon_subscriptions-1.0.1/pyproject.toml +48 -0
  38. amazon_subscriptions-1.0.1/release-please-config.json +38 -0
  39. amazon_subscriptions-1.0.1/renovate.json +12 -0
  40. amazon_subscriptions-1.0.1/scripts/anonymize_fixtures.py +377 -0
  41. amazon_subscriptions-1.0.1/scripts/extract_product_fixture.py +249 -0
  42. amazon_subscriptions-1.0.1/scripts/privacy_scan.py +161 -0
  43. amazon_subscriptions-1.0.1/scripts/vendor/README.md +8 -0
  44. amazon_subscriptions-1.0.1/scripts/vendor/anonymize_test_resources.py +559 -0
  45. amazon_subscriptions-1.0.1/tests/__init__.py +0 -0
  46. amazon_subscriptions-1.0.1/tests/conftest.py +222 -0
  47. amazon_subscriptions-1.0.1/tests/fixtures/de/deliveries-page-2.html +1 -0
  48. amazon_subscriptions-1.0.1/tests/fixtures/de/delivery-1.html +306 -0
  49. amazon_subscriptions-1.0.1/tests/fixtures/de/delivery-2.html +304 -0
  50. amazon_subscriptions-1.0.1/tests/fixtures/de/landing.html +309 -0
  51. amazon_subscriptions-1.0.1/tests/fixtures/de/product-alternative-offer.html +54 -0
  52. amazon_subscriptions-1.0.1/tests/fixtures/de/product-no-buybox.html +8 -0
  53. amazon_subscriptions-1.0.1/tests/fixtures/de/product-rrp.html +78 -0
  54. amazon_subscriptions-1.0.1/tests/fixtures/de/product-single-offer.html +15 -0
  55. amazon_subscriptions-1.0.1/tests/fixtures/de/product-unavailable.html +12 -0
  56. amazon_subscriptions-1.0.1/tests/fixtures/de/product-was.html +80 -0
  57. amazon_subscriptions-1.0.1/tests/fixtures/de/product.html +71 -0
  58. amazon_subscriptions-1.0.1/tests/fixtures/de/subscription-detail.html +293 -0
  59. amazon_subscriptions-1.0.1/tests/test_cli.py +228 -0
  60. amazon_subscriptions-1.0.1/tests/test_client.py +537 -0
  61. amazon_subscriptions-1.0.1/tests/test_dates.py +46 -0
  62. amazon_subscriptions-1.0.1/tests/test_locale.py +199 -0
  63. amazon_subscriptions-1.0.1/tests/test_matching.py +132 -0
  64. amazon_subscriptions-1.0.1/tests/test_parse_deliveries.py +163 -0
  65. amazon_subscriptions-1.0.1/tests/test_parse_detail.py +15 -0
  66. amazon_subscriptions-1.0.1/tests/test_parse_product.py +146 -0
  67. amazon_subscriptions-1.0.1/tests/test_parse_subscriptions.py +111 -0
  68. amazon_subscriptions-1.0.1/tests/test_serialize.py +46 -0
  69. amazon_subscriptions-1.0.1/uv.lock +952 -0
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: Schedule Release Automerge
3
+
4
+ on:
5
+ schedule:
6
+ - cron: '0 9 * * 1' # Every Monday at 9am UTC
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ automerge:
11
+ if: ${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - name: Enable auto-merge on release-please PR
15
+ env:
16
+ GH_TOKEN: ${{ secrets.OVERRIDE_TOKEN }}
17
+ run: |
18
+ PRS=$(gh pr list \
19
+ --repo "${{ github.repository }}" \
20
+ --label "autorelease: pending" \
21
+ --base "${{ github.event.repository.default_branch }}" \
22
+ --state open \
23
+ --json number)
24
+
25
+ PR_COUNT=$(printf '%s' "$PRS" | jq 'length')
26
+
27
+ if [ "$PR_COUNT" -gt 1 ]; then
28
+ echo "Expected at most one open release-please PR, found $PR_COUNT"
29
+ exit 1
30
+ fi
31
+
32
+ PR=$(printf '%s' "$PRS" | jq -r '.[0].number // empty')
33
+
34
+ if [ -n "$PR" ]; then
35
+ # Auto-merge waits for the branch to be up to date with main, and release-please leaves it
36
+ # behind when the new commits don't change the release notes.
37
+ gh pr update-branch "$PR" --repo "${{ github.repository }}"
38
+ gh pr merge "$PR" --auto --squash --delete-branch \
39
+ --repo "${{ github.repository }}"
40
+ echo "Auto-merge enabled on PR #$PR"
41
+ else
42
+ echo "No open release-please PR found"
43
+ fi
@@ -0,0 +1,87 @@
1
+ ---
2
+ name: Lint
3
+
4
+ on:
5
+ # Called by mr-test.yml, so its required job covers the linters too.
6
+ workflow_call:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ # Python is linted in mr-test.yml. Each linter here is its own job so a failure names the file type. The linters'
12
+ # versions are in mise.toml, where Renovate updates them.
13
+ jobs:
14
+ markdown:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
18
+ with:
19
+ persist-credentials: false
20
+
21
+ # Files and rules come from .markdownlint-cli2.yaml, whose globs add to the action's default of the root files.
22
+ - uses: DavidAnson/markdownlint-cli2-action@21c1be1b93ad9ed58fa840aacc3f279cde2a72ff # v24.2.0
23
+
24
+ markdown-links:
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
28
+ with:
29
+ persist-credentials: false
30
+
31
+ - name: Restore lychee cache
32
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
33
+ with:
34
+ path: .lycheecache
35
+ key: lychee-${{ github.sha }}
36
+ restore-keys: lychee-
37
+
38
+ - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0
39
+ with:
40
+ install_args: lychee
41
+
42
+ - name: lychee
43
+ env:
44
+ # Authenticated requests to GitHub links are not rate limited as quickly.
45
+ GITHUB_TOKEN: ${{ github.token }}
46
+ run: lychee --config lychee.toml --no-progress README.md
47
+
48
+ yaml:
49
+ runs-on: ubuntu-latest
50
+ steps:
51
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
52
+ with:
53
+ persist-credentials: false
54
+
55
+ - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0
56
+ with:
57
+ install_args: python uv pypi:yamllint
58
+
59
+ # Files and rules come from .yamllint.yml.
60
+ - name: yamllint
61
+ run: yamllint --strict --format github .
62
+
63
+ actions:
64
+ runs-on: ubuntu-latest
65
+ steps:
66
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
67
+ with:
68
+ persist-credentials: false
69
+
70
+ - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0
71
+ with:
72
+ install_args: actionlint shellcheck
73
+
74
+ # Also runs shellcheck over every `run:` block.
75
+ - name: actionlint
76
+ run: actionlint
77
+
78
+ json:
79
+ runs-on: ubuntu-latest
80
+ steps:
81
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
82
+ with:
83
+ persist-credentials: false
84
+
85
+ # Syntax only: release-please owns the formatting of the files it writes.
86
+ - name: jq
87
+ run: git ls-files -z '*.json' | xargs -0 -n1 jq empty
@@ -0,0 +1,73 @@
1
+ ---
2
+ name: MR Test
3
+
4
+ on:
5
+ workflow_dispatch:
6
+ push:
7
+ branches:
8
+ - main
9
+ pull_request:
10
+ branches:
11
+ - main
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ concurrency:
17
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
18
+ cancel-in-progress: true
19
+
20
+ jobs:
21
+ test:
22
+ runs-on: ubuntu-latest
23
+ timeout-minutes: 15
24
+ strategy:
25
+ fail-fast: false
26
+ matrix:
27
+ # The oldest supported version (requires-python) and the one of mise.toml.
28
+ python: ["3.10", "3.14"]
29
+ env:
30
+ UV_PYTHON: ${{ matrix.python }}
31
+ steps:
32
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
33
+ with:
34
+ persist-credentials: false
35
+
36
+ - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0
37
+
38
+ - name: Install dependencies
39
+ run: mise run install
40
+
41
+ - name: Lint
42
+ run: mise run lint
43
+
44
+ - name: Test
45
+ run: mise run test
46
+
47
+ build:
48
+ runs-on: ubuntu-latest
49
+ timeout-minutes: 10
50
+ steps:
51
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
52
+ with:
53
+ persist-credentials: false
54
+
55
+ - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0
56
+
57
+ - name: Build
58
+ run: uv build
59
+
60
+ lint:
61
+ uses: ./.github/workflows/lint.yml
62
+
63
+ # One status check for the ruleset, whatever jobs are added later.
64
+ required:
65
+ runs-on: ubuntu-latest
66
+ needs: [test, build, lint]
67
+ if: always()
68
+ steps:
69
+ - name: Check result
70
+ run: |
71
+ if [[ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
72
+ exit 1
73
+ fi
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: Conventional PR Title
3
+
4
+ on:
5
+ pull_request:
6
+ types:
7
+ - opened
8
+ - reopened
9
+ - edited
10
+ - synchronize
11
+
12
+ permissions:
13
+ pull-requests: read
14
+
15
+ jobs:
16
+ # Required by the ruleset. Squash merges use the PR title as the commit
17
+ # message, which release-please reads.
18
+ conventional-pr-title:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
22
+ env:
23
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,39 @@
1
+ ---
2
+ name: Publish to PyPI
3
+
4
+ on:
5
+ workflow_dispatch:
6
+ release:
7
+ types: [published]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+ timeout-minutes: 10
16
+ environment:
17
+ name: pypi
18
+ url: https://pypi.org/p/amazon-subscriptions
19
+ permissions:
20
+ id-token: write
21
+ steps:
22
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23
+ with:
24
+ persist-credentials: false
25
+
26
+ - name: Refuse dependencies by URL
27
+ run: |
28
+ if grep -n ' @ git+' pyproject.toml; then
29
+ echo "::error file=pyproject.toml::PyPI rejects dependencies by URL. Require a released version instead."
30
+ exit 1
31
+ fi
32
+
33
+ - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0
34
+
35
+ - name: Build
36
+ run: uv build
37
+
38
+ # Trusted publishing: PyPI trusts this workflow and the pypi environment, no token needed.
39
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: release-please
3
+
4
+ on:
5
+ workflow_dispatch:
6
+ push:
7
+ branches:
8
+ - main
9
+
10
+ permissions:
11
+ contents: write
12
+ pull-requests: write
13
+
14
+ jobs:
15
+ release-please:
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ # OVERRIDE_TOKEN is required: a release created with GITHUB_TOKEN would not trigger publish.yml.
19
+ - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
20
+ with:
21
+ token: ${{ secrets.OVERRIDE_TOKEN }}
@@ -0,0 +1,16 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .venv/
5
+ build/
6
+ dist/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ .mypy_cache/
10
+ .coverage
11
+ htmlcov/
12
+
13
+ # Debug output of amazon-orders (real, unanonymized pages)
14
+ output/
15
+
16
+ .claude/worktrees/
@@ -0,0 +1,13 @@
1
+ ---
2
+ config:
3
+ # Prose is written one paragraph per line, and tables and command lines can't be wrapped.
4
+ MD013: false
5
+
6
+ globs:
7
+ - "**/*.md"
8
+
9
+ ignores:
10
+ # Generated by release-please.
11
+ - CHANGELOG.md
12
+ - .venv/**
13
+ - .claude/**
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.0.1"
3
+ }
@@ -0,0 +1,17 @@
1
+ ---
2
+ extends: default
3
+
4
+ ignore: |
5
+ .venv/
6
+ .claude/
7
+
8
+ rules:
9
+ # `on:` is a workflow key, not a boolean.
10
+ truthy:
11
+ check-keys: false
12
+ # Workflow comments and step commands explain themselves on one line.
13
+ line-length:
14
+ max: 160
15
+ # SHA-pinned actions carry their version as `@sha # vX.Y.Z`.
16
+ comments:
17
+ min-spaces-from-content: 1
@@ -0,0 +1,39 @@
1
+ # Changelog
2
+
3
+ ## [1.0.1](https://github.com/udondan/amazon-subscriptions/compare/v1.0.0...v1.0.1) (2026-10-28)
4
+
5
+
6
+ ### Documentation
7
+
8
+ * install from PyPI, where every release is published ([#22](https://github.com/udondan/amazon-subscriptions/issues/22)) ([f56bba4](https://github.com/udondan/amazon-subscriptions/commit/f56bba404a031c7476a8887f9b45e9c4448bebf7))
9
+
10
+ ## [1.0.0](https://github.com/udondan/amazon-subscriptions/compare/v0.1.0...v1.0.0) (2026-10-28)
11
+
12
+
13
+ ### ⚠ BREAKING CHANGES
14
+
15
+ * the English instead of the German pages are read, and the configured language package is ignored. The list price type "uvp" is now "rrp". amazon-orders comes from PyPI, version 5.2.0 or a later 5.x, which keeps the session of each site in its own file (e.g. cookies.amazon.de.json); a session stored in cookies.json is still used. Sites other than amazon.de are no longer rejected if they offer English.
16
+
17
+ ### Features
18
+
19
+ * parse the English Subscribe & Save pages ([#20](https://github.com/udondan/amazon-subscriptions/issues/20)) ([f6361e4](https://github.com/udondan/amazon-subscriptions/commit/f6361e4fecd94247b5b4f13ad34ce57749251b94))
20
+
21
+ ## 0.1.0 (2026-10-13)
22
+
23
+
24
+ ### Features
25
+
26
+ * attribute product page prices to their offer and add list price type and alternative offer ([#5](https://github.com/udondan/amazon-subscriptions/issues/5)) ([07550d4](https://github.com/udondan/amazon-subscriptions/commit/07550d442b58ac87edddbb92bb5ff41bc0580a4d))
27
+ * read Subscribe & Save subscriptions and upcoming deliveries ([#1](https://github.com/udondan/amazon-subscriptions/issues/1)) ([0bdb700](https://github.com/udondan/amazon-subscriptions/commit/0bdb700d61ccac665897278de7c21d25a2697597))
28
+ * read the ASIN of backup products and, with --with-prices, regular, list and unit prices ([#3](https://github.com/udondan/amazon-subscriptions/issues/3)) ([22d96f0](https://github.com/udondan/amazon-subscriptions/commit/22d96f0a7e128b0a12a552e4ec974edf7816c9f9))
29
+
30
+
31
+ ### Bug Fixes
32
+
33
+ * read the seller name of alternative offers from the offer row and add seller_id ([#6](https://github.com/udondan/amazon-subscriptions/issues/6)) ([0accfb9](https://github.com/udondan/amazon-subscriptions/commit/0accfb9d4ce017e0a3fe50e47d2d29d709fd1b38))
34
+ * return a partial result instead of failing when a delivery or product page cannot be read ([#7](https://github.com/udondan/amazon-subscriptions/issues/7)) ([3a3f08f](https://github.com/udondan/amazon-subscriptions/commit/3a3f08f8d797a0472b6add240c3ae454bb5755d2))
35
+
36
+
37
+ ### Dependencies
38
+
39
+ * **deps:** update dependency ruff to v0.16.9 ([#9](https://github.com/udondan/amazon-subscriptions/issues/9)) ([ff25334](https://github.com/udondan/amazon-subscriptions/commit/ff25334c39d4e21857432ae0b9e83ddbfbcadf92))
@@ -0,0 +1,189 @@
1
+ # Contributing
2
+
3
+ ## Pages of another Amazon site
4
+
5
+ amazon-subscriptions reads the English Subscribe & Save pages of every Amazon site that offers English, but only
6
+ amazon.de has been tested. The texts, the markup or the URLs of another site may differ. To find out, and to keep it
7
+ working, anonymized pages of that site are needed as test fixtures. This guide shows how to capture and anonymize them
8
+ step by step. You need a Subscribe & Save account on that site, and no knowledge of the code.
9
+
10
+ > [!WARNING]
11
+ > The raw pages you capture contain your name, your address, your subscriptions, order numbers, IDs and session
12
+ > tokens. Never share them, attach them or commit them unchanged. Only share the anonymized pages, after checking
13
+ > them yourself (step 5), and delete the raw pages when you are done (step 8).
14
+
15
+ The examples use `amazon.co.uk`. Replace it with your site, and `co.uk` with the part after `amazon.`, e.g. `com`,
16
+ `ca` or `com.au`.
17
+
18
+ ### 1. Install
19
+
20
+ You need [git](https://git-scm.com/) and [uv](https://docs.astral.sh/uv/getting-started/installation/), which
21
+ installs Python itself if needed.
22
+
23
+ ```sh
24
+ git clone https://github.com/udondan/amazon-subscriptions
25
+ cd amazon-subscriptions
26
+ uv sync
27
+ ```
28
+
29
+ All commands below are run in this directory.
30
+
31
+ ### 2. Log in
32
+
33
+ ```sh
34
+ uv run amazon-subscriptions --domain amazon.co.uk login
35
+ ```
36
+
37
+ This asks for your e-mail address, your password and, if enabled, a one-time password. They are sent to Amazon only
38
+ and never stored. Only the session cookies are kept, in `~/.config/amazonorders/cookies.amazon.co.uk.json`, so the
39
+ next commands do not need to log in. If you already have a session for this site, `login` signs out of it first.
40
+
41
+ Check that it worked:
42
+
43
+ ```sh
44
+ uv run amazon-subscriptions --domain amazon.co.uk check-session
45
+ ```
46
+
47
+ ### 3. Capture the pages
48
+
49
+ ```sh
50
+ uv run amazon-subscriptions --domain amazon.co.uk --debug --output-dir raw list --with-prices
51
+ ```
52
+
53
+ This only reads pages, it never changes a subscription. It writes every page it loads into the new directory `raw`:
54
+ the Subscribe & Save overview, its further pages, one page per upcoming delivery and the product page of each
55
+ subscription. Together, these are all pages that `list` and `upcoming` read, so you do not need to run `upcoming` as
56
+ well. The requests are logged to the terminal.
57
+
58
+ If the command fails or prints warnings, keep its output: it tells what is not understood yet (step 7). If it ends
59
+ with an error before writing any page, open an issue with the error message instead.
60
+
61
+ Always capture into a new, empty directory. If you capture again, delete `raw` first (`rm -rf raw`).
62
+
63
+ ### 4. Anonymize the pages
64
+
65
+ ```sh
66
+ uv run python scripts/anonymize_fixtures.py --from-debug-dir raw --output-dir tests/fixtures/co.uk \
67
+ --replace "Jane Doe=Test Person" --sensitive "Jane" --sensitive "Doe" --sensitive "High Street"
68
+ ```
69
+
70
+ Replace `Jane Doe` with your name as Amazon shows it, and add a `--sensitive` for each part of your name and address
71
+ that must not appear in the result. Each option can be given several times.
72
+
73
+ The script sorts the pages by their content and writes `landing.html`, `delivery-1.html`, `delivery-2.html` and so
74
+ on, and, if the lists have further pages, `subscriptions-page-2.html` and `deliveries-page-2.html` to
75
+ `tests/fixtures/co.uk/`. It replaces:
76
+
77
+ - product titles with `Product 01`, `Product 02` and so on (followed by filler words), also in links,
78
+ - ASINs, subscription IDs, delivery IDs and session IDs with fake values,
79
+ - addresses, tokens and the values of your session cookies,
80
+ - every text given with `--replace`.
81
+
82
+ It does not replace free text, such as your name in a greeting or a message, and nothing the page shows in an
83
+ unknown place. Prices, quantities, intervals and dates are kept.
84
+
85
+ Afterwards, it checks every page for the sensitive values it has seen. If one is left, it prints `LEAK` and exits
86
+ with status 1. It also prints a `WARN` line for text that looks like personal data: e-mail addresses, phone numbers,
87
+ order and session numbers, customer IDs, names after "Hello" or "Deliver to", postcodes and product names in links.
88
+ The value itself is masked. Look at each of them; if it is personal, add a `--replace` or `--sensitive` for it, delete
89
+ `tests/fixtures/co.uk` and run the command again.
90
+
91
+ At the end, it prints a command for the product pages, like this:
92
+
93
+ ```text
94
+ python scripts/extract_product_fixture.py --output-dir tests/fixtures/co.uk --asin-map raw/asin-map.json \
95
+ raw/B0XXXXXXXX_0.html raw/B0YYYYYYYY_0.html …
96
+ ```
97
+
98
+ Run it with `uv run` in front:
99
+
100
+ ```sh
101
+ uv run python scripts/extract_product_fixture.py --output-dir tests/fixtures/co.uk --asin-map raw/asin-map.json \
102
+ raw/B0XXXXXXXX_0.html raw/B0YYYYYYYY_0.html …
103
+ ```
104
+
105
+ It reduces each product page to its title and price blocks, replaces titles, ASINs, seller names and IDs, and
106
+ writes it as `product-<fake ASIN>.html`. For each page, it prints what the parser reads from it:
107
+
108
+ ```text
109
+ ok tests/fixtures/co.uk/product-B0TEST0024.html: price 8.69, list price 11.99 (rrp, from buybox), unit price 57.93 per l
110
+ ok tests/fixtures/co.uk/product-B0TEST0046.html: no price
111
+ ```
112
+
113
+ Keep about five pages that show different things, e.g. a recommended retail price (`rrp`), a former price (`was`),
114
+ a product without price (not available), one with an alternative offer, and one with a plain price. Delete the
115
+ others. You may rename the ones you keep to say what they show, e.g. `product-rrp.html`.
116
+
117
+ ### 5. Check the pages yourself
118
+
119
+ The scripts only replace what they know about. Search the anonymized pages for your own data, with every value you
120
+ can think of: your first and last name, street, postcode, town, e-mail address and phone number.
121
+
122
+ ```sh
123
+ grep -rniE "jane|doe|high street|SW1A 1AA|jane@example.com|7700 900123" tests/fixtures/co.uk/
124
+ ```
125
+
126
+ Search for order numbers too; `000-0000000-0000000` is the placeholder of the scripts. This must find nothing:
127
+
128
+ ```sh
129
+ grep -rnoE "[0-9]{3}-[0-9]{7}-[0-9]{7}" tests/fixtures/co.uk/ | grep -v 000-0000000-0000000
130
+ ```
131
+
132
+ Then run the scan of step 4 once more on all pages, including the product pages:
133
+
134
+ ```sh
135
+ uv run python scripts/privacy_scan.py tests/fixtures/co.uk/*.html
136
+ ```
137
+
138
+ It ends with "Nothing found" or lists `WARN` lines as in step 4. If something personal is left, add a `--replace` or
139
+ `--sensitive` for it in step 4, delete `tests/fixtures/co.uk` and run steps 4 and 5 again. For a product page, you
140
+ can also delete it.
141
+
142
+ ### 6. Share the pages
143
+
144
+ Either way, describe in a few words what your subscriptions look like (e.g. "one paused subscription, one with a
145
+ backup product") and include the output and warnings of step 3, after checking them for personal data as well.
146
+
147
+ - **Pull request**: [Fork the repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo),
148
+ commit only the directory `tests/fixtures/co.uk/` and open a pull request. Its title follows
149
+ [Conventional Commits](https://www.conventionalcommits.org/), e.g. `test: add pages of amazon.co.uk`.
150
+ - **Issue**: If you do not want to open a pull request, pack the directory (`zip -r co.uk.zip tests/fixtures/co.uk`)
151
+ and attach the archive to a [new issue](https://github.com/udondan/amazon-subscriptions/issues/new).
152
+
153
+ ### 7. Unknown texts, formats and markup
154
+
155
+ If step 3 printed warnings or the result misses values, the site shows something that is not known yet. You do not
156
+ need to fix it yourself; the pages from step 6 are enough to do it. If you want to, this is where it goes:
157
+
158
+ - **Texts** (e.g. "Next delivery:", "Arriving", "RRP", status and alert texts) and **date and amount formats**:
159
+ `amazon_subscriptions/locales/en.py`. The `TODO` comments there name the texts that are not known yet for any site.
160
+ Only take texts from real pages, never guess them.
161
+ - **Currency**: `SITE_CURRENCIES` in `amazon_subscriptions/locales/__init__.py`.
162
+ - **Markup**: the CSS selectors in `amazon_subscriptions/parse/selectors.py`.
163
+ - **URLs**: the allow-list in `amazon_subscriptions/client.py`. Only add pages that are read; never add a URL that
164
+ changes a subscription.
165
+
166
+ Add tests in `tests/` that read your fixtures, and run them with:
167
+
168
+ ```sh
169
+ uv run ruff check .
170
+ uv run ruff format --check .
171
+ uv run mypy
172
+ uv run pytest
173
+ ```
174
+
175
+ (With [mise](https://mise.jdx.dev/), `mise run lint` and `mise run test` do the same.)
176
+
177
+ ### 8. Clean up
178
+
179
+ Delete the raw pages, and the pages that could not be read, if any were saved:
180
+
181
+ ```sh
182
+ rm -rf raw ~/.cache/amazon-subscriptions/failed
183
+ ```
184
+
185
+ To end the session as well:
186
+
187
+ ```sh
188
+ uv run amazon-subscriptions --domain amazon.co.uk logout
189
+ ```
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Schroeder
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.