All projects
Code★ 02026 ⎇ main GitHub ↗

krealalejo/lili-bot

Li Li

lilibot

A Discord bot that shares out the seats in a game group capped at 5 players.
Runs on Google Cloud Run for $0/month.


What it does

Opens a sign-up, people join with a button, and when it closes it draws at random who sits out — remembering last round's leftovers so the same people don't always miss out.

  • /rotacion posts a sign-up with Apuntarme, Salir and Cerrar convocatoria
  • Each person counts once, however many times they click
  • It closes on a timer or on the button, whichever comes first
  • 5 or fewer signed up → everybody plays, no rotation
  • More than 5 → the excess are drawn at random, but anyone benched last round has a guaranteed seat and is not entered into the draw
  • This round's nominees are next round's immune players — that handover is the whole rotation

Memory is per channel. The full rules, including what happens when more than five players are owed a seat, are in the architecture guide.

Architecture

flowchart LR
  user([Discord user]) -->|/rotacion · buttons| discord[Discord]
  discord -->|POST /interactions<br/>Ed25519 signed| run[Cloud Run · scales to zero]
  run -->|read / write state| store[(Firestore<br/>rotations/channelId)]
  run -->|post · edit messages| discord
  run -->|schedule the close| tasks[Cloud Tasks]
  tasks -->|POST /close at closesAt| run

There is no gateway and no WebSocket — Discord calls the bot, not the other way round. Nothing stays running between sign-ups, which is what makes it free, and also why joining is a button rather than a reaction: reactions are gateway-only events.

Zero runtime dependencies. Node 24's WebCrypto covers Ed25519, fetch covers the rest, and type stripping means no build step. Boot is ~85 ms, against Discord's 3-second response deadline. The reasoning is in Architecture, along with how to add a feature.

Commands

Command What it does
pnpm test The full suite, 70 tests
pnpm typecheck tsc --noEmit
pnpm dev Runs the server with --watch, reading .env
pnpm commands Registers the /rotacion slash command with Discord
./deploy/provision.sh Creates the GCP infrastructure, once
./deploy/deploy.sh Builds and deploys to Cloud Run

Configuration

Copy env.example to .env for local work. In production Cloud Run injects these: the two secrets from Secret Manager, the rest as plain variables.

Variable Required Default What it is
DISCORD_TOKEN yes Bot token (secret)
DISCORD_PUBLIC_KEY yes Public key used to verify signatures
APPLICATION_ID yes Discord application ID
GUILD_ID no global Server to register the command in
GCP_PROJECT yes Project ID (GOOGLE_CLOUD_PROJECT also works)
SERVICE_URL yes The service's public URL; Cloud Tasks calls ${SERVICE_URL}/close
TASKS_LOCATION no us-east1 Queue region
TASKS_QUEUE no lilibot-close Queue name
PORT no 8080 Listening port

Where each key comes from is in Discord setup.

Documentation

Guide What's in it
Discord setup Creating the application, the keys you need, inviting the bot, connecting the endpoint
Deployment Provisioning and deploying to GCP, automatic deploys, cost, uninstalling
Architecture How it works and why: the interactions model, data model, security, project layout
Development Running the tests, working locally, where to ch
View full README on GitHub ↗