Skip to content
jaysoncdev.com
Contact
All projects

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

Node.jsdiscord.js v14dotenv

Data

Supabase (shared with the web app)Postgres change listeners

AI

Anthropic SDK — vision extraction & translation

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.