ShopSavvy for Bluesky

April 18, 2026 · View on GitHub

A TypeScript suite of Bluesky integrations for ShopSavvy — a reactive mention bot, a custom algorithmic feed, and a daily deal poster.

What It Does

Mention Bot — Watches the Bluesky firehose for posts mentioning @shopsavvy.bsky.social. When someone pastes a retailer URL, ASIN, barcode, or product name, the bot replies with real-time price comparisons across retailers, sorted lowest-first.

ShopSavvy Deals Feed — A custom AT Protocol feed that surfaces deal-relevant posts from across Bluesky. Scored by recency, engagement, retailer URL presence, and deal keyword signals. Subscribe at at://did.shopsavvy.bsky.social/app.bsky.feed.generator/shopsavvy-deals.

Daily Deal Poster — A cron job that posts the top ShopSavvy deal at 11:00 AM EST every day, with image embed, was/now pricing, and affiliate disclosure.

Architecture

bluesky-shopsavvy/

├── packages/
│   ├── bot/                  # Mention bot (Jetstream → reply)
│   │   └── src/
│   │       ├── index.ts          Entry point + graceful shutdown
│   │       ├── jetstream.ts      Jetstream WebSocket subscription
│   │       ├── mention-handler.ts  Intent parser (URL/ASIN/barcode/search)
│   │       └── reply-builder.ts  Rate-limit check + reply post
│   │
│   ├── feed-generator/       # AT Protocol feed generator (Express)
│   │   └── src/
│   │       ├── server.ts         Express server + XRPC endpoints
│   │       ├── algorithm.ts      Recency × engagement scoring
│   │       ├── indexer.ts        Jetstream indexer (writes to SQLite)
│   │       └── well-known.ts     did:web DID document builder
│   │
│   ├── poster/               # Daily deal cron poster
│   │   └── src/
│   │       ├── index.ts          node-cron scheduler (11am EST)
│   │       └── daily-deal.ts     Fetch + format + post deal
│   │
│   └── shared/               # Shared utilities (all packages)
│       └── src/
│           ├── types.ts          Shared TypeScript types + constants
│           ├── bsky.ts           @atproto/api wrappers (auth, post, RichText)
│           ├── db.ts             bun:sqlite helpers (cooldowns, feed index)
│           └── shopsavvy.ts      @shopsavvy/sdk wrappers + formatters

├── deploy/
│   ├── bot.Dockerfile
│   ├── feed-generator.Dockerfile
│   └── fly.toml              Fly.io config for feed generator

├── tests/
│   ├── test-algorithm.test.ts
│   ├── test-mention-handler.test.ts
│   └── test-db.test.ts

└── .env.example

Data flow:

Bluesky Jetstream ──▶ bot/indexer.ts ──▶ SQLite ──▶ feed skeleton (XRPC)
                  └──▶ mention-handler ──▶ ShopSavvy API ──▶ reply post

ShopSavvy API ──▶ poster/daily-deal.ts ──▶ Bluesky post (11am EST)

Features

Mention Bot

  • Connects to Bluesky Jetstream via @skyware/jetstream
  • Filters for app.bsky.feed.post commits mentioning @shopsavvy.bsky.social
  • Intent detection priority: retailer URL → ASIN → barcode → text search → help
  • Supported retailers: Amazon, Target, Walmart, Best Buy, eBay, Newegg, Costco, Home Depot, Lowe's, Macy's, Nordstrom, B&H, Adorama, Staples, Wayfair, Chewy, and more
  • Rate limit: one reply per user per 6 hours (enforced via SQLite)
  • All replies include #ShopSavvy #affiliate disclosure

Feed Generator

  • Express server implementing the AT Protocol Feed Generator spec
  • /.well-known/did.json — did:web DID document
  • /xrpc/app.bsky.feed.describeFeedGenerator — feed metadata
  • /xrpc/app.bsky.feed.getFeedSkeleton — paginated feed with cursor
  • Scoring: recency_decay × 10 + log(engagement) + retailer_bonus + keyword_bonus
  • SQLite index auto-prunes posts older than 72 hours
  • Like events on indexed posts trigger score updates

Daily Poster

  • node-cron scheduler at 11:00 AM EST (0 16 * * * UTC)
  • Fetches top hot deal from ShopSavvy API
  • Posts with image embed (fetched from deal.image.url) + rich text
  • Format: emoji + grade + title, was/now price, retailer, URL, disclosure

Setup

1. Clone and install

git clone https://github.com/shopsavvy/bluesky-shopsavvy.git
cd bluesky-shopsavvy
bun install

2. Configure environment

cp .env.example .env
# Edit .env with your credentials

Required variables:

VariableDescription
BSKY_IDENTIFIERYour bot's Bluesky handle (e.g. shopsavvy.bsky.social)
BSKY_APP_PASSWORDApp password from https://bsky.app/settings/app-passwords
SHOPSAVVY_API_KEYShopSavvy Data API key from https://shopsavvy.com/data
FEEDGEN_HOSTNAMEPublic hostname for the feed generator (e.g. feeds.shopsavvy.com)

3. Run each service

Mention bot:

bun run bot
# or directly:
bun run packages/bot/src/index.ts

Feed generator:

bun run feed
# or directly:
bun run packages/feed-generator/src/server.ts

Daily poster:

bun run poster
# Post immediately for testing:
POSTER_RUN_NOW=1 bun run poster

4. Publish the custom feed to Bluesky

After the feed generator is running and publicly reachable, publish the feed record to your Bluesky account so users can subscribe:

import { Agent, CredentialSession } from "@atproto/api"

const session = new CredentialSession(new URL("https://bsky.social"))
await session.login({ identifier: "shopsavvy.bsky.social", password: "your-app-password" })
const agent = new Agent(session)

await agent.api.com.atproto.repo.putRecord({
  repo: agent.accountDid!,
  collection: "app.bsky.feed.generator",
  rkey: "shopsavvy-deals",
  record: {
    did: "did:web:feeds.shopsavvy.com",
    displayName: "ShopSavvy Deals",
    description: "Real-time deal posts from across Bluesky — retailer links, price drops, and community finds.",
    createdAt: new Date().toISOString(),
  },
})

After publishing, the feed URI is: at://did:plc:<your-did>/app.bsky.feed.generator/shopsavvy-deals

Community Etiquette

We take Bluesky's culture seriously:

  • Transparency — Every bot post and reply includes #ShopSavvy so it's clearly from us, and #affiliate on any post with a monetized link.
  • Rate limits — The mention bot replies at most once per user per 6 hours. We never spam.
  • No unsolicited DMs — The bot only replies when directly mentioned.
  • Opt-in feed — The ShopSavvy Deals feed is a custom feed that users must actively subscribe to.
  • No vote manipulation — We don't programmatically like, repost, or boost posts.
  • Honest replies — If a product lookup fails, we say so clearly instead of posting bad data.

Deployment

# Install flyctl: https://fly.io/docs/hands-on/install-flyctl/
fly auth login
fly launch --config deploy/fly.toml

# Set secrets (never commit these)
fly secrets set \
  BSKY_IDENTIFIER=shopsavvy.bsky.social \
  BSKY_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx \
  SHOPSAVVY_API_KEY=ss_live_your_key_here \
  FEEDGEN_HOSTNAME=feeds.shopsavvy.com \
  FEEDGEN_DB_PATH=/data/feed.db

fly deploy --config deploy/fly.toml

The bot and poster are long-running processes. Deploy them as separate Fly.io apps or run them alongside on your own server:

# On your server
bun run packages/bot/src/index.ts &
bun run packages/poster/src/index.ts &

Docker

# Feed generator
docker build -f deploy/feed-generator.Dockerfile -t shopsavvy-feed-generator .
docker run -p 3000:3000 --env-file .env shopsavvy-feed-generator

# Bot
docker build -f deploy/bot.Dockerfile -t shopsavvy-bot .
docker run --env-file .env shopsavvy-bot

Testing

# Structural checks + unit tests
bash test.sh

# Unit tests only
bun test tests/

More

Learn more at shopsavvy.com/integrations/bluesky.

ShopSavvy Data API documentation: shopsavvy.com/data.

License

MIT — Copyright (c) 2026 Monolith Technologies, Inc.