
Discord Bot
Forge
Ticket bot with Gemini-powered classification for a dev studio (Minecraft plugins, websites, Discord/Telegram bots).
About
Forge — Discord Ticket & Order Bot
Ticket bot with Gemini-powered classification for a dev studio (Minecraft plugins, websites, Discord/Telegram bots).
Architecture
| Part | File | Notes |
|---|---|---|
| Entry point / event routing | src/index.js | Client, command registration, button router, jobs timer |
Database (SQLite, built-in node:sqlite) | src/db.js | Tables: settings, panels, tickets, kb, orders, ratings |
| Runtime config | src/settings.js + /config | Nothing hardcoded — everything editable live |
| Ticket panels | src/panels.js + /panel | Multiple panels, each with its own category & styling |
| Ticket lifecycle | src/tickets.js | Open → describe → AI classify → open → close/archive |
| AI (Gemini, structured JSON) | src/ai.js | @google/genai, responseSchema, model set in config.json |
| Knowledge base | src/kb.js + /kb | kb.json (applied on restart) + live /kb add/remove/list |
| Transcripts | src/transcript.js | Self-contained dark HTML file |
| Background jobs | src/jobs.js | Inactivity warn/auto-close, owner reminders |
| Audit log | src/log.js | Every open/close/claim/rating/AI event → log channel |
Ticket flow
- User clicks Open ticket on a panel → private channel under the panel's category (or the default one).
- Bot asks for a short description; the user's first message is sent to Gemini with the whole KB in the system prompt.
- Gemini returns structured JSON:
type(bug/order/other),priority,project_type, detectedlanguage, KB match, follow-up questions — all generated text is in the user's language.- bug → priority tag, KB solution suggestion (entry id cited in the log channel only), numbered follow-up questions.
- order → pings the owner, creates a row in the
orderstable. - other/unclear → one clarifying question; still unclear → flags for manual review + owner ping.
- Close button (with confirmation):
- order tickets → customer's access removed, channel moved to the archive category (kept for staff), customer gets a 1–5⭐ rating DM;
- everything else → HTML transcript to the log channel, channel deleted.
Modrinth release announcements
Watches a Modrinth user and posts a message whenever they publish a new plugin or a new version of an existing one. You choose the channel and, optionally, a role to ping.
/modrinth watch user:YourModrinthName - whose projects to track
/modrinth channel channel:#releases - where to announce
/modrinth role-release role:@NewPlugins - ping when a NEW plugin is published (optional)
/modrinth role-update role:@Updates - ping when a NEW version is published (optional)
/modrinth noping - stop pinging any role
/modrinth interval minutes:10 - how often to check (restart to apply)
/modrinth check - check right now
/modrinth view - show current settings
On the first check after /modrinth watch, existing releases are recorded silently, so only releases published from that point on are announced. A brand new plugin posts one "New release" message and pings the release role; a new version of an existing plugin posts a per-version message (loaders, MC versions, changelog) and pings the updates role. Each ping role is independent and optional. Uses the public Modrinth API, no key required.
Gemini model & cost
Default gemini-2.5-flash — one classification call per ticket (~1–3K tokens), effectively fractions of a cent. Set GEMINI_MODEL=gemini-2.5-flash-lite for even cheaper, or a Pro model for smarter triage. Get a key at https://aistudio.google.com/apikey.
Notes / decisions
- Priority is shown as an embed field (not a channel-name prefix) — Discord rate-limits channel renames to 2 per 10 min.
- Follow-up questions are sent as one numbered message — more reliable than one-at-a-time parsing.
- Closed order tickets go to a single archive category (simplest reliable structure) — per-customer categories would hit Discord's 50-channels-per-category limit quickly.
- Language matching: canned bot messages are English; everything AI-generated (questions, KB answers) is written in the user's detected language.