arizukocomponents › bskyd

bskyd

What it is

bskyd is the Bluesky channel adapter. It polls Bluesky notifications via the AT Protocol xrpc API for mentions and replies, posts inbound to gated, and writes outbound as app.bsky.feed.* records on the user’s PDS (personal data server). JID prefix is bluesky:; per-user JIDs are bluesky:<did>.

Why it exists

Bluesky is the AT Protocol social surface. There is no streaming API for arbitrary accounts — the firehose is consumer-wide and too noisy — so bskyd polls listNotifications and the auth-session refresh flow. Writing posts means signing record creates against the PDS, which is what bskyd handles for the agent.

How it fits

Bluesky PDS (bsky.social or custom)
        |  xrpc poll: app.bsky.notification.listNotifications
        v
      bskyd     (LISTEN_ADDR=:8080)
        |  POST /v1/messages    (signed by CHANNEL_SECRET)
        v
      gated
        |  POST /v1/send         (callback to bskyd)
        v
      bskyd     (com.atproto.repo.createRecord on app.bsky.feed.*)
        |
        v
      Bluesky PDS

Wired verbs (bskyd/client.go, bskyd/server.go): send / reply (app.bsky.feed.post; reply_to sets the reply ref), send_file (uploadBlob + app.bsky.embed.images, single image), post (with first media_paths[0] embedded), like (app.bsky.feed.like record), delete (com.atproto.repo.deleteRecord), quote (app.bsky.embed.record), repost (app.bsky.feed.repost record). dislike, forward, edit are 501-with-hint.

The edit hint is by design: putRecord succeeds at the PDS, but the Bluesky appview intentionally ignores updates to app.bsky.feed.post records — the edit never appears in the feed. Use delete + post instead.

Inbound is public-feed only. DMs would need chat.bsky.convo proxied through did:web:api.bsky.chat, and no inbound DM polling is wired today.

Standalone usage

bskyd is a plain Go binary. It needs a reachable gated, a Bluesky handle, and an app password (not your main password).

export ROUTER_URL=http://gated:8080
export CHANNEL_SECRET=$(grep ^CHANNEL_SECRET .env | cut -d= -f2)
export LISTEN_ADDR=:8080
export BLUESKY_IDENTIFIER=you.bsky.social
export BLUESKY_PASSWORD=abcd-efgh-ijkl-mnop    # app password
export BLUESKY_SERVICE=https://bsky.social     # or your PDS
export DATA_DIR=/srv/data/arizuko_demo
./bskyd

Bluesky-side setup: Settings → Privacy and security → App passwords. App passwords skip 2FA and can be revoked individually. GET /health returns 503 when the auth session is invalid; bskyd refreshes automatically and recovers on the next poll.

Go deeper