ScrubJay is a NestJS-powered Discord bot built for the California Birding community, and has since expanded to North Carolina. It polls eBird for notable observations, stores them in Postgres, and posts grouped alerts to Discord channels so birders can act fast without noisy duplicates.
- Poll eBird on a 15 minute schedule, normalize observations with Drizzle ORM, and keep location metadata current.
- Group alerts per channel and send Discord embeds with media counts and confirmation status while avoiding repeat deliveries.
- Necord and discord.js power slash commands and reactions; a
pingcommand is included for basic health checks. - Local mock of the eBird v2 API lets you develop without hitting the production service.
apps/scrubjay- Discord bot service (NestJS, Necord, Drizzle, discord.js).apps/test-api- Mock eBird API for local development and tests.packages/typescript-config- Shared TypeScript config.docker-compose.yaml- Postgres 17 for local use.
- Node.js 24 (see
.nvmrc) and pnpm 10. - Postgres 17 running locally (
docker compose up -d postgres). - Discord application with a bot token and client ID.
- eBird API token. Point the bot at
apps/test-apiif you want to avoid the live API.
Create an .env in the repo root:
DATABASE_URL=postgres://scrubjay:scrubjay@localhost:5432/scrubjay
DISCORD_TOKEN=your_discord_bot_token
DISCORD_CLIENT_ID=your_discord_client_id
EBIRD_TOKEN=your_ebird_token
# Optional
EBIRD_BASE_URL=http://localhost:8080/ # use the mock API
DEVELOPMENT_SERVER_ID=your_dev_guild_id # limit commands to a dev server
PORT=3000The bot runs database migrations on startup using the Drizzle files in apps/scrubjay/src/drizzle.
- Install dependencies:
pnpm install - Start Postgres:
docker compose up -d postgres - Start the bot:
pnpm --filter scrubjay dev - Optional: run the mock API at
localhost:8080withpnpm --filter test-api dev
Jobs ingest eBird data every 15 minutes and dispatch grouped alerts every 5 minutes. Bootstrap logic runs on startup to backfill observations without sending Discord messages.
- Alert subscriptions live in the
channel_ebird_subscriptionstable; add your channels there before expecting dispatches. - Formatting and linting:
pnpm format-and-lint. Build tasks are coordinated by Turbo.