A Discord bot for AC × KMUTT Rocket Camp 2025 — Operated by DTI.
It supports both classic prefix commands and automatic replies in channels.
Optional Gemini integration is included with strict token limits to protect free quota.
Built with discord.js v14 and Node.js 18+.
No extra config files needed. This bot works out‑of‑the‑box with a single
.env.
- Prefix commands:
p!help,p!rocketcamp,p!price,p!apply,p!contact,p!venue,p!schedule <workshop|launch>,p!ask <question> - Auto-reply in channels (no
p!required) with three modes:- all: reply to every message
- loose: reply to most messages
- strict: reply only to clear questions
- Knowledge‑base first: answers from curated keywords before calling Gemini
- Gemini fallback (optional) with hard caps on output tokens and truncated inputs
- Clean embeds for overview, venue, and daily schedules
- Runtime admin control via
p!admin:- toggle auto‑reply, modes, allowed channels
- adjust rate limits (cooldown/per‑minute)
- enable/disable Gemini, change model, cap tokens/chars
- switch command prefix at runtime
- Hot‑reload camp data from
camp.config.json(optional) - Persist runtime edits with
p!saveconfig
-
Prerequisites
- Node.js 18+
- A Discord Bot Token (Developer Portal → Applications → Bot → Token)
- Developer Portal → Bot → Privileged Gateway Intents: enable Message Content Intent
- Give the bot View Channel and Send Messages permissions in your channels
-
Install
npm init -y npm install discord.js dotenv node-fetch
-
Create
.envDISCORD_TOKEN=YOUR_DISCORD_BOT_TOKEN PREFIX=! # Auto-reply AUTO_REPLY=on # on/off AUTO_REPLY_CHANNELS= # empty = all channels, or comma-separated channel IDs AUTO_REPLY_MODE=all # all | loose | strict AUTO_REPLY_COOLDOWN_SECONDS=8 AUTO_REPLY_MAX_PER_MIN=20 AUTO_REPLY_USE_THREADS=off DEBUG=off # Admins (comma-separated Discord user IDs) ADMIN_IDS= # Gemini (optional, quota-friendly) GEMINI_PROVIDER=google GEMINI_API_KEY=YOUR_GOOGLE_API_KEY GEMINI_MODEL=gemini-1.5-flash GEMINI_MAX_OUTPUT_TOKENS=256 GEMINI_MAX_INPUT_CHARS=3000 # Optional custom endpoint for non-Google providers: GEMINI_ENDPOINT=
-
(Optional) Add
camp.config.jsonIf present, the bot auto‑reloads it when you save. Example:{ "camp": { "title": "AC x KMUTT Rocket Camp 2025 — Operated by DTI", "desc": "Short multi-line description...", "where1": "Workshop 1–3 Oct 2025 @ Assumption College", "where2": "Launch 6–10 Oct 2025 @ Wangchan Valley, Rayong", "forms": { "individual": "https://go.spaceac.tech/rocket-camp-2025-form", "team": "https://go.spaceac.tech/rocket-camp-2025-team", "line": "https://lin.ee/W4dKV7D", "facebook": "https://go.spaceac.tech/facebook" }, "pricing": { "spectator": 2000, "individual": 12345, "team": 100000 }, "scheduleSummary": "Workshop 3 days (1–3 Oct) + Launch 5 days (6–10 Oct), total 8 days", "schedule": { "workshop": [], "launch": [] }, "eligibility": [], "perks": [] }, "venues": [ { "name": "Wangchan Valley, Rayong", "url": "https://maps.app.goo.gl/rmx8v35oLzxpFVXx7" } ] } -
Run
node index.js
Expected logs:
Logged in as <YourBot#1234>
Auto-reply: ON (all)
Allowed channels: ALL
| Variable | Default | Description |
|---|---|---|
DISCORD_TOKEN |
— | Bot token (required). |
PREFIX |
! |
Command prefix. |
AUTO_REPLY |
on |
Enable auto replies without !. |
AUTO_REPLY_CHANNELS |
(empty) | Comma‑separated channel IDs; empty = all channels. |
AUTO_REPLY_MODE |
all |
all / loose / strict. |
AUTO_REPLY_COOLDOWN_SECONDS |
8 |
Per‑user cooldown per channel. |
AUTO_REPLY_MAX_PER_MIN |
20 |
Max replies per channel per minute. |
AUTO_REPLY_USE_THREADS |
off |
Reply inside threads. |
DEBUG |
off |
Log debug skip reasons. |
ADMIN_IDS |
(empty) | Comma‑separated admin user IDs. |
GEMINI_PROVIDER |
google |
Provider name. |
GEMINI_API_KEY |
(empty) | Required to call Gemini. |
GEMINI_MODEL |
gemini-1.5-flash |
Model name. |
GEMINI_MAX_OUTPUT_TOKENS |
256 |
Output token cap. |
GEMINI_MAX_INPUT_CHARS |
3000 |
Input truncation size. |
GEMINI_ENDPOINT |
(empty) | Custom endpoint for non‑Google providers. |
p!help— Show commandsp!rocketcamp— Overview embedp!price— Pricing summaryp!apply— Application links (individual/team)p!contact— Contact channels (LINE, Facebook)p!venue— Venue list embedp!schedule workshop— Workshop days embedp!schedule launch— Launch days embedp!ask <question>— Ask Gemini (used only if the keyword KB does not answer)
p!admin help— Show admin helpp!admin show— Show current runtime config (JSON)p!admin prefix <symbol>— Change command prefixp!admin auto <on|off>— Toggle auto repliesp!admin mode <all|loose|strict>— Change auto‑reply sensitivityp!admin channels list— Show allowed channel IDs (empty = all)p!admin channels set <id,id,...>— Restrict to a list of channel IDsp!admin channels add <id>— Allow an extra channelp!admin channels remove <id>— Remove a channel from allowed listp!admin cooldown <seconds>— Per‑user cooldown per channelp!admin rate <per-minute>— Per‑channel reply cap per minutep!admin threads <on|off>— Reply inside threadsp!admin debug <on|off>— Verbose debug loggingp!admin gemini <on|off>— Enable/disable Gemini calls (API key still required)p!admin gemini model <name>— Change Gemini modelp!admin gemini maxout <tokens>— Cap output tokensp!admin gemini maxin <chars>— Cap input chars sent to Gemini
p!reloadconfig— Manually reloadcamp.config.jsonp!saveconfig— Write currentSTATEtocamp.config.jsonp!set price <spectator|individual|team> <number>— Update feesp!set forms <individual|team|line|facebook> <url>— Update linksp!set schedule "<summary text>"— Update schedule summaryp!set venue add "Name" "URL"— Add a venuep!set venue remove <index>— Remove a venue (1‑based)
- Channel filter: if
.envAUTO_REPLY_CHANNELSis empty, replies in all text channels; otherwise only in listed channel IDs. - Mode:
AUTO_REPLY_MODEcontrols when the bot replies:all— reply to every messageloose— reply to most messages (skips empty/very short noise)strict— reply only when the message clearly contains camp‑related keywords/questions
- Camp relevance filter: messages that are not camp‑related receive a polite “not related” response with tips.
- Rate limits: per‑user cooldown (
AUTO_REPLY_COOLDOWN_SECONDS) and per‑channel per‑minute cap (AUTO_REPLY_MAX_PER_MIN).
- The bot answers from KB first (free), then calls Gemini only if needed and configured.
- Output is capped by
GEMINI_MAX_OUTPUT_TOKENS. - Context is truncated to
GEMINI_MAX_INPUT_CHARSbefore sending. - You can disable/enable Gemini at runtime:
!admin gemini off/on.
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --omit=dev
COPY . .
CMD ["node", "index.js"]docker build -t rocketcamp-bot .
docker run --env-file .env --name rocketcamp-bot --restart unless-stopped rocketcamp-botnpm i -g pm2
pm2 start index.js --name rocketcamp-bot
pm2 save
pm2 startup-
Bot replies only when using
!- Set
AUTO_REPLY=onin.env - Ensure
AUTO_REPLY_CHANNELSincludes your channel ID(s) or is blank to allow all - Verify Message Content Intent is enabled in the Developer Portal
- Check channel permissions (View Channel + Send Messages)
- Set
-
“Gemini is disabled or not configured”
- Add
GEMINI_API_KEYto.env - Run
!admin gemini on
- Add
-
HTTP 403/401 from Gemini
- Verify API key and project access; check
GEMINI_MODELis correct
- Verify API key and project access; check
-
“Missing DISCORD_TOKEN”
- Add
DISCORD_TOKENto.env
- Add
-
Syntax error like “catch or finally expected”
- You likely pasted an incomplete file or mismatched braces. Validate quickly with:
node --check index.js
- Ensure Node.js 18+ and copy the full
index.jsincluding all closing braces
- You likely pasted an incomplete file or mismatched braces. Validate quickly with:
-
Rate limit skips
- Turn on debug: set
DEBUG=onor use!admin debug onto see skip reasons
- Turn on debug: set
Start-Process -FilePath "node" -ArgumentList "index.js" -WorkingDirectory "C:\Users\Example\Example" -WindowStyle Hidden