diff --git a/backend/app/services/subscribestar_client.py b/backend/app/services/subscribestar_client.py
index e247c92..abf93c5 100644
--- a/backend/app/services/subscribestar_client.py
+++ b/backend/app/services/subscribestar_client.py
@@ -87,12 +87,15 @@ _GDL_HEADERS = {
_POST_OPEN = '
str:
+ """Substring between the first `start` and the next `end` after it (gallery-
+ dl's `text.extr`); '' when either marker is absent."""
+ i = text.find(start)
+ if i < 0:
+ return ""
+ i += len(start)
+ j = text.find(end, i)
+ if j < 0:
+ return ""
+ return text[i:j]
+
+
+def _extract_content(chunk: str) -> str:
+ """The post body HTML — gallery-dl's `_data_from_post` content rule: between
+ the post_content-text wrapper and the youtube-uploads div, with the trix
+ editor's `…` wrapper stripped to its inner."""
+ content = _extr(chunk, _CONTENT_OPEN, _CONTENT_CLOSE)
+ if "" in content:
+ content = _extr(content, "", "")
+ return content.strip()
+
+
def _split_creator_url(campaign_id: str) -> tuple[str, str]:
"""`campaign_id` is the creator URL → (base, slug).
@@ -316,8 +342,7 @@ class SubscribeStarClient:
post_id = m.group(1)
date_m = _POST_DATE_RE.search(chunk)
published = _parse_ss_datetime(date_m.group(1)) if date_m else None
- body_m = _BODY_RE.search(chunk)
- content = body_m.group(1).strip() if body_m else ""
+ content = _extract_content(chunk)
return {
"id": post_id,
"attributes": {
@@ -368,6 +393,12 @@ class SubscribeStarClient:
rel = it.get("url")
if not isinstance(rel, str) or not rel:
continue
+ # gallery-dl's _media_from_post: a gallery item whose URL is under
+ # /previews is a locked/blurred TEASER, not the real file — skip it
+ # (the SubscribeStar analog of the Patreon gated-preview bug #874).
+ # This is why a locked post yields no downloadable media.
+ if "/previews" in rel:
+ continue
url = urljoin(base + "/", rel)
media_id = str(it.get("id") or "")
name = it.get("original_filename")
diff --git a/tests/test_subscribestar_native.py b/tests/test_subscribestar_native.py
index be9742f..7e21f87 100644
--- a/tests/test_subscribestar_native.py
+++ b/tests/test_subscribestar_native.py
@@ -53,7 +53,12 @@ def _post_html(post_id="111", date="May 01, 2026 12:00 pm", body="
hello
",
f'
{date}
'
f'
'
+ f'
{body}
'
+ # The youtube-uploads div always follows post-content in real markup; it
+ # is the close marker our content extraction (and gallery-dl's) keys on.
+ f'