bufo-bot #
bluesky bot that listens to the jetstream firehose, replies to image tags, and quote-posts matching bufo phrases.
image tags #
start a post with @find-bufo.com and
attach an image or quote a post containing one. the bot compares the first
available image with the Bufo collection and replies with the closest match.
post records and source blobs are fetched from the author's resolved PDS through
com.atproto.repo.getRecord and
com.atproto.sync.getBlob, not through a Bluesky CDN derivative. attached
images take precedence over quoted images. if neither post has a
readable image, the bot replies with a short explanation instead. tags later
in a post are treated as ordinary mentions and ignored.
opt-in #
the bot is opt-in: it only quote-posts for accounts that follow @find-bufo.com. matches from non-followers are counted in stats but never trigger a post. follow to opt in, unfollow to opt out.
how it works #
- connects to bluesky jetstream (firehose)
- handles direct image tags with visual search and a reply
- otherwise, checks if text contains an exact phrase matching a bufo name
- if matched, checks whether the author follows the bot
- if the author is a follower, quote-posts with the corresponding bufo image
direct image tags are explicit requests and do not require following the bot. phrase matching remains follower-only.
matching logic #
- extracts phrase from bufo filename (e.g.,
bufo-let-them-eat-cake->let them eat cake) - requires exact consecutive word match in post text
- configurable minimum phrase length (default: 3 words)
configuration #
| env var | default | description |
|---|---|---|
BSKY_HANDLE |
find-bufo.com |
bluesky handle |
BSKY_APP_PASSWORD |
required | app password from bsky settings |
MIN_PHRASE_WORDS |
3 |
minimum words in phrase to match |
POSTING_ENABLED |
false |
must be true to actually post |
EXCLUDE_PATTERNS |
... |
exclude bufos matching these patterns |
PREFERRED_JETSTREAM |
required | sole Jetstream host; no implicit fallback hosts |
VIDEO_SERVICE_URL |
https://video.bsky.app |
HTTPS video-processing service for GIFs |
STATS_PORT |
8080 |
port for the stats HTTP server |
BACKEND_URL |
https://find-bufo.com |
backend used for image proxy + bufo list |
local dev #
# build
zig build
# run locally (dry run by default)
PREFERRED_JETSTREAM=stream.waow.tech ./zig-out/bin/bufo-bot
deploy #
# set secrets (once)
fly secrets set BSKY_HANDLE=find-bufo.com BSKY_APP_PASSWORD=xxxx -a bufo-bot
# deploy
fly deploy
# enable posting
fly secrets set POSTING_ENABLED=true -a bufo-bot
# check logs
fly logs -a bufo-bot
service routing #
The bot follows the same Zat discovery pattern as publex: handle → DID →
DID document → PDS. Login, uploads, record writes/deletes, and service-auth
requests use that account's PDS. Public post reads independently resolve the
post author's PDS and send no bot credentials. Discovery failures fail the
operation; they never select a provider as a fallback.
Jetstream uses only PREFERRED_JETSTREAM (the deployment config already sets
stream.waow.tech). GIF processing defaults to https://video.bsky.app and can be overridden with
VIDEO_SERVICE_URL. This is the only provider-specific service default.
Image and text searches do not require a video service.
verification #
zig build test includes a loopback PDS fixture that exercises blob uploads,
all five post variants, service auth, deletion, and expired-token handling.
It uses synthetic credentials and never writes to a public repo.
To check a real public post through the bot's own code, without logging in or posting a reply:
zig build check-post -- --post at://did:plc:AUTHOR/app.bsky.feed.post/RKEY