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.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
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-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
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:
| 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) |
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
#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.
Deployment
Fly.io (recommended for feed generator)
# 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.