Discord Server Builder and Ticket Bot
A small Node command-line tool and a Cloudflare Worker bot that set up a Discord community and run role buttons, applications and support tickets.

My role
I directed AI (Claude Code) to build it and ran the deployment. I own the Discord server and the bot application. AI wrote the code; no other person worked on it.
What it is
This project has two parts. The first is a set of dependency-free Node scripts that call the Discord API to create categories, channels and roles, and post styled messages from a layout file. That is how the Stratum Tech Group community server was set up.
The second is a serverless bot on Cloudflare Workers. It receives signed webhooks from Discord and handles buttons and forms: self-serve role toggles, a creator application that opens a private ticket channel, support tickets, and team-only Accept, Decline and Close buttons.
The bot token and other credentials are kept out of the code and out of the repo. Nothing here is public code; the server itself is a community space.
How it was built
- Write a tiny Discord REST clientdiscord.mjs (71 lines) wraps the Discord v10 API with plain fetch so there is nothing to install, and retries when Discord rate-limits a call.
- Describe the server as databuild-stratum.mjs (150 lines) and native-setup.mjs (67 lines) create categories, channels and roles from a layout, so the server can be rebuilt the same way.
- Post styled contentstyled-posts.mjs (119 lines), stratum-posts.mjs (71), games-posts.mjs (68), forum-and-announce.mjs (41) and upload-kit.mjs (21) publish the welcome, rules, games and announcement posts.
- Generate brand artmake-banners.py (33 lines) renders the server icon (1024 px), profile banner (1360x480) and server banner (1920x1080) in the Stratum gold-on-dark style.
- Pull game stats into a filegames.json snapshots the studio's Roblox games (players, visits, favourites) so posts can show current numbers.
- Write the interaction botworker/stratum-bot.js (157 lines) is the Worker. It answers Discord's ping, then routes button clicks (type 3) and form submissions (type 5).
- Verify every requestBefore doing anything the Worker checks Discord's Ed25519 signature on the raw request body using Web Crypto, and returns 401 on a bad signature.
- Run tickets as private channelsA creator application or support form creates a private channel under a tickets category, visible only to the member and the team roles, and stores the ticket in KV so each member gets one open ticket per kind.
- Gate team actionsAccept, Decline and Close buttons only work for members with a team, founder or moderator role, or admin permission; replies use deferred responses (ctx.waitUntil) so slow API work never times out.
- Deploy by APIworker/deploy.mjs (71 lines) uploads the Worker and its KV binding through the Cloudflare API, with the Cloudflare token taken from the environment and the bot token from a local secrets file.
Decisions and why
- No npm dependencies
- The scripts run on any machine with Node 18 and there is nothing to keep updated or audit.
- Serverless Worker instead of an always-on bot
- Button clicks arrive as webhooks, so a Worker with KV storage is cheap and needs no server to keep running.
- Private ticket channel per application
- Applicants get a place to talk with the team without exposing their details to the rest of the server.
- Keep tokens and ids out of anything published
- Credentials live in a local secrets file and environment variables; the deploy script's ids stay private.
Hardest problems
- Discord rate limits break bulk setup scripts
- Every API call retries up to four times, waiting the retry_after time Discord returns.
- Discord needs a reply within seconds but ticket creation takes many API calls
- The Worker answers instantly with a deferred message and finishes the work in the background, then edits the reply.
- Stopping anyone but the team from using staff buttons
- A role and permission check on every Accept, Decline and Close click.
Numbers
| Source files | 11 scripts (.mjs, .js, .py) | 2026-11-06 |
|---|---|---|
| Lines of code | 869 across the 11 files | 2026-11-06 |
| Worker size | 157 lines (stratum-bot.js) | 2026-11-06 |
| Brand images | 3 (icon, profile banner, server banner) | 2026-11-06 |
Timeline
- 2026-10-26Admin scripts, brand banners and the Cloudflare Worker bot all built and deployed the same day.
What I took from it
- Handling rate limits and deferred replies up front avoids most flaky bot behaviour.
- Verifying signatures and gating staff buttons is the minimum for any public-facing bot.
- Keeping secrets out of the scripts makes the code safe to show and move around.