Files
2026-03-25 20:50:17 -03:00

3.4 KiB
Raw Permalink Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project

A FreshRSS extension that enriches Bluesky posts in RSS feeds by fetching the full reply thread and embedded post content via the Bluesky public API, collapsing everything into a single article.

Extension structure

FreshRSS expects the extension directory name to start with x. Required files:

File Purpose
metadata.json Extension metadata (name, entrypoint are required)
extension.php Main class — must be named {entrypoint}Extension extends Minz_Extension
configure.phtml Optional settings form; submitted values handled by handleConfigureAction()

The extension is installed by dropping this directory into FreshRSS's extensions/ folder.

How it works

Two hooks work in tandem so both the web UI and API sync clients (Fever, GReader, etc.) receive enriched content:

  1. EntryBeforeInsert (fetchThread) — fires once when a new entry is first saved to the DB. Fetches the thread immediately and stores it, so API clients get enriched content from the very first sync.
  2. EntryBeforeDisplay (refreshThread) — fires on every web render. Checks the file cache for staleness; if stale, re-fetches and calls FreshRSS_Factory::createEntryDao()->updateEntry($entry) to write the refreshed HTML back to the DB, keeping API clients up to date.
  3. Staleness rules (needsRefetch): posts ≥ 7 days old are frozen. Fresher posts use progressively tighter refresh windows — 10 min (< 1 h old), 1 h (< 24 h old), 12 h (< 7 d old).
  4. Cache: JSON files in DATA_PATH/BlueskyThreads/{md5(url)}.json, each containing {html, fetched_at}. On API failure, the stale cache is served as a fallback.
  5. Handle → DID: GET https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle={handle} (skipped if the handle is already a did: URI).
  6. Thread fetch: GET https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=at://{did}/app.bsky.feed.post/{rkey}&depth={depth}&parentHeight=0
  7. Rendering: renderThread() walks the recursive threadViewPost structure. Root post uses a bordered card style; replies are indented under a left border. Each post renders its richtext facets (links, mentions, hashtags) and embeds (images, external links, quoted posts, video).

Key implementation details

  • Facets — Bluesky richtext uses UTF-8 byte offsets (byteStart/byteEnd). applyFacets() walks the raw PHP byte string directly rather than converting to characters first.
  • Embed type normalisation — the API returns $type values like app.bsky.embed.images#view; the #view suffix is stripped before the switch statement.
  • User configdepth (int, 11000, default 10) is stored via setUserConfigurationValue/getUserConfigurationValue and read in handleConfigureAction() on POST.
  • No auth required — all requests go to public.api.bsky.app and need no credentials.

Bluesky API reference

FreshRSS extension docs

https://freshrss.github.io/FreshRSS/en/developers/03_Backend/05_Extensions.html