Go to file
nak0x 3f71d324c9 Add search, feeds and listing views to the reader server
Extraction looks for prose, so a front page, a comment thread or a search
result page correctly yields almost nothing. These render the structure
instead, and every link they emit routes back through /read.

  /search?q=   results from a configurable HTML endpoint
  /feed?u=     RSS and Atom
  /read?u=     now picks a site view, falling back to a link index when a
               page has too little text to be an article

Feeds are scanned rather than parsed. A feed needs five fields per entry
and an HTML parser mangles XML, so this walks the tags directly: CDATA,
named and numeric entities, and Atom's preference for rel=alternate over
rel=self. No XML dependency.

Hacker News gets a real adapter: stories with score, author and a link
into the discussion, and comment threads rendered with their indent
preserved. Comment bodies go through the article renderer so links inside
them behave like every other link.

Search is deliberately engine-agnostic. Known result shapes are tried
first, then heading links, then any link, because every free HTML endpoint
eventually rate-limits a repeat visitor. When one answers with a challenge
page rather than results the reader says so and points at the config,
instead of showing an empty page; detection reads the body, since these
arrive as 200 or 202 rather than an error status. A blocked search is
never cached.

Pages that turn out to be feeds redirect to the feed view, and a page that
declares its own feed offers it.
2026-09-06 20:07:49 +02:00
furst-read Add search, feeds and listing views to the reader server 2026-09-06 20:07:49 +02:00
furst-serve Add search, feeds and listing views to the reader server 2026-09-06 20:07:49 +02:00
src Add furst, a URL router for low-end hardware 2026-09-06 19:18:13 +02:00
.gitignore Add furst, a URL router for low-end hardware 2026-09-06 19:18:13 +02:00
Cargo.lock Add furst-serve, a local reader server 2026-09-06 19:55:59 +02:00
Cargo.toml Add furst-serve, a local reader server 2026-09-06 19:55:59 +02:00
LICENSE Add furst, a URL router for low-end hardware 2026-09-06 19:18:13 +02:00
README.md Add furst-read, an article extractor and minimal renderer 2026-09-06 19:35:30 +02:00

furst

A URL router. It sits where your default browser used to, and sends each URL to the cheapest tool that can actually handle it — mpv for video, a pager for PDFs, a light WebKit browser for reading, and Firefox only when nothing else will do.

Built for a Core2 Duo with 4GB of RAM, where the browser is the problem.

Why

A news page is 25MB across 80+ requests with 13MB of JavaScript to parse and JIT. The same article extracted is ~20KB. Choosing a lighter engine buys 23×; not loading the payload at all buys 10100×. furst is the dispatcher that decides which of those you get, per URL.

Install

cargo build --release
install -Dm755 target/release/furst ~/.local/bin/furst

furst --init      # writes ~/.config/furst/rules.toml, probes for a light browser
furst --install   # registers furst as the system default browser

--install writes ~/.local/share/applications/furst.desktop and points xdg-settings at it, so every link click in every application routes here.

Use

furst <url>              # match a rule and exec its command
furst --explain <url>    # show what would run, and why; run nothing
furst --list             # show the loaded rules

--explain is the one you want when a URL goes somewhere surprising:

$ furst --explain https://youtu.be/abc123
scheme  https
host    youtu.be
path    /abc123

-> [video] mpv --ytdl-format=bestvideo[vcodec^=avc1][height<=?720]+... https://youtu.be/abc123
   [default] surf https://youtu.be/abc123

Rules

~/.config/furst/rules.toml. First matching rule wins. If its command is missing from $PATH, furst falls through to the next matching rule, and finally to default — which is what lets you name tools you have not written yet and have the config stay working today.

default = ["surf", "{url}"]

[[rule]]
name = "video"
hosts = ["youtube.com", "youtu.be"]
run = ["mpv", "--ytdl-format=bestvideo[vcodec^=avc1][height<=?720]+bestaudio/best", "{url}"]

[[rule]]
name = "hn"
hosts = ["news.ycombinator.com"]
terminal = true                      # wrap in $TERMINAL -e
run = ["furst-hn", "{url}"]

A rule matches when every criterion it states is satisfied; a criterion is satisfied by any one of its patterns. A rule that states nothing matches everything.

Key Matches against
schemes https, mailto, magnet, …
hosts example.com = apex and every subdomain; *.example.com = same; =example.com = that host exactly; * = any
paths path component only, glob with *, case-insensitive
contains substring of the whole raw URL
Placeholder
{url} {url_enc} the URL, raw or percent-encoded
{host} {path} {scheme} parsed components

If no argument mentions {url} or {url_enc}, the URL is appended last.

Notes for old hardware

  • Force H.264 for video. A Core2 handles 720p avc1 in software but stalls on VP9/AV1, which is what YouTube serves by default. That format string is doing more work than the resolution cap.
  • Host matching strips userinfo with rfind('@'), so https://bank.example@evil.example/ routes on evil.example.
  • furst execs the handler rather than forking, so it leaves no process behind.

Companion tools

  • furst-read — fetch a page, extract the article, and render it as minimal HTML with no scripts, stylesheets, or webfonts. The reader rule in the starter config points at it.

License

MIT