feat(downloads): native Patreon verify + uniform backend dispatch (plan #697)
The credential Verify button still ran gallery-dl --simulate for Patreon after the cutover — testing the wrong path (and prone to the vanity "Failed to extract campaign ID" the native resolver fixes). Wire it to the native ingester, behind a DRY dispatch so callers never branch on platform. - services/download_backends.py (new): the ONE place that knows which platforms are native vs gallery-dl. `uses_native_ingester(platform)` is the shared predicate; `verify_source_credential(...)` is the uniform probe (same (ok|None, message) contract for both backends). As a platform migrates, it moves into NATIVE_INGESTER_PLATFORMS here and BOTH download routing and verify switch together. - PatreonClient.verify_auth(campaign_id): one authenticated /api/posts fetch → True (valid) / False (401/403/HTML-login) / None (drift or network — inconclusive, not a credential verdict). - patreon_ingester.verify_patreon_credential(): resolve campaign id, then verify_auth — the verify counterpart to the download path. - patreon_resolver.resolve_campaign_id_for_source(): extracted the override / id:-URL / vanity resolution into ONE helper now shared by the download ingester and verify (download_service no longer carries its own copy + regex; −`import re`). - download_service: routes on uses_native_ingester() instead of inline `== "patreon"` (3 sites); uses the shared resolver. - api/credentials: calls verify_source_credential — no platform branch. Tests: verify_auth mapping, resolve_campaign_id_for_source (override/id:/ vanity/none), the dispatch predicate, verify_patreon_credential glue, credentials endpoint proves Patreon uses the native path (gallery-dl verify asserted not-called); repointed the gallery-dl verify test to subscribestar. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,7 @@ FC runs on a plain-HTTP homelab; nothing here uses a secure-context Web API.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
@@ -56,6 +57,7 @@ from .patreon_client import (
|
||||
PatreonDriftError,
|
||||
)
|
||||
from .patreon_downloader import PatreonDownloader
|
||||
from .patreon_resolver import resolve_campaign_id_for_source
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -377,3 +379,26 @@ class PatreonIngester:
|
||||
)
|
||||
session.execute(stmt)
|
||||
session.commit()
|
||||
|
||||
|
||||
async def verify_patreon_credential(
|
||||
url: str,
|
||||
cookies_path: str | None,
|
||||
overrides: dict | None,
|
||||
) -> tuple[bool | None, str]:
|
||||
"""Native Patreon credential probe — the verify counterpart to the ingester's
|
||||
download path, sharing its campaign-id resolution. Resolves the campaign id
|
||||
(override / id: URL / vanity) then does ONE authenticated `/api/posts` fetch
|
||||
via PatreonClient.verify_auth. Returns the uniform `(ok, message)` contract
|
||||
(True / False / None) so download_backends.verify_credential can treat it
|
||||
interchangeably with the gallery-dl probe. No download, no DB.
|
||||
"""
|
||||
campaign_id, _ = await resolve_campaign_id_for_source(url, cookies_path, overrides)
|
||||
if not campaign_id:
|
||||
return None, (
|
||||
"Couldn't resolve the Patreon campaign id from the source URL — "
|
||||
"can't verify (cookies expired, or the creator moved/renamed?)."
|
||||
)
|
||||
client = PatreonClient(cookies_path)
|
||||
loop = asyncio.get_running_loop()
|
||||
return await loop.run_in_executor(None, client.verify_auth, campaign_id)
|
||||
|
||||
Reference in New Issue
Block a user