Mastodon RSS: image alt-text replaces the real post text instead of appearing alongside it
Summary
For Mastodon RSS feeds (https://<instance>/@<user>.rss), when a toot has an image, NewsBlur shows the image's alt-text as the story body instead of the toot's actual text. The real caption is silently dropped; the image is still shown, appended after the (wrong) alt-text.
Steps to reproduce
- Subscribe to any Mastodon account's RSS feed.
- Open a toot that has a short caption and one image with alt-text.
Expected: the toot's own text, followed by the image. Actual: the image's alt-text, followed by the image.
Example shape
<item>
<description><p>Good morning!</p></description>
<media:content url="https://example.invalid/photo.jpg" type="image/jpeg" medium="image">
<media:description type="plain">A long descriptive sentence about what's in the photo, written for screen readers.</media:description>
</media:content>
</item>Real toot text: "Good morning!" — what NewsBlur shows instead: the whole alt-text sentence, then the image. This reproduces on essentially any image toot where the caption is shorter than the alt-text, which is the common case.
Root cause
utils/story_functions.py, pre_process_story() (~lines 272–292):
summary = entry.get("summary") or ""
content = ""
...
if entry.get("content"):
html_items = [c for c in entry["content"] if c.get("type") == "text/html"]
chosen = (
max(html_items, key=lambda c: len(c.get("value", "") or ""))
if html_items
else max(entry["content"], key=lambda c: len(c.get("value", "") or ""))
)
content = chosen.get("value", "") or ""
if len(content) > len(summary):
entry["story_content"] = content.strip()
else:
entry["story_content"] = summary.strip()feedparser folds a Media RSS <media:description> into entry["content"] as a separate text/plain item (already covered by an existing test for a different feed shape — apps/rss_feeds/test_rss_feeds.py, Test_PreProcessStoryContentSelection.test_picks_html_content_over_plain_media_description, which asserts entry.get("content") has 2 items when a feed has both content:encoded and media:description).
That test's fix correctly prefers text/html (the real content:encoded body) over plain-text media:description when both exist. But Mastodon toots have no content:encoded — the real text lives only in <description> → entry.summary. So entry["content"] contains exactly one item: the media:description alt-text. With no html_items, the code falls back to max(entry["content"], ...), picks that alt-text item, and since it's almost always longer than a short/empty caption, it overwrites entry.summary as the story body.
Suggested fix direction
Keep the text/html-preference branch (needed for the case the existing test covers). Fix the fallback: don't let a lone text/plain entry.content item override a non-empty entry.summary purely by length.
- Only fall back to the longest
entry.contentitem whensummaryitself is empty. - Better: positively identify content items that correspond 1:1 to an
entry.media_content[i]item (i.e. came frommedia:description) and exclude those from the content-vs-summary comparison — they're image metadata, not article body. This also fixes multi-image toots, where today the lastmedia:descriptionarbitrarily wins. - If the alt-text should stay visible for accessibility, append it as distinguishable markup (e.g.
<figcaption>) next to the<img>insertion the code already does further down, instead of substituting it for the real body.
Relevant code
utils/story_functions.py:259-352(pre_process_story)apps/rss_feeds/test_rss_feeds.py:1606-1686(Test_PreProcessStoryContentSelection) — a new case for "nocontent:encoded, singlemedia:description" belongs next to it.
Source: samuelclay/NewsBlur