Skip to content

lanbodikai/queuebot

Repository files navigation

Queuebot

Discord-first FPS queue bot with a live mobile viewer for sharing into WeChat or any mobile chat. Discord is the only control surface. The public viewer is mobile-friendly, backed by the same PostgreSQL state as the bot, and can now accept WeChat-authenticated joiners.

Product Model

  • Discord users create and manage queues.
  • The backend is the single source of truth.
  • Each queue gets a stable public share URL.
  • The share URL opens a live viewer page that polls the backend for updates.
  • The share card shown in chat does not mutate in place. The opened page stays current.
  • The current implementation is WeChat-browser friendly and Mini Program ready, but it is not a WeChat chat bot.
  • WeChat users can join a queue from the shared link after WeChat OAuth.

Stack

  • Node.js 20+
  • TypeScript
  • Fastify
  • discord.js
  • PostgreSQL
  • Prisma ORM
  • Docker Compose
  • Dynamic OG image rendering with @resvg/resvg-js

Commands

  • m!queue 5
  • m!queue 10
  • m!join PARTYCODE
  • m!add @USER
  • m!leave
  • m!updatecode NEWCODE

Discord queue messages also include built-in Join Queue and Leave Queue buttons.

Queue creation is intentionally two-step:

  1. Send m!queue 5 or m!queue 10
  2. Send the party code as your next plain text message

The queue is only created after the party code passes validation and uniqueness checks.

Implemented Behavior

Queue lifecycle

  • m!queue 5 creates 5 main slots and 2 waitlist slots.
  • m!queue 10 creates 10 main slots and 2 waitlist slots.
  • Host auto-joins main slot 1.
  • Party codes are unique among active queues.
  • Each Discord user can only be in one active queue at a time.
  • m!join fills main slots first, then waitlist.
  • m!add @USER lets the current host add one mentioned Discord member into their active queue.
  • m!leave removes the caller from their active queue.
  • Waitlist users auto-promote when a main slot opens.
  • If the queue becomes empty, it is soft-deleted.
  • If the host leaves while users remain, host transfers to the first occupied main slot.
  • m!updatecode is host-only.
  • Active queues expire automatically after a configurable inactivity timeout.

Discord rendering

  • Queue 5 uses one main-slot list and one waitlist list.
  • Queue 10 uses two columns for slots 1-5 and 6-10.
  • Slot rows use boxed labels like [01] and explicit status icons for occupied vs open slots.
  • Queue embeds include a shareable live viewer URL.
  • The Discord message is edited in place after queue changes.
  • Each queue message includes Join Queue and Leave Queue buttons.
  • Queue messages can include a Share button when PUBLIC_BASE_URL is configured.

Public viewer

  • Each queue has a stable share route: /q/:shareSlug
  • The viewer is mobile-first and suitable for WeChat’s in-app browser.
  • It shows live queue state from the backend.
  • It polls every few seconds for updates.
  • It includes dynamic metadata and an OG image endpoint for richer link previews.
  • It includes a WeChat-friendly share action that hands out a WeChat join link.

Architecture

src/
  app/
  config/
  core/
  db/
  http/
  platforms/
    discord/
    shared/
  renderers/
  services/

Layers

  • src/core Queue types, validation, share payload helpers, and pure queue-state helpers.
  • src/services Queue business logic, pending queue creation flow, share payload generation, and message sync.
  • src/platforms/discord Discord intake and message publishing.
  • src/http Health route, public JSON API, share metadata routes, OG image route, and public viewer page.
  • src/renderers Discord embed rendering, queue page HTML, and OG image rendering.
  • src/db Prisma client and queue-record mapping.

Key separation

Business logic lives in src/services/queue-service.ts. Discord and the public viewer both consume the same backend state instead of owning queue state locally.

Data Model

Schema lives in:

Tables:

  • UserIdentity
  • Queue
  • QueueSlot
  • QueueMessageRef

Important queue fields:

  • partyCode User-facing mutable code used by Discord commands.
  • activePartyCode Unique only while the queue is active.
  • shareSlug Stable public viewer identifier so the share URL survives m!updatecode.
  • lastActivityAt Updated whenever queue membership or party code changes and used by the inactivity timer.

API Surface

Health

  • GET /health

Queue JSON

  • GET /api/queues/:partyCode Active queue snapshot by party code.
  • GET /api/share/:shareSlug Queue snapshot by stable public share slug.

Share payload

  • GET /api/queues/:partyCode/share Returns share metadata for the active queue.

Dynamic preview image

  • GET /api/queues/:partyCode/og-image
  • GET /api/share/:shareSlug/og-image

Public viewer

  • GET /q/:shareSlug Public live viewer page.
  • GET /join/:shareSlug Smart join link for mobile shares. In WeChat, it redirects through WeChat OAuth and joins the WeChat identity to the queue. Outside WeChat, it can still fall back to Discord OAuth if configured.
  • GET /auth/wechat/callback WeChat OAuth callback for share-link joins.
  • GET /queue/:partyCode Redirects active party codes to the stable share route.
  • GET /auth/discord/callback Optional Discord OAuth callback for non-WeChat share-link joins.

Optional internal diagnostics

  • GET /internal/queues/:partyCode

If INTERNAL_API_TOKEN is set, send it as x-internal-token.

Queue JSON Shape

The public queue snapshot includes:

  • partyCode
  • shareSlug
  • type
  • host
  • currentMainCount
  • maxMainCount
  • waitlistCount
  • maxWaitlistCount
  • totalCount
  • availability
  • status
  • lastActivityAt
  • updatedAt
  • slots
  • waitlist

Local Development

1. Install dependencies

npm install

2. Create .env

cp .env.example .env

PowerShell:

Copy-Item .env.example .env

3. Fill required environment variables

At minimum:

DATABASE_URL=postgresql://queuebot:queuebot@localhost:5432/queuebot?schema=public
APP_PUBLIC_PORT=3000
DISCORD_ENABLED=true
DISCORD_CLIENT_ID=YOUR_CLIENT_ID
DISCORD_TOKEN=YOUR_ROTATED_BOT_TOKEN
PUBLIC_BASE_URL=http://localhost:3000
WECHAT_ENABLED=true
WECHAT_APP_ID=YOUR_WECHAT_OFFICIAL_ACCOUNT_APP_ID
WECHAT_APP_SECRET=YOUR_WECHAT_OFFICIAL_ACCOUNT_APP_SECRET
WECHAT_OAUTH_SCOPE=snsapi_userinfo
QUEUE_BRAND_NAME=Valorant Queue

PUBLIC_BASE_URL, WECHAT_APP_ID, and WECHAT_APP_SECRET are required for WeChat share-link joins. If you also want non-WeChat browsers to fall back to Discord OAuth, keep DISCORD_CLIENT_SECRET and the Discord redirect URI configured too.

4. Start PostgreSQL

docker compose up -d postgres

5. Run migrations

npx prisma migrate deploy

For local schema iteration:

npx prisma migrate dev

6. Start the app

npm run dev

7. Open the app

  • Health: http://localhost:3000/health
  • Public viewer example: http://localhost:3000/q/<shareSlug>

Docker Compose

Bring up app and database together:

docker compose up --build

The app container runs prisma migrate deploy before booting the server.

Discord Setup

  1. Create a Discord application in the Discord Developer Portal.
  2. Create a bot user.
  3. Enable MESSAGE CONTENT INTENT.
  4. Copy the application client ID into DISCORD_CLIENT_ID.
  5. Generate a client secret and copy it into DISCORD_CLIENT_SECRET only if you want non-WeChat browsers to fall back to Discord OAuth.
  6. In the OAuth2 settings, add this redirect URI:
    • https://YOUR_PUBLIC_DOMAIN/auth/discord/callback
  7. Set DISCORD_SERVER_INVITE_URL to a permanent invite only if you want non-WeChat browsers to fall back into Discord after OAuth.
  8. Copy the bot token into DISCORD_TOKEN.
  9. Invite the bot into your server with:
    • View Channels
    • Send Messages
    • Read Message History
    • Embed Links
  10. Start the bot and test:
  • m!queue 5
  • send a party code
  • m!join CODE
  • m!add @USER

Discord runtime files:

WeChat Setup

This join flow is built for WeChat Official Account webpage authorization inside the WeChat browser.

  1. Create or use a WeChat Official Account with webpage authorization enabled.
  2. In the WeChat Official Account platform, copy:
    • App ID into WECHAT_APP_ID
    • App Secret into WECHAT_APP_SECRET
  3. In the Official Account settings, add your production domain to the OAuth authorized domain list.
  4. Set PUBLIC_BASE_URL to that same public HTTPS domain.
  5. The callback URI used by the app is:
    • https://YOUR_PUBLIC_DOMAIN/auth/wechat/callback
  6. Choose the scope:
    • snsapi_userinfo if you want WeChat nicknames in queue slots
    • snsapi_base if you prefer quieter auth and can accept generic WeChat display names
  7. Restart the app after updating env vars.

WeChat runtime files:

Environment Variables

Required

  • DATABASE_URL
  • DISCORD_TOKEN
  • DISCORD_CLIENT_ID

Strongly recommended

  • PUBLIC_BASE_URL Must be your public domain in production so the share URLs and OG image URLs are absolute.
  • WECHAT_APP_ID Required for WeChat OAuth join links.
  • WECHAT_APP_SECRET Required for WeChat OAuth join links.
  • WECHAT_OAUTH_SCOPE Use snsapi_userinfo if you want nickname-based display names. Use snsapi_base if you prefer quieter authorization and can tolerate generic WeChat display names.
  • DISCORD_CLIENT_SECRET Only required if you want Discord OAuth as a fallback outside WeChat.
  • DISCORD_SERVER_INVITE_URL Used when a non-WeChat visitor signs in with Discord but is not yet in your server.
  • QUEUE_BRAND_NAME Used in viewer metadata and OG image rendering.

Optional

  • APP_PUBLIC_PORT Host port Docker exposes publicly. Use 80 on the VM if you want clean http://IP links instead of :3000.
  • DISCORD_AFTER_JOIN_URL Fallback Discord destination when the queue message URL is unavailable.
  • PORT
  • LOG_LEVEL
  • INTERNAL_API_TOKEN
  • DISCORD_PREFIX
  • QUEUE_CREATION_TIMEOUT_SECONDS
  • QUEUE_INACTIVITY_TIMEOUT_MINUTES
  • QUEUE_INACTIVITY_SWEEP_SECONDS
  • PARTY_CODE_MIN_LENGTH
  • PARTY_CODE_MAX_LENGTH
  • VIEWER_POLL_INTERVAL_SECONDS

How Queue Sharing Works

When a queue is created:

  1. The queue is stored in PostgreSQL.
  2. The queue gets a stable shareSlug.
  3. The Discord embed is rendered with the queue state and share URL.
  4. The public viewer at /q/:shareSlug reads from the same backend.

When the host changes the party code:

  • The party code changes.
  • The stable share URL does not change.
  • The public viewer continues to work.
  • New shares use the same stable viewer entry point with fresh metadata.

How WeChat-Friendly Sharing Works

This MVP is designed for sharing the queue viewer into WeChat, not for controlling queues from a WeChat chat bot.

What works now

  • Rich link metadata on the public viewer route
  • Dynamic OG image endpoint
  • Mobile-friendly page design
  • Live polling after the page opens
  • WeChat OAuth join flow from the shared link
  • Shared backend updates the Discord queue message immediately after a WeChat join

What does not happen

  • Shared cards inside chat do not mutate after being sent
  • There is no native WeChat chat bot flow
  • There is no Mini Program yet

Why this still fits the product goal

  • The share card is the polished entry point
  • The opened viewer is live
  • WeChat users can authenticate in WeChat and secure a slot without controlling the queue from chat
  • Re-sharing later can reflect fresher metadata

Future Mini Program-Ready Path

The current backend is structured so a Mini Program can be added later without rewriting queue logic.

Use these endpoints from a future Mini Program client:

  • GET /api/share/:shareSlug
  • GET /api/queues/:partyCode
  • GET /api/queues/:partyCode/share

The queue service and database remain unchanged. The new frontend would simply consume the JSON API.

One-VM Deployment

Recommended layout:

  • One Linux VM
  • Docker Compose for app + postgres
  • One public domain routed to Fastify
  • Discord bot, API, viewer page, and OG image endpoint all hosted from the same service

Steps

  1. Install Docker Engine and Docker Compose on the VM.
  2. Copy the repo to the VM.
  3. Create .env from .env.example.
  4. Set PUBLIC_BASE_URL to your real domain, for example:
PUBLIC_BASE_URL=https://queue.yourdomain.com
  1. Fill Discord credentials.
  2. Start the stack:
docker compose up -d --build
  1. Put a reverse proxy in front of the app.

Proxy examples:

Viewer Design Notes

  • Queue 5 uses a single main-slot column.
  • Queue 10 uses two columns.
  • The page shows:
    • party code
    • queue type
    • host
    • current count
    • waitlist count
    • live status
    • last updated
    • copy-code action

Viewer rendering files:

Scripts

  • npm run dev
  • npm run build
  • npm run start
  • npm run prisma:generate
  • npm run db:migrate
  • npm run db:deploy
  • npm run db:push
  • npm run db:studio

Known MVP Boundaries

  • Discord is the only control platform.
  • The public viewer is read-only.
  • Inactivity cleanup is time-based and configurable through env vars.
  • There is no auth on the public viewer.
  • WeChat/WeCom chat-bot control is intentionally out of scope.

Verified

  • npm install
  • npx prisma generate
  • npm run build

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors