3.4 KiB
3.4 KiB
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:
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.EntryBeforeDisplay(refreshThread) — fires on every web render. Checks the file cache for staleness; if stale, re-fetches and callsFreshRSS_Factory::createEntryDao()->updateEntry($entry)to write the refreshed HTML back to the DB, keeping API clients up to date.- 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). - 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. - Handle → DID:
GET https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle={handle}(skipped if the handle is already adid:URI). - 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 - Rendering:
renderThread()walks the recursivethreadViewPoststructure. 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
$typevalues likeapp.bsky.embed.images#view; the#viewsuffix is stripped before the switch statement. - User config —
depth(int, 1–1000, default 10) is stored viasetUserConfigurationValue/getUserConfigurationValueand read inhandleConfigureAction()on POST. - No auth required — all requests go to
public.api.bsky.appand need no credentials.
Bluesky API reference
- Thread endpoint:
app.bsky.feed.getPostThread— params:uri(AT-URI),depth(0–1000),parentHeight(0–1000) - Handle resolution:
com.atproto.identity.resolveHandle— param:handle - Docs: https://docs.bsky.app/docs/api/app-bsky-feed-get-post-thread
FreshRSS extension docs
https://freshrss.github.io/FreshRSS/en/developers/03_Backend/05_Extensions.html