fix(patreon): full-fetch fallback when sparse fieldset returns null content (#842)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 25s
CI / backend-lint-and-test (push) Successful in 1m0s
CI / integration (push) Successful in 3m27s

Operator-flagged: 9 StickySpoodge posts had empty bodies in FC despite the body
plainly existing + being accessible (creds refresh didn't help). All 9 are
body-only / poll / embed / announcement posts with no downloadable gallery
media — Patreon's detail endpoint returns content:null for these under the
sparse fields[post]=content request even though the body exists.

fetch_post_detail_content now re-fetches the FULL post resource once when the
sparse request comes back empty: recovers the body when the sparse fieldset was
the cause, and logs post_type when even the full resource is empty (body lives
elsewhere). Only the empty cases pay the extra GET; the 126 already-working
posts keep the fast sparse path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-14 22:26:56 -04:00
parent b999480db5
commit 3df191e255
2 changed files with 54 additions and 5 deletions
+32 -5
View File
@@ -612,12 +612,39 @@ class PatreonClient:
if isinstance(content, str) and content.strip():
log.info("post-detail: fetched %d chars (post %s)", len(content), post_id)
return content
# 200 OK but content is null/empty — the common silent case (a tier-gated
# post serves content:null, or the post genuinely has no text body). Logged
# so a recapture that "caught nothing" is diagnosable, not invisible.
# Sparse `fields[post]=content` came back null/empty. Patreon serves null
# under the sparse fieldset for some post types (polls, embeds/films,
# body-only announcements) even when the body plainly exists. Re-fetch the
# FULL post resource ONCE — only the empty cases pay this extra GET — which
# both DIAGNOSES (logs post_type) and RECOVERS the body when the sparse
# fieldset was the cause (#842).
try:
full = self._session.get(
url, params={"json-api-version": "1.0"}, timeout=_TIMEOUT_SECONDS,
)
except requests.RequestException as exc:
log.warning("post-detail full re-fetch failed (post %s): %s", post_id, exc)
return None
fa = None
if full.status_code == 200:
try:
fp = full.json()
except ValueError:
fp = None
fdata = fp.get("data") if isinstance(fp, dict) else None
fa = fdata.get("attributes") if isinstance(fdata, dict) else None
fcontent = fa.get("content") if isinstance(fa, dict) else None
ptype = fa.get("post_type") if isinstance(fa, dict) else None
if isinstance(fcontent, str) and fcontent.strip():
log.warning(
"post-detail: sparse fieldset gave null but FULL fetch has %d chars "
"(post %s, post_type=%s) — using full body",
len(fcontent), post_id, ptype,
)
return fcontent
log.info(
"post-detail: empty/null content (post %s) — body unavailable "
"(tier-gated, or post has no text)", post_id,
"post-detail: empty/null content (post %s, post_type=%s) even on full "
"fetch — body not in the post resource", post_id, ptype,
)
return None