Skip to content

Completes MAM API migration and hardens auth - #17

Merged
H2OKing89 merged 5 commits into
mainfrom
refact/mam_api
May 6, 2026
Merged

Completes MAM API migration and hardens auth#17
H2OKing89 merged 5 commits into
mainfrom
refact/mam_api

Conversation

@H2OKing89

@H2OKing89 H2OKing89 commented May 6, 2026

Copy link
Copy Markdown
Owner

Why

  • Removes brittle browser-driven scraping and login behavior in favor of direct API access.
  • Reduces security and maintenance overhead by relying on session-cookie auth instead of local credential templates.
  • Improves failure visibility by treating stale or invalid auth responses as explicit API auth errors.

What it improves

  • Updates the metadata lookup flow to use the API adapter path consistently.
  • Strengthens client-side auth handling for redirects, login-page responses, and invalid payload formats.
  • Extends tests to cover auth-edge cases and optional real download verification.
  • Aligns CI setup, environment guidance, and documentation with the API-first integration model.
  • Cleans up deprecated generated and legacy artifacts and expands ignore rules for sensitive/runtime files.

Summary by CodeRabbit

  • New Features

    • MAM integration migrated to the official JSON API; optional MAM_TEST_TID support for gated integration tests.
    • MAM authentication now uses a MAM_ID environment variable (no local JSON config required).
  • Improvements

    • Richer metadata returned (authors, narrators, series info, position).
    • More consistent authentication/error handling and reduced dependency footprint (no web-scraping runtime).
  • Documentation

    • Updated setup, configuration, troubleshooting, and test instructions to reflect the API-based workflow.

H2OKing89 and others added 2 commits May 5, 2026 21:26
- Removed Playwright dependency and the old MAMScraper implementation.
- Introduced MAMApiAdapter for API-based scraping, providing a cleaner and faster interface.
- Updated MetadataCoordinator to utilize the new adapter.
- Adjusted tests to reflect changes in method names and functionality.
- Updated documentation and logging to align with the new API usage.
- Removed backward compatibility alias for MAMScraper.
…integration tests

Co-authored-by: Copilot <copilot@github.com>
@coderabbitai

coderabbitai Bot commented May 6, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e64091f2-3837-45c6-bf0d-38f8339e2d5e

📥 Commits

Reviewing files that changed from the base of the PR and between 62d3ff0 and a6f7109.

📒 Files selected for processing (14)
  • .pre-commit-config.yaml
  • docs/MAM_API_MIGRATION.md
  • docs/user-guide/troubleshooting.md
  • pyproject.toml
  • src/http_client.py
  • src/logging_setup.py
  • src/main.py
  • src/mam_api/adapter.py
  • src/mam_api/client.py
  • src/metadata_coordinator.py
  • src/qbittorrent.py
  • src/utils.py
  • tests/test_mam_api.py
  • tests/test_metadata_coordinator.py

📝 Walkthrough

Walkthrough

Migration from a Playwright-based MAM scraper to an httpx-based MAM JSON API client/adapter. Config moved from local JSON to env cookie (MAM_ID) with optional MAM_TEST_TID for integration tests. Playwright removed from requirements; client+adapter centralize auth/response validation and update metadata wiring and tests.

Changes

MAM JSON API Migration

Layer / File(s) Summary
Configuration & CI
\.env.example, .gitignore, config/..., .circleci/config.yml, .github/workflows/ci.yml
Added optional MAM_TEST_TID to .env.example; removed config/mam_config.json.example; updated CI steps to copy config/config.yaml.exampleconfig/config.yaml; adjusted .gitignore to add logs/, db.sqlite, *.sqlite-journal, secrets/ and removed negation for old JSON example.
Dependencies / Packaging
requirements.txt, audiobook_dev.egg-info/*
Removed playwright from runtime requirements; cleaned/removed stale egg-info metadata files.
Public surface / Exports
src/mam_api/__init__.py
Removed MAMScraper export from __all__ and removed backward-compat docstring alias (adapter remains available via code-level alias in adapter module).
Client: HTTP / Validation
src/mam_api/client.py
Added constants MAM_LOGIN_PATHS, MAM_AUTH_ERROR_MESSAGE; centralized helpers _url_path_looks_like_login, _raise_for_api_response, _api_response_json, _validated_torrent_content; set httpx clients to follow_redirects=False; download/search flows now validate auth/JSON and raise MamApiError consistently.
Adapter: ASIN / Metadata
src/mam_api/adapter.py
Added get_asin_from_url(url) and richer get_full_metadata (authors, narrators, series, series_position); changed extract_tid_from_url to @staticmethod accepting `str
Wiring: Coordinator
src/metadata_coordinator.py
Rewired to instantiate/use MAMApiAdapter as mam_adapter; replaced scrape_asin_from_url calls with get_asin_from_url and added explicit MamApiError handling path.
Tests
tests/test_mam_api.py, tests/test_metadata_coordinator.py, tests/test_audnex_direct.py
Expanded unit and async tests for client redirect/auth handling, tid extraction, ASIN lookup, full metadata, torrent download validation; added optional integration tests gated by MAM_ID and MAM_TEST_TID; updated mocks/wiring to use mam_adapter API.
Docs & Guides
docs/* (migration, system summary, user-guide, concurrency, troubleshooting, contributing, PR1 fixes)
Extensive docs updated to reflect API migration: new migration plan/phases, config/docs updated to use MAM_ID cookie and pytest integration checks, concurrency guidance shifted to HTTP client/connection-pool concerns, archived Playwright async summary.
Archived/Removed Scraper
src/archive/mam_scraper.py.old, src/mam_scraper.py (archived)
Removed Playwright-based scraper implementation and archived legacy files.
Misc. Small refactors
src/http_client.py, src/logging_setup.py, src/qbittorrent.py, src/utils.py, src/main.py, .pre-commit-config.yaml, pyproject.toml
Minor API/typing/initialization and lint adjustments: simplify default_factory, extract package-version helper, add bandit exclude for tests, tighten qbittorrent validation, add new qBittorrent cookie-backed add_torrent_file_with_cookie and exceptions, small string-split and linter comments.

Sequence Diagram

sequenceDiagram
    autonumber
    actor Webhook
    participant Coord as metadata_coordinator
    participant Adapter as MAMApiAdapter
    participant Client as MamClient (httpx)
    participant MAM as MAM API
    participant Validator as ResponseValidator

    Webhook->>Coord: webhook(url)
    Coord->>Adapter: get_asin_from_url(url)
    Adapter->>Adapter: extract_tid_from_url(url)
    Adapter->>Client: search/download request
    Client->>MAM: HTTP GET /search or /download
    MAM-->>Client: HTTP response (200 / redirect / 401/403 / HTML)
    Client->>Validator: _api_response_json / _raise_for_api_response
    alt Auth redirect or 401/403 detected
        Validator-->>Client: raise MamApiError
        Client-->>Adapter: MamApiError
        Adapter-->>Coord: propagate MamApiError (log + re-raise)
    else Valid JSON / torrent bytes
        Validator-->>Client: parsed JSON or validated bytes
        Client-->>Adapter: parsed data
        Adapter->>Adapter: extract ASIN / metadata enrichment
        Adapter-->>Coord: asin or metadata
        Coord->>Coord: proceed with downstream lookups
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Completes MAM API migration and hardens auth' accurately captures the two primary changes: the completion of replacing browser-based scraping with API-based access, and strengthening authentication handling for edge cases.
Docstring Coverage ✅ Passed Docstring coverage is 89.66% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refact/mam_api

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 62d3ff0259

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/mam_api/client.py
@coderabbitai coderabbitai Bot added the enhancement New feature or request label May 6, 2026
coderabbitai[bot]
coderabbitai Bot previously requested changes May 6, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/mam_api/client.py (1)

249-263: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider validating torrent file content on download.

  • Issue: With follow_redirects=False, a non-login redirect could pass _raise_for_api_response and return unexpected content (error page HTML, etc.) as raw bytes.
  • Impact: Caller receives corrupted data silently. Low probability since login redirects are caught, but non-auth redirects could still occur.
  • Fix: Validate response starts with torrent magic bytes (d8:announce or bencode dict start d).
  • Test: Mock redirect to non-login URL, verify MamApiError raised.
🛡️ Optional validation snippet
def _validate_torrent_content(content: bytes) -> None:
    """Raise if content doesn't look like a .torrent file."""
    if not content.startswith(b"d"):
        raise MamApiError("Downloaded content is not a valid torrent file")

Apply after _raise_for_api_response(r) in download methods:

 _raise_for_api_response(r)
+_validate_torrent_content(r.content)
 log.debug("mam.download.complete", tid=tid, size=len(r.content))

Also applies to: 265-283

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mam_api/client.py` around lines 249 - 263, The download_torrent_by_tid
method currently returns raw bytes from self._client.get which can be
non-torrent HTML if a non-auth redirect occurred; after calling
_raise_for_api_response(r) in download_torrent_by_tid (and the sibling download
method around lines 265-283), validate the returned bytes before returning by
checking torrent/bencode magic bytes (e.g., content.startswith(b"d") or
content.startswith(b"d8:announce")) and raise MamApiError (or similar existing
API error) when the check fails so callers never receive corrupted/non-torrent
data.
src/metadata_coordinator.py (1)

64-75: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Do not demote MAM auth failures into the Audible fallback path.

  • Issue: This block catches everything from the new adapter call and continues the normal fallback flow.
  • Impact: An expired/invalid MAM_ID becomes indistinguishable from “no ASIN found,” which defeats the PR’s auth-hardening goal.
  • Fix: Let the adapter surface a distinct auth error, catch MamApiError explicitly here, and return/raise a controlled auth failure instead of falling through to Audnex/Audible.
  • Test: Add a webhook test where mam_adapter.get_asin_from_url() raises MamApiError and assert that the auth error is surfaced.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/metadata_coordinator.py` around lines 64 - 75, The current try/except
around self.mam_adapter.get_asin_from_url(url) swallows all exceptions and
treats auth failures like "no ASIN"; change it to explicitly catch MamApiError
from the adapter (e.g., except MamApiError as e:) and surface a controlled auth
failure (raise or return an auth-specific exception/result) instead of falling
through to the Audible fallback, while keeping the existing handlers for
httpx.RequestError, ValueError and a generic Exception; also add a unit/webhook
test that simulates mam_adapter.get_asin_from_url() raising MamApiError and
asserts the coordinator surfaces the auth error rather than proceeding to the
Audible path.
src/mam_api/adapter.py (1)

120-121: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Redact the incoming URL before logging extraction failures.

  • Issue: This warning logs the full URL verbatim.
  • Impact: If a caller passes a tokenized MAM link, that token lands in logs.
  • Fix: Log only scheme/host/path, or strip query/fragment before logging.
  • Test: Add a log assertion with a URL containing ?token=... and verify the token never appears.
🛡️ Proposed fix
-        log.warning("mam.adapter.tid_extract_failed", url=url)
+        parsed = urlparse(url)
+        redacted_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
+        log.warning("mam.adapter.tid_extract_failed", url=redacted_url)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mam_api/adapter.py` around lines 120 - 121, The warning currently logs
the full incoming URL (log.warning("mam.adapter.tid_extract_failed", url=url));
change this to sanitize the URL first by parsing it (e.g.,
urllib.parse.urlparse) and reconstructing only scheme, netloc, and path
(omitting query and fragment) or otherwise stripping query/fragment, then pass
that sanitized string to log.warning; update any test to assert that a URL with
?token=... does not appear in logs (e.g., replace the log call in the tid
extraction failure path to use the sanitized URL variable and add a unit test
verifying the token is not present).
tests/test_metadata_coordinator.py (1)

259-381: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add a MamApiError webhook test for the new auth path.

  • Issue: The exception matrix covers network, parse, and generic failures, but not the explicit MAM auth error introduced by this migration.
  • Impact: CI will not catch regressions where stale MAM_ID handling silently falls back instead of surfacing the auth problem.
  • Fix: Add a case where coordinator.mam_adapter.get_asin_from_url raises MamApiError and assert the coordinator handles it distinctly.
  • Test: That case should fail against the current behavior and pass once auth failures stop being treated like normal misses.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_metadata_coordinator.py` around lines 259 - 381, Add an async
pytest that simulates an auth failure by setting
coordinator.mam_adapter.get_asin_from_url =
AsyncMock(side_effect=MamApiError("Auth failed")) and then call
coordinator.get_metadata_from_webhook(sample_webhook_payload) expecting the
MamApiError to be raised (use pytest.raises(MamApiError)); reference
coordinator.get_metadata_from_webhook and
coordinator.mam_adapter.get_asin_from_url so the test verifies auth errors are
surfaced rather than treated as normal misses.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/MAM_API_MIGRATION.md`:
- Around line 190-196: The example command only sets MAM_TEST_TID but the text
states both MAM_ID and MAM_TEST_TID are required; update the runnable example to
set both environment variables (MAM_ID and MAM_TEST_TID) inline in the command
or explicitly state that MAM_ID must already be exported in the shell; modify
the snippet so it shows something like setting MAM_ID and MAM_TEST_TID together
before running pytest (or add a note that MAM_ID must be exported) to ensure the
test is actually executed rather than skipped.

In `@docs/user-guide/troubleshooting.md`:
- Around line 137-155: In the "MAM API Auth Failed" section (heading "MAM API
Auth Failed") the ordered list uses "1." twice; update the second list item (the
step that begins with "Refresh the cookie value:") to use "2." so the steps read
"1." then "2." for correct sequential numbering.

---

Outside diff comments:
In `@src/mam_api/adapter.py`:
- Around line 120-121: The warning currently logs the full incoming URL
(log.warning("mam.adapter.tid_extract_failed", url=url)); change this to
sanitize the URL first by parsing it (e.g., urllib.parse.urlparse) and
reconstructing only scheme, netloc, and path (omitting query and fragment) or
otherwise stripping query/fragment, then pass that sanitized string to
log.warning; update any test to assert that a URL with ?token=... does not
appear in logs (e.g., replace the log call in the tid extraction failure path to
use the sanitized URL variable and add a unit test verifying the token is not
present).

In `@src/mam_api/client.py`:
- Around line 249-263: The download_torrent_by_tid method currently returns raw
bytes from self._client.get which can be non-torrent HTML if a non-auth redirect
occurred; after calling _raise_for_api_response(r) in download_torrent_by_tid
(and the sibling download method around lines 265-283), validate the returned
bytes before returning by checking torrent/bencode magic bytes (e.g.,
content.startswith(b"d") or content.startswith(b"d8:announce")) and raise
MamApiError (or similar existing API error) when the check fails so callers
never receive corrupted/non-torrent data.

In `@src/metadata_coordinator.py`:
- Around line 64-75: The current try/except around
self.mam_adapter.get_asin_from_url(url) swallows all exceptions and treats auth
failures like "no ASIN"; change it to explicitly catch MamApiError from the
adapter (e.g., except MamApiError as e:) and surface a controlled auth failure
(raise or return an auth-specific exception/result) instead of falling through
to the Audible fallback, while keeping the existing handlers for
httpx.RequestError, ValueError and a generic Exception; also add a unit/webhook
test that simulates mam_adapter.get_asin_from_url() raising MamApiError and
asserts the coordinator surfaces the auth error rather than proceeding to the
Audible path.

In `@tests/test_metadata_coordinator.py`:
- Around line 259-381: Add an async pytest that simulates an auth failure by
setting coordinator.mam_adapter.get_asin_from_url =
AsyncMock(side_effect=MamApiError("Auth failed")) and then call
coordinator.get_metadata_from_webhook(sample_webhook_payload) expecting the
MamApiError to be raised (use pytest.raises(MamApiError)); reference
coordinator.get_metadata_from_webhook and
coordinator.mam_adapter.get_asin_from_url so the test verifies auth errors are
surfaced rather than treated as normal misses.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 889fdff6-8e67-45f2-b379-4e2efe15cfc0

📥 Commits

Reviewing files that changed from the base of the PR and between fec2401 and 62d3ff0.

📒 Files selected for processing (27)
  • .circleci/config.yml
  • .env.example
  • .github/workflows/ci.yml
  • .gitignore
  • audiobook_dev.egg-info/PKG-INFO
  • audiobook_dev.egg-info/SOURCES.txt
  • audiobook_dev.egg-info/dependency_links.txt
  • audiobook_dev.egg-info/requires.txt
  • audiobook_dev.egg-info/top_level.txt
  • config/mam_config.json.example
  • docs/CONCURRENCY_ANALYSIS.md
  • docs/MAM_API_MIGRATION.md
  • docs/PR1_REVIEW_FIXES.md
  • docs/SYSTEM_COMPLETION_SUMMARY.md
  • docs/development/PLAYWRIGHT_ASYNC_FIX_SUMMARY.md
  • docs/development/contributing.md
  • docs/user-guide/configuration.md
  • docs/user-guide/troubleshooting.md
  • requirements.txt
  • src/archive/mam_scraper.py.old
  • src/mam_api/__init__.py
  • src/mam_api/adapter.py
  • src/mam_api/client.py
  • src/metadata_coordinator.py
  • tests/test_audnex_direct.py
  • tests/test_mam_api.py
  • tests/test_metadata_coordinator.py
💤 Files with no reviewable changes (11)
  • .github/workflows/ci.yml
  • audiobook_dev.egg-info/PKG-INFO
  • .circleci/config.yml
  • audiobook_dev.egg-info/requires.txt
  • docs/development/PLAYWRIGHT_ASYNC_FIX_SUMMARY.md
  • audiobook_dev.egg-info/SOURCES.txt
  • src/archive/mam_scraper.py.old
  • requirements.txt
  • src/mam_api/init.py
  • config/mam_config.json.example
  • audiobook_dev.egg-info/top_level.txt

Comment thread docs/MAM_API_MIGRATION.md
Comment thread docs/user-guide/troubleshooting.md
H2OKing89 and others added 3 commits May 5, 2026 21:59
…rectories from Bandit analysis, refining version retrieval logic, and optimizing qBittorrent configuration handling.
…rrent content, and updating documentation for cookie usage in tests

Co-authored-by: Copilot <copilot@github.com>
…amApiError test

Co-authored-by: Copilot <copilot@github.com>
@H2OKing89
H2OKing89 merged commit 1d6fa8f into main May 6, 2026
10 checks passed
@H2OKing89
H2OKing89 deleted the refact/mam_api branch May 6, 2026 03:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant