A bot that reads from https://storiedcolors.com/ and posts to daily@color.goodstuff.im
  • Rust 95.6%
  • Dockerfile 2.6%
  • Shell 1.8%
Find a file
David Pollak fda9594a1b
Updated to removed Docker compose
Signed-off-by: David Pollak <feeder.of.the.bears@gmail.com>
2026-06-21 11:12:47 -04:00
docs Updated to removed Docker compose 2026-06-21 11:12:47 -04:00
scripts Updated to removed Docker compose 2026-06-21 11:12:47 -04:00
specs Updated to removed Docker compose 2026-06-21 11:12:47 -04:00
src Initial commit 2026-06-20 14:39:00 -04:00
tests Initial commit 2026-06-20 14:39:00 -04:00
.env.example Updated to removed Docker compose 2026-06-21 11:12:47 -04:00
.gitignore Initial commit 2026-06-20 14:39:00 -04:00
Cargo.lock Initial commit 2026-06-20 14:39:00 -04:00
Cargo.toml Initial commit 2026-06-20 14:39:00 -04:00
Dockerfile Initial commit 2026-06-20 14:39:00 -04:00
README.md Updated to removed Docker compose 2026-06-21 11:12:47 -04:00

storied-colors-bot

A small daemon that watches the Storied Colors homepage for the daily "Read today's entry" link and posts it to a Mastodon account when it changes.

What this does

Once a day, Storied Colors features a new color on its homepage under "Today's Color," with a "Read today's entry" link to that color's full page. This bot watches for that link and posts it — and only it — to a configured Mastodon account, once per day, with no duplicate posts across restarts.

The bot runs as a long-lived Docker container. It polls the homepage every 2 hours, compares the current entry URL against the last one it posted, and posts the bare URL if it is new. Mastodon's own link preview generation creates the visual card.

Why this scrapes the homepage instead of using RSS

storiedcolors.com publishes an RSS feed at /rss.xml, but that feed is known to lag behind the live site and miss recent entries. This bot deliberately scrapes the homepage's "Read today's entry" link directly instead, since that's the reliable source of the current day's pick.

Respecting robots.txt

This bot only ever fetches the homepage (/). It never touches /proofs/, /journal/, or /rss.xml. It identifies itself with the User-Agent string in src/scraper.rs — please update the contact URL in that string to your own before deploying, so the site owner has a way to reach you if needed.

How it works

  • Runs as a long-lived process in Docker (docker run --restart unless-stopped).
  • Checks the homepage immediately on startup, then every 2 hours.
  • Compares the current "today's entry" URL against the last one posted, tracked in a JSON state file on a persistent volume.
  • Posts a short prefix plus the entry URL to Mastodon if it is new.
  • Logs structured JSON to stdout; use docker logs -f storied-colors-bot to watch it.
  • Handles SIGTERM/SIGINT gracefully so docker stop does not kill an in-flight request.

Setup

1. Get a Mastodon access token

  1. Log in to your Mastodon instance in a web browser.
  2. Open Preferences → Development → New Application (the exact path varies by instance; look for "Development" or "Applications" under the settings gear).
  3. Fill in the application form:
    • Application name: Storied Colors Bot (or any name you will recognize).
    • Application website: optional; you can leave it blank or use a contact URL.
    • Redirect URI: leave blank, or set it to urn:ietf:wg:oauth:2.0:oob if a value is required.
  4. Under Scopes, enable only write:statuses. The bot does not need read or write:media permissions.
  5. Submit the form, open the newly created application, and copy the Access token.
  6. Paste the token into your .env file as MASTODON_ACCESS_TOKEN=....

Treat the access token like a password. If it leaks, revoke it in the same Development settings page and generate a new one.

2. Configure the bot

cp .env.example .env
# edit .env with your MASTODON_INSTANCE_URL and MASTODON_ACCESS_TOKEN

3. Build and run

# Build the image
docker build -t storied-colors-bot:latest .

# Start the daemon
./scripts/run.sh

Or run the long command directly:

mkdir -p data
docker run -d \
  --name storied-colors-bot \
  --env-file .env \
  -v "$(pwd)/data:/data" \
  --restart unless-stopped \
  storied-colors-bot:latest

4. Verify (without posting)

To preview what the bot would post without touching Mastodon:

# Using cargo
cargo run -- --dry-run

# Or inside the container
./scripts/dry-run.sh

Output will be one of:

would post: Today's featured color from Storied Colors: https://storiedcolors.com/color/<slug>
nothing new: Today's featured color from Storied Colors: https://storiedcolors.com/color/<slug>

Then start the daemon and watch logs:

docker logs -f storied-colors-bot

Configuration reference

Variable Required Default Notes
MASTODON_INSTANCE_URL yes e.g. https://mastodon.social, no trailing slash, HTTPS only
MASTODON_ACCESS_TOKEN yes needs write:statuses scope
STATE_PATH no /data/state.json path to the JSON state file

Status format

Each post uses a consistent prefix plus the entry URL:

Today's featured color from Storied Colors: https://storiedcolors.com/color/confederate-grey

Mastodon's link preview generation handles the visual card.

If the site's markup changes

The entry-extraction logic matches on the link text "Read today's entry." If the site changes that wording or restructures the homepage significantly, the bot will log a warning each cycle instead of posting. Check the logs, update the parser in src/scraper.rs if needed, and add a new fixture to the test suite.

Development

Prerequisites: a recent stable Rust toolchain (the current local target is 1.95) and Docker.

# Run the test suite
cargo test

# Check formatting and lints
cargo fmt --check
cargo clippy -- -D warnings

# Build the Docker image
docker build -t storied-colors-bot:latest .

Non-goals

This bot intentionally does not: use the site's RSS feed, attach images to posts, backfill missed days, scrape any page other than the homepage, or provide a web UI. See docs/IMPLEMENTATION_PLAN.md and docs/DECISIONS.md for the reasoning behind these choices.

Project documentation

  • docs/IMPLEMENTATION_PLAN.md — phased build plan
  • docs/ARCHITECTURE.md — component and data-flow design
  • docs/TESTING.md — test strategy and red-to-green workflow
  • docs/DECISIONS.md — architecture decision records
  • docs/GAP_ANALYSIS.md — adversarial review of the implementation
  • docs/ACCEPTANCE.md — acceptance-criteria verification log
  • docs/REMOTE_DEPLOYMENT.md — deploying the built container to a server without Rust/Cargo