Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

"The Algorithm" — the timeline like it used to be

A personalized Bluesky custom feed that recreates the mid-2010s Twitter home timeline: posts from people you follow ranked by likes and friendship, a "several people you follow just liked this" discovery lane, and zero reward for arguments.

Built from the primary sources: Twitter's 2017 timeline-ranking engineering post and the open-sourced legacy Earlybird ranker (like=30, retweet=20, reply=1). The 2019+ ranker that Threads imitates weights a reply at 13.5–27× a like — that's the dunk machine, and this feed inverts it. See docs/DESIGN.md for the research and full scoring model.

How it ranks

Page 1 opens with a ranked "while you were away" block (~15 posts), then everything else is reverse-chronological (including your follows' reposts):

score = affinity × (1 + engagement / author_baseline) × boosts × ratio_guard × decay
  • Engagement: 30·likes + 20·reposts + 1·replies — likes matter, arguments don't
  • Author baseline: normalized by each author's typical engagement, so a small account's banger beats a celebrity's shrug
  • Affinity: authors you like often (from your own public like history) float up
  • Ratio guard: replies ≥ 10 and > 2× likes → suppressed. Dunks don't spread here
  • Tone: a transparent word-list classifier (src/tone.ts) boosts fun posts ×1.2, dampens charged political content ×0.5, and crushes outrage-affect posts ×0.2 — catching "agreement rage" (outrage that gets liked, not ratio'd) that reply-based guards can't see. Discovery never surfaces negative-tone posts at all
  • Boosts: media ×1.15, self-thread roots ×1.25 (advice threads live!)
  • Decay: 6h half-life over a 48h window; bare replies never appear
  • Never the same feed twice: the ranked block remembers what it showed you (48h) and only surfaces posts you haven't seen — the real "while you were away" mechanic
  • Variety: max 2 block slots per author; out-of-network content capped at 5 of 15 slots (friends first)
  • MagicRecs: out-of-network posts injected when ≥2 people you follow liked the same post within 6h, weighted by how much you like those likers
  • Interest authors: people you don't follow but have liked 2+ times compete for block slots — posts from your interests, from people you haven't met

All knobs live in src/scoring.ts.

Run it

cp .env.example .env   # set FEEDGEN_HOSTNAME
npm install
npm test
npm run dev            # starts ingesting Jetstream + serving on :3000

The service needs a public HTTPS hostname (reverse proxy via Caddy/nginx, or a Cloudflare Tunnel). It serves its own did:web document at /.well-known/did.json.

Publish the feed to your account

BLUESKY_HANDLE=you.bsky.social BLUESKY_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx npm run publish-feed
# then put the printed FEEDGEN_PUBLISHER_DID in .env and restart

Open the printed bsky.app link, pin the feed, done.

Notes

  • First request from a new viewer takes a few seconds (fetching their follows and like history); afterwards it's cached 12h and refreshed in the background.
  • Storage is a rolling 48h SQLite window — single-digit GB, prunes hourly.
  • Discovery (MagicRecs) warms up as the feed learns which accounts are followed by its viewers; expect it to kick in within a few hours of first use.
  • Fits comfortably on a $5–10/mo VPS.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages