# Install dependencies
mise run install
# Start development server with auto-rebuild
mise run dev
# View all available tasks
mise tasks# Install Mise (macOS/Linux)
curl https://mise.jdx.dev/install.sh | sh
# Or with Homebrew
brew install mise
# Then follow "Quick Start" abovesrc/bot/
├── bot.ts # Main Discord client & events
├── api/
│ ├── idleChampionsApi.ts # Game API client (query-param based)
│ └── types/ # Type definitions from game API
├── commands/
│ ├── setup.ts # Save user credentials
│ ├── redeem.ts # Manual code redemption
│ ├── catchup.ts # Redeem all known codes the user hasn't claimed
│ ├── autoredeem.ts # Toggle automatic redemption per user
│ ├── inventory.ts # Show account info
│ ├── open.ts # Open chests
│ ├── blacksmith.ts # Upgrade heroes
│ ├── codes.ts # Show code history (paginated)
│ ├── makepublic.ts # Share codes with other users
│ ├── notifications.ts # View/update DM notification preferences
│ ├── stats.ts # Server-wide stats and aggregate loot
│ ├── logs.ts # Show last N lines of bot log (admin)
│ ├── backfill.ts # Recover missed codes from history (admin)
│ ├── deleteaccount.ts # GDPR account deletion
│ └── help.ts # Command help
├── database/
│ ├── db.ts # Drizzle database connection & migrate()
│ ├── userManager.ts # User credentials storage (AES-256-GCM encrypted)
│ ├── codeManager.ts # Code tracking, history & loot totals
│ ├── auditManager.ts # Audit log operations
│ ├── backfillManager.ts # Backfill operations & locking
│ ├── schema/ # Drizzle table definitions (one file per table)
│ │ ├── index.ts
│ │ ├── users.ts
│ │ ├── redeemed_codes.ts
│ │ ├── pending_codes.ts
│ │ ├── audit_log.ts
│ │ ├── backfill_operations.ts
│ │ └── loot_totals.ts
│ └── migrations/ # Auto-generated SQL migrations (drizzle-kit)
├── handlers/
│ ├── codeScanner.ts # Message code detection
│ ├── autoRedeemer.ts # Auto-redeem detected codes for all eligible users
│ └── backfillHandler.ts # Message history scanning & redemption
└── utils/
├── logger.ts # Winston logger (file + console output)
├── crypto.ts # AES-256-GCM encryption/decryption for credentials
├── apiRequestLogger.ts # API response logging & cleanup
└── debugLogger.ts # Debug utilities
src/test/
└── setup.ts # Bun test preload: sets DB_PATH=:memory:
- Mise - Task runner and tool version manager (MANDATORY)
- Bun 1.3.14 - JavaScript runtime (3-4x faster than Node.js; also used as package manager)
- discord.js 14.26 - Discord bot framework
- bun:sqlite - Built-in SQLite module (replaces
sqlite3) - Drizzle ORM - Type-safe query builder and schema manager
- Bun Fetch API - Built-in HTTP client (replaces
node-fetch) - TypeScript - Type-safe development (
noEmit: true; type-check only) - Winston - Structured logging to file + console
- Bun test - Built-in test runner (no additional dependencies)
The Idle Champions API server has an expired SSL certificate. Always start the bot with:
NODE_TLS_REJECT_UNAUTHORIZED=0When calling game APIs (redeem, open chests, blacksmith), you must:
- Fetch fresh user details via
getUserDetails() - Extract
instance_idfromdetails.instance_id - Pass it to the API call
This prevents "Outdated instance id" errors from the server.
The bot uses Winston for structured logging.
- Log directory:
logs/(auto-created) - Files:
combined.log(all levels, 20 rotated files),error.log(errors only, 10 files) - Default level:
info— override withLOG_LEVEL=debugin.env - Levels:
error,warn,info,debug,trace - Console: colour-coded timestamps, printed alongside file output
# Follow live logs
tail -f logs/combined.log
# Errors only
tail -f logs/error.logThe project uses the built-in Bun test runner. Tests use an in-memory SQLite database so the real data/idle.db is never touched.
bun test # Run all tests once
bun test --watch # Re-run on file changes| File | What it tests |
|---|---|
src/bot/handlers/codeScanner.test.ts |
extractCodesFromText — regex, emoji stripping, case normalisation |
src/bot/handlers/autoRedeemer.test.ts |
Queue serialization, DM sending, skip logic |
src/bot/handlers/backfillHandler.test.ts |
Message history scanning, server swap handling |
src/bot/database/codeManager.test.ts |
All CodeManager methods — per-user redemption, public/private, pending codes, loot totals |
src/bot/database/userManager.test.ts |
All UserManager CRUD operations (with AES-256-GCM encryption) |
src/bot/database/auditManager.test.ts |
Audit log operations |
src/bot/database/backfillManager.test.ts |
Backfill rate limiting, global lock |
src/bot/commands/notifications.test.ts |
/notifications command and preference updates |
src/bot/commands/stats.test.ts |
/stats with empty/populated DB |
src/bot/commands/logs.test.ts |
/logs command with mocked filesystem |
src/bot/utils/apiRequestLogger.test.ts |
API log file cleanup, sensitive param masking |
bunfig.toml configures a preload file:
[test]
preload = ["./src/test/setup.ts"]src/test/setup.ts sets environment variables before any module is imported:
process.env.DB_PATH = ':memory:';
process.env.MIGRATIONS_PATH = '...';This means test files can use plain static imports — db.ts opens an in-memory database automatically.
- Co-locate test files:
<module>.test.tsnext to<module>.ts - Import
dbfrom./dband callinitializeDatabase()inbeforeAll - Clear tables in
beforeEachin FK-safe order (children before parents) - Never call
closeDatabase()inafterAll— Bun reuses workers between test files; closing the connection breaks later files
All game API calls use URL query parameters, not JSON body:
POST /~idledragons/post.php?call=redeemcoupon&user_id=X&hash=Y&instance_id=Z&code=ABC
The production build compiles TypeScript into a self-contained native binary:
# Build production binary (bun build --compile)
mise run prod:build
# Type-check only (no output files)
mise run build
# Run the binary
./dist-bundle/botFor development, Bun runs TypeScript directly — no compile step is needed.
All tasks are run through Mise. Use mise tasks to see all available commands:
mise run install # Install dependencies
mise run dev # Start bot directly from TypeScript source
mise run build # Type-check only (noEmit: true)
mise run prod:build # Build self-contained production binary
mise run lint # Check code quality
mise run lint:fix # Auto-fix linting issues
mise run audit # Check for vulnerabilities
mise run clean # Clean build artifactsSQLite database (./data/idle.db) managed with Drizzle ORM. Schema is defined in TypeScript files under src/bot/database/schema/. Migrations are automatically applied at startup via migrate().
To regenerate migrations after schema changes:
bun run db:generate # Regenerate SQL migrations from schema
bun run db:studio # Open Drizzle Studio (visual DB browser)erDiagram
USERS ||--o{ REDEEMED_CODES : "has"
USERS ||--o{ PENDING_CODES : "has"
USERS ||--o{ AUDIT_LOG : "generates"
USERS {
string discord_id PK
string user_id "AES-256-GCM encrypted"
string user_hash "AES-256-GCM encrypted"
string server "game server URL"
string instance_id
boolean auto_redeem "default true"
boolean dm_on_code "default false"
boolean dm_on_success "default true"
boolean dm_on_failure "default false"
datetime created_at
datetime updated_at
}
REDEEMED_CODES {
int id PK
string code
string discord_id FK
string status "Success or Code Expired"
json loot_detail
int is_public "0 = private, 1 = public"
datetime expires_at
datetime redeemed_at
}
PENDING_CODES {
int id PK
string code
string discord_id FK
datetime found_at
}
AUDIT_LOG {
int id PK
string discord_id FK
string action
json details
datetime created_at
}
BACKFILL_OPERATIONS {
int id PK
string initiated_by "discord_id"
datetime started_at
datetime completed_at
int codes_found
int codes_redeemed
string status "in_progress, completed, failed"
}
LOOT_TOTALS {
string loot_key PK
string loot_type "chest or item"
string scope PK "discordId or __server__"
int total_count
}
/setup user_id:123456 user_hash:abc123def456...
/redeem code:TESTCODE
/inventory
/open chest_type:Gold count:5
/blacksmith contract_type:Small hero_id:1 count:3
/help
The bot saves API responses to debug/ folder automatically:
- Files older than 1 hour are deleted
- Useful for troubleshooting API issues
- Format:
endpoint_YYYY-MM-DDTHH-mm-ss-SSSZ.json
DISCORD_TOKEN # Bot token from Discord Developer Portal
DISCORD_GUILD_ID # Server ID (for guild-specific commands)
DISCORD_CHANNEL_ID # Channel ID (for auto code scanning)
DISCORD_CODE_AUTHOR_ID # User/bot ID that posts promo codes (filters backfill to that author only)
ENCRYPTION_KEY # Required — 64-char hex key (32 bytes) for AES-256-GCM credential encryption
# Generate: openssl rand -hex 32
# Note: users.user_id and users.user_hash are stored as AES-256-GCM
# ciphertext (enc1:<iv>:<authTag>:<ct>), not as raw int/token values.
DB_PATH # Database file path (default: ./data/idle.db)
NODE_ENV # development or production