Case study
AFTL Companion Bot
The web app solved the data problem, but nobody wants to open a website to find out a raid was rescheduled. This bot puts the same database in front of people where they already are, and pulls data back in from screenshots they paste into chat.
- Role
- Solo developer
- Timeline
- 2025 – present
- Status
- Running in production
The problem
A web app only helps the people who remember to open it. Raid signups, roster changes, and loot payouts all needed to reach the guild in Discord — and the loot data itself arrived as screenshots pasted into a channel, which someone was transcribing by hand.
What I built
- Thirteen slash commands covering lineups, loot payouts, raid threads, role management, server audits, and translation.
- Postgres change listeners that watch the same tables the web app writes to, so a roster edit on the site announces itself in Discord within seconds.
- A loot pipeline: screenshot posted → Claude vision extracts items → rows written to the shared ledger → reaction-driven payout state → automatic claim reminders on a schedule.
- Automated raid threads that create themselves, mention the right roles, track signups, and mark completion.
Technical notes
One database, two front doors
The bot and the web app are separate deployments over a single Supabase project. That means no sync layer and no API between them, but it also means schema changes have to land in both places together — which is exactly the kind of coupling worth being deliberate about rather than accidental.
Screenshots as an input format
Players already screenshot their drops, so the data exists before anyone asks for it — it's just sitting in an image. Typing it into a form adds a step at the moment nobody wants one, and skipped steps become gaps in the ledger. Claude's vision model turns the screenshot into structured rows, and the bot asks in-thread when something's ambiguous rather than guessing.
Scheduling and rate limits
Reminder scheduling, raid role rotation, and payout nudges all run on timers inside the bot process, behind a small rate limiter so a burst of database changes can't trip Discord's API limits.
By the numbers
- 76
- Commits
- 13
- Slash commands
- 8
- Event handlers
- 6.3k
- Lines of code
Stack
Runtime
Data
AI
What I'd do differently
- Long-lived timers in a single bot process are fragile — a restart loses in-flight schedules. A durable job queue would be the first thing I'd add.
- Splitting logic across a bot and a web app doubles the places a schema change can break. Shared types generated from the database would have caught several of those for me.