A TypeScript suite of Bluesky integrations for ShopSavvy — a reactive mention bot, a custom algorithmic feed, and a daily deal poster.
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.
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)
- Connects to Bluesky Jetstream via
@skyware/jetstream - Filters for
app.bsky.feed.postcommits 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 #affiliatedisclosure
- 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
node-cronscheduler 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
git clone https://github.com/shopsavvy/bluesky-shopsavvy.git
cd bluesky-shopsavvy
bun installcp .env.example .env
# Edit .env with your credentialsRequired variables:
| Variable | Description |
|---|---|
BSKY_IDENTIFIER |
Your bot's Bluesky handle (e.g. shopsavvy.bsky.social) |
BSKY_APP_PASSWORD |
App password from https://bsky.app/settings/app-passwords |
SHOPSAVVY_API_KEY |
ShopSavvy Data API key from https://shopsavvy.com/data |
FEEDGEN_HOSTNAME |
Public hostname for the feed generator (e.g. feeds.shopsavvy.com) |
Mention bot:
bun run bot
# or directly:
bun run packages/bot/src/index.tsFeed generator:
bun run feed
# or directly:
bun run packages/feed-generator/src/server.tsDaily poster:
bun run poster
# Post immediately for testing:
POSTER_RUN_NOW=1 bun run posterAfter 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
We take Bluesky's culture seriously:
- Transparency — Every bot post and reply includes
#ShopSavvyso it's clearly from us, and#affiliateon 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.
# 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.tomlThe 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 &# 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# Structural checks + unit tests
bash test.sh
# Unit tests only
bun test tests/Learn more at shopsavvy.com/integrations/bluesky.
ShopSavvy Data API documentation: shopsavvy.com/data.
MIT — Copyright (c) 2026 Monolith Technologies, Inc.