fix(subscribestar): mirror gallery-dl's full request profile (verify_subscriber gate)
After the delimiter fix, the live cheunart fetch 302'd to /cheunart/verify_ subscriber even with valid .adult cookies (confirmed present: _personalization_id + _subscribestar_session on .subscribestar.adult, logged in). Walking gallery-dl's ENTIRE flow — including the base Extractor._init_session I'd not read — the divergence is the HTTP request profile, not the cookies or parser. gallery-dl's default (cookies-only) mode sends, on EVERY request including the first creator-page GET: a Firefox UA, Accept: */*, Accept-Language, and a same- site Referer (root/), with NO X-Requested-With anywhere (the load-more endpoint is a plain GET parsed as JSON). Our Chrome UA + missing Referer + XHR toggling looked unlike a browser → SubscribeStar gated the adult-creator page. Make our SubscribeStar session identical: Firefox UA + Accept */* + Accept- Language via make_session extra_headers; stamp Referer=<base>/ per walk; drop the per-request XHR headers (both feed and load-more now use the shared profile). Test updated to assert the gallery-dl-parity profile instead of the old navigation-vs-XHR split. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -54,15 +54,22 @@ from .native_ingest_common import (
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
_TIMEOUT_SECONDS = 30.0
|
||||
# The INITIAL creator-page GET is a browser-like NAVIGATION (Accept: html, NO
|
||||
# X-Requested-With). SubscribeStar is Rails: an XHR-flagged request to the full
|
||||
# page content-negotiates to a non-HTML response with no posts_container — which
|
||||
# tripped the drift guard on the first live run (cheunart, 2026-06-17). The
|
||||
# "load more" endpoint IS a real XHR and gets these headers per-request.
|
||||
_NAV_ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
|
||||
_LOADMORE_HEADERS = {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"Accept": "application/json, text/javascript, */*; q=0.01",
|
||||
# Match gallery-dl's default (cookies-only) request profile EXACTLY — the proven
|
||||
# way to read SubscribeStar without tripping its /verify_subscriber gate. In that
|
||||
# mode gallery-dl's base Extractor._init_session sends a Firefox UA, Accept: */*,
|
||||
# Accept-Language, and a same-site Referer (root/) on EVERY request — including
|
||||
# the first creator-page GET — and uses NO X-Requested-With on either the creator
|
||||
# page or the "load more" JSON endpoint (it GETs and parses the body as JSON).
|
||||
# Our prior Chrome UA + missing Referer + XHR toggling looked enough unlike a
|
||||
# browser that SubscribeStar 302'd the adult-creator page to /<slug>/verify_
|
||||
# subscriber even with valid cookies (cheunart, 2026-06-17).
|
||||
_FIREFOX_UA = (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) "
|
||||
"Gecko/20100101 Firefox/140.0"
|
||||
)
|
||||
_GDL_HEADERS = {
|
||||
"User-Agent": _FIREFOX_UA,
|
||||
"Accept-Language": "en-US,en;q=0.5",
|
||||
}
|
||||
|
||||
# A post block opens with this wrapper; we slice the page between consecutive
|
||||
@@ -196,9 +203,11 @@ class SubscribeStarClient:
|
||||
max_retries: int = _MAX_429_RETRIES,
|
||||
):
|
||||
self.cookies_path = str(cookies_path) if cookies_path else None
|
||||
# Browser-like navigation defaults; the load-more XHR headers are applied
|
||||
# per-request (NOT session-wide) so the initial page GET isn't flagged XHR.
|
||||
self._session = make_session(cookies_path, accept=_NAV_ACCEPT)
|
||||
# gallery-dl-parity request profile (Firefox UA, Accept: */*, Accept-
|
||||
# Language; Referer is stamped per-walk once the creator base is known).
|
||||
self._session = make_session(
|
||||
cookies_path, accept="*/*", extra_headers=_GDL_HEADERS
|
||||
)
|
||||
self._request_sleep = request_sleep or 0.0
|
||||
self._max_retries = max_retries
|
||||
|
||||
@@ -272,8 +281,9 @@ class SubscribeStarClient:
|
||||
return text
|
||||
|
||||
def _loadmore_html(self, url: str) -> str:
|
||||
"""Subsequent pages: a real XHR GET that returns JSON {"html": "..."}."""
|
||||
resp = self._get(url, headers=_LOADMORE_HEADERS)
|
||||
"""Subsequent pages: a plain GET (gallery-dl uses no XHR header) to the
|
||||
`/posts?...` endpoint, which returns JSON {"html": "..."}."""
|
||||
resp = self._get(url)
|
||||
try:
|
||||
payload = resp.json()
|
||||
except ValueError as exc:
|
||||
@@ -427,6 +437,8 @@ class SubscribeStarClient:
|
||||
raise SubscribeStarDriftError(
|
||||
f"Could not extract a creator slug from {campaign_id!r}"
|
||||
)
|
||||
# Same-site Referer (gallery-dl sends root/ on every request).
|
||||
self._session.headers["Referer"] = f"{base}/"
|
||||
current = cursor
|
||||
first_page = True
|
||||
while True:
|
||||
@@ -469,6 +481,7 @@ class SubscribeStarClient:
|
||||
base, slug = _split_creator_url(campaign_id)
|
||||
if not slug:
|
||||
return None, f"Couldn't parse a SubscribeStar creator from {campaign_id!r}"
|
||||
self._session.headers["Referer"] = f"{base}/"
|
||||
try:
|
||||
html = self._feed_html(f"{base}/{slug}")
|
||||
except SubscribeStarAuthError as exc:
|
||||
|
||||
Reference in New Issue
Block a user