Skip to content

Latest commit

 

History

229 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Discord Ticketbot

🎫 Discord Ticket Bot

A modern, self-hosted Discord ticket bot built on Discord.js v14. SQLite out of the box (no external database required), with optional MySQL/MariaDB and PostgreSQL support. No telemetry, full feature set out of the box.

It is a plain Discord bot: no game server, no FiveM, no ESX or QBCore, no framework. All it needs is Node.js and a bot token.

Version License: AGPL-3.0 Node.js Discord.js Docker Website Documentation

📄 Readme (EN) · Readme (DE)

Self-hosted Discord ticket bot: overview, features and hosted transcripts


✨ Features

Feature Description
🎫 Ticket Types Up to 25 configurable types with individual emoji, color, category & questions
📋 Questionnaires Modal forms (up to 5 questions) shown when opening a ticket
🙋 Claim System Staff can claim/unclaim. The button toggles, embed and topic update automatically
🔴 Priorities Low / Medium / High / Urgent, predefined per ticket type or set via /priority, shown in channel topic & embed
📝 Staff Notes Private notes via /note add / /note list
🔀 Move Ticket Move to a different type/category via /move or button (staff only)
🛡️ Type-specific Staff Roles Each ticket type can define its own staff roles
🖼️ Panel Logo & Banner Optional logo thumbnail and/or banner image in the panel embed
🎛️ Panel Interaction Type Choose between a Button or a direct Select Menu in the panel
⭐ Rating System 1–5 star feedback after closing, automatically posted to a configured channel
⏰ Staff Reminder Automatic ping inside the ticket if no staff responds within X hours
⏰ Auto-Close Automatically close inactive tickets with a configurable warning period
♻️ Reopen Tickets Reopen a closed ticket via the ♻️ button or /reopen. Configurable, restores access and moves it back
🔗 Transcript Links Transcripts stored online and accessible via a public link
📄 HTML Transcript Self-contained HTML transcript in a modern or classic style. Avatars and custom emojis are embedded as Base64, mentions and Created/Claimed/Closed-by shown as names instead of IDs, no CDN required
🌐 Custom Domain Paid tiers can serve transcripts, and a hosted bot's dashboard, under their own domain
📊 Statistics Server-wide stats and detailed per-user stats via /stats
🚫 Blacklist /blacklist add/remove/list to block users from opening tickets
💬 Canned Responses Pre-defined snippets sent with one command, configured in snippets.jsonc
🔒 Ticket Lock Lock/unlock a ticket to prevent the user from sending messages
📢 Broadcast Send a message to all open ticket channels at once
🔔 User Notifications Optional DM notification for users when a staff member replies
🎮 Dynamic Bot Status Automatically display the number of open tickets in the bot status
🌍 Multilingual German and English included, easily extensible
🗄️ Flexible Database SQLite out of the box (zero setup). Optional MySQL/MariaDB or PostgreSQL via DATABASE_URL, with a migration script
🔄 Auto-Update Check Checks for new GitHub releases on startup and notifies with update instructions
🖥️ Web Dashboard Optional self-hosted browser dashboard (off by default): tickets, statistics, a form/file config editor, bot control and per-role/user permissions

🔗 MSK Transcript Service

Instead of sending transcripts as file attachments via DM, the bot can upload them to www.msk-scripts.de and generate a public link. It opens in any browser, no download required.

Subscription Tiers

Feature Basic (free) Premium (€3.99/mo) Premium+ (€6.99/mo) Business (€9.99/mo)
Transcript as link
Max. transcript size 10 MB 50 MB 100 MB 200 MB
File attachments in transcript
Max. attachment size per ticket 100 MB 200 MB 500 MB
Custom domain
Storage duration 30 days 180 days 365 days 10 years
Uploads per hour 30 60 300 300
Remove the MSK notice
Hosted bot management

Paid tiers are subscribed directly in your dashboard via Stripe, with a 14-day free trial for new customers, no credit card required, cancellable anytime.

Getting your API Key

  1. Visit www.msk-scripts.de/ticketbot/verify
  2. Sign in with your Discord account
  3. Select your server → your API key is generated instantly

To upgrade to Premium/Premium+, open your dashboard and start the free trial. Billing is handled by Stripe.

Then add it to your .env:

MSK_API_KEY="your_api_key_here"
MSK_API_URL="https://www.msk-scripts.de"

Custom Domain (Premium & Premium+)

Premium users can serve transcripts under their own domain (e.g. tickets.yourserver.com). A hosted bot's dashboard can use one too.

The Custom Domain tab: one card for transcripts, one for the bot dashboard

  1. Visit www.msk-scripts.de/ticketbot/dashboard after verifying
  2. Enter your domain and set a DNS A-Record pointing to the server IP shown
  3. Click "Check DNS" once propagation is complete. SSL is set up automatically

📖 Full setup guide: docu.msk-scripts.de


🖥️ Hosted Bot Management (Premium, Premium+ & Business)

Do not want to run a server at all? Any paid tier can have the bot hosted by MSK Scripts. You set it up yourself in the dashboard at msk-scripts.de/ticketbot/dashboard, no SSH access or server knowledge required.

The Bot Hosting tab before activation

Setting it up

  1. Open your dashboard and go to Bot Hosting.
  2. Enter three values from the Discord developer portal: your bot token, client ID and client secret. Everything else, from the guild id to the ports and secrets, is filled in for you.
  3. Press Activate bot hosting. We install the bot, start it and check that it really came up. If one of your values is wrong, you get the bot's own log back and can correct it right there.
  4. Add the redirect URL the dashboard shows you under OAuth2 → Redirects in the developer portal. Only you can do this, and the dashboard login does not work without it.

What's included

Bot Hosting once it runs: its address, bot control and live logs

Feature Description
Its own address Your bot's dashboard is published at tickets-<id>.msk-scripts.de, or under a domain of your own. The TLS certificate is included either way.
A login for your team That dashboard runs its own Discord login, so your staff sign in with their own account and see exactly what their permissions allow.
Bot Control Start, stop and restart the bot with a single click.
One-click Update Downloads the latest version via git pull, installs new dependencies and prompts you to restart.
Live Log Console Real-time stream of the bot's output directly in the browser, no terminal needed.
Credentials form Correct the token or the client secret without a terminal. That matters precisely when the bot is down, because then its own dashboard is the thing you cannot reach.
Reversible Remove hosting whenever you want. The installation is archived for 14 days, and one click brings it back with its tickets and settings intact.

🖥️ Self-Hosted Web Dashboard

Running the bot yourself? The optional web dashboard lets you manage everything in the browser instead of editing files over SSH. It is disabled by default, so if you never turn it on, nothing about your bot changes.

Area What you get
Tickets Filterable list, ticket detail with the live conversation, and claim / close / reopen / move / lock / priority.
My tickets Every member sees the tickets they opened and can reply to the open ones. Their reply is posted in the Discord channel under their own name.
Statistics Totals, average rating, average handling time and a team ranking by tickets closed.
Configuration A structured form editor and a raw file editor for config.jsonc, snippets.jsonc, .env and the locale files, with line numbers, syntax highlighting and a Discord role/channel/category name lookup.
Bot control Start, stop, restart and update the bot, plus a live console.
Permissions Grant access to roles or single users, where a user entry can override that person's role to revoke a single permission.

Quick start

npm run dashboard:setup   # guided setup: generates the secret, writes .env, prints a ready-to-use reverse-proxy config
npm run dashboard         # starts the bot WITH the dashboard

npm start keeps running the plain bot with no web server at all, exactly as before.

Safe by default

  • Disabled unless you set DASHBOARD_ENABLED=true.
  • Bound to 127.0.0.1, so it is not reachable from the internet. Use an SSH tunnel or a reverse proxy with HTTPS.
  • Refuses to start on a public interface without HTTPS, with a clear message telling you how to fix it.
  • The signing secret is generated per installation, never shipped as a default.

Login is Discord OAuth using the application you already created for the bot. Your roles are resolved server-side, the server owner always has full access and cannot be locked out, and every change is written to an audit log.

📖 Full guide: docs/dashboard-en.md · docu.msk-scripts.de


📁 Project Structure

discord_ticketbot/
├── index.js                    # Entry point (the plain bot)
├── dashboard.js                # Optional dashboard entry (npm run dashboard) that supervises the bot
├── package.json
├── .env.example                # Environment variable template
├── ticketbot.service           # systemd unit file for Linux servers
├── scripts/
│   ├── migrate-db.js           # `npm run db:migrate`; SQLite → MySQL/PostgreSQL
│   └── dashboard-setup.js      # `npm run dashboard:setup`; guided dashboard setup
├── tests/                      # node:test suites (npm test); no extra dependencies
├── web/                        # Dashboard UI (React + Vite). web/dist is committed; no build needed on the server
├── assets/                     # Static files (logo, banner images)
│   ├── logo.png
│   └── banner.png
├── config/
│   ├── config.example.jsonc    # Configuration template (with comments)
│   └── snippets.example.jsonc  # Canned responses template
├── docs/
│   ├── setup-en.md
│   ├── setup-de.md
│   └── dashboard-en.md         # Full web dashboard guide
├── locales/
│   ├── de.json
│   └── en.json
├── data/
│   └── tickets.db              # SQLite database (auto-created; default backend)
└── src/
    ├── client.js
    ├── config.js
    ├── database/               # Engine-agnostic DB layer (SQLite/MySQL/PostgreSQL)
    │   ├── index.js            # Public async API + all queries
    │   ├── url.js              # DATABASE_URL parsing → driver selection
    │   ├── schema.js           # Per-dialect schema + migrations
    │   └── drivers/            # sqlite.js / mysql.js / postgres.js
    ├── dashboard/              # Optional web dashboard (only loaded when enabled)
    │   ├── server.js           # Express app + one central security middleware chain
    │   ├── supervisor.js       # Forks and manages the bot process
    │   ├── security.js         # Session, CSRF, rate limit, client IP
    │   ├── permissions.js      # Role/user permission model
    │   ├── auth.js             # Discord OAuth (identify scope)
    │   ├── discord.js          # REST client + name resolution
    │   ├── routes.js           # API routes
    │   └── botBridge.js        # Bot-side IPC handlers (Discord actions)
    ├── handlers/
    │   ├── commandHandler.js
    │   ├── eventHandler.js
    │   └── componentHandler.js
    ├── commands/
    │   ├── setup.js            # /setup
    │   ├── close.js            # /close
    │   ├── reopen.js           # /reopen
    │   ├── add.js              # /add
    │   ├── remove.js           # /remove
    │   ├── claim.js            # /claim
    │   ├── unclaim.js          # /unclaim
    │   ├── move.js             # /move
    │   ├── rename.js           # /rename
    │   ├── transcript.js       # /transcript
    │   ├── priority.js         # /priority
    │   ├── note.js             # /note
    │   ├── blacklist.js        # /blacklist
    │   ├── stats.js            # /stats
    │   ├── snippet.js          # /snippet
    │   ├── broadcast.js        # /broadcast
    │   └── lock.js             # /lock
    ├── events/
    │   ├── ready.js            # Bot start, status, auto-close & staff reminder loop
    │   ├── messageCreate.js    # Activity tracking + DM notifications
    │   └── interactionCreate.js
    ├── components/
    │   ├── buttons/
    │   │   ├── openTicket.js
    │   │   ├── closeTicket.js
    │   │   ├── claimTicket.js
    │   │   ├── unclaimTicket.js
    │   │   ├── moveTicket.js
    │   │   ├── deleteTicket.js
    │   │   ├── deleteConfirm.js
    │   │   ├── deleteCancel.js
    │   │   ├── reopenTicket.js     # tb_reopen
    │   │   ├── rateTicket.js       # tb_rate:N
    │   │   └── notifyToggle.js     # tb_notifyToggle
    │   ├── modals/
    │   │   ├── closeReason.js
    │   │   └── ticketQuestions.js
    │   └── menus/
    │       ├── panelSelect.js
    │       ├── ticketType.js
    │       └── moveSelect.js
    └── utils/
        ├── logger.js
        ├── embeds.js
        ├── transcript.js       # Self-contained HTML (avatars embedded as Base64)
        ├── mskApi.js
        ├── ticketActions.js
        ├── permissionCheck.js  # Startup Discord-permission check + invite URL
        ├── versionCheck.js     # Startup update check against GitHub releases
        └── snippets.js         # Snippet loader & placeholder engine

🚀 Installation

Docker (shortest path)

Images are published for linux/amd64 and linux/arm64. No Node install, no build tools for the native SQLite module.

mkdir -p ticketbot/config ticketbot/data && cd ticketbot
sudo chown -R 1000:1000 config data     # the container runs as uid 1000
curl -O https://raw.githubusercontent.com/MSK-Scripts/discord_ticketbot/main/docker-compose.yml
# create .env with TOKEN, CLIENT_ID and GUILD_ID, then:
docker compose up -d

The first start writes the example config into config/ and stops, naming the placeholders you still have to replace. Fill them in and start again.

Full guide, including the dashboard and an external database: docu.msk-scripts.de → Docker

Requirements (without Docker)

1. Install dependencies

cd discord_ticketbot
npm install

2. Set up environment variables

cp .env.example .env

Fill in .env:

# Required
TOKEN="your_bot_token"
CLIENT_ID="your_application_id"
GUILD_ID="your_server_id"

# Optional: MSK Transcript Service
MSK_API_KEY="your_msk_api_key"
MSK_API_URL="https://www.msk-scripts.de"

# Optional: Database (leave unset to use the bundled SQLite file)
# MySQL/MariaDB:  mysql://user:pass@host:3306/ticketbot
# PostgreSQL:     postgres://user:pass@host:5432/ticketbot
# DATABASE_URL=""

# Optional: Web Dashboard (off unless enabled; run `npm run dashboard:setup`)
# DASHBOARD_ENABLED="false"
# See docs/dashboard-en.md for the full list.

Database backends. By default the bot stores everything in a local SQLite file (data/tickets.db), no setup needed. To use MySQL/MariaDB or PostgreSQL instead, set DATABASE_URL (append ?ssl=true for managed databases that need TLS). The schema is created automatically. To move an existing SQLite database into the new backend, run npm run db:migrate (it preserves your ticket history and stats).

3. Set up the configuration

cp config/config.example.jsonc config/config.jsonc

4. (Optional) Set up canned responses

cp config/snippets.example.jsonc config/snippets.jsonc

Edit config/snippets.jsonc to define your team's canned responses. If the file does not exist, /snippet commands will show a setup hint.

5. Start the bot

npm start

6. Set up the panel

Run /setup on your Discord server (Administrator permission required).


🖥️ Autostart with systemd (Linux Server)

1. Copy bot files

sudo cp -r discord_ticketbot /opt/discord_ticketbot
sudo useradd -r -s /bin/false discord
sudo chown -R discord:discord /opt/discord_ticketbot

2. Set up .env on the server

sudo nano /opt/discord_ticketbot/.env

3. Verify the Node.js path

which node

Adjust ExecStart in ticketbot.service if the path differs from /usr/bin/node.

4. Install the systemd unit

sudo cp /opt/discord_ticketbot/ticketbot.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now ticketbot.service

5. Check the status

sudo systemctl status ticketbot.service
sudo journalctl -u ticketbot.service -f

Useful commands

Command Description
sudo systemctl start ticketbot.service Start the bot
sudo systemctl stop ticketbot.service Stop the bot
sudo systemctl restart ticketbot.service Restart the bot
sudo systemctl enable ticketbot.service Enable autostart
sudo systemctl disable ticketbot.service Disable autostart
sudo journalctl -u ticketbot.service -f --output=cat Follow live logs with colors

⚙️ Slash Commands

Command Permission Description
/setup Administrator Send the ticket panel
/close [reason] Configurable Close the current ticket
/reopen Configurable Reopen a closed ticket
/claim Staff Claim a ticket
/unclaim Staff Release a claimed ticket
/move Staff Move ticket to a different type/category
/add <user> Staff Add a user to the ticket
/remove <user> Staff Remove a user from the ticket
/rename <name> Staff Rename the ticket channel
/transcript Staff Generate an HTML transcript
/priority <level> Staff Set ticket priority
/note add <text> Staff Add a staff note
/note list Staff List all notes for this ticket
/stats [user] Staff Server-wide or per-user statistics
/blacklist add/remove/list Manage Guild Manage the user blacklist
/snippet send <name> Staff Send a canned response into the ticket
/snippet list Staff Show all available snippets
/lock lock [reason] Staff Lock ticket, user cannot send messages
/lock unlock Staff Unlock ticket, restore user message access
/broadcast <message> Staff Send a message to all open ticket channels

🔘 Ticket Buttons

Button Visible when Description
🔒 Close Ticket Always (configurable) Generates transcript, closes & renames channel
🙋 Claim claimButton: true, unclaimed Claim the ticket
🙌 Unclaim claimButton: true, claimed Release the ticket
🔀 Move More than 1 type configured Open type selection
🗑️ Delete Ticket After closing Delete channel after confirmation
♻️ Reopen After closing (reopenOption.enabled) Reopen the ticket, restores access & moves it back to its category
🔕 Notify me userNotifications.enabled: true User opts in to DM notifications on staff reply

🛠️ Configuration Reference

Panel Interaction Type

"panel": {
  "interactionType": "BUTTON"    // "BUTTON" (default) or "SELECT_MENU"
}

Panel Logo & Banner

"panel": {
  "logo":   { "enabled": true, "file": "logo.png"   },
  "banner": { "enabled": true, "file": "banner.png" }
}

Bot Status

"status": {
  "enabled": true,
  "dynamic": false,              // true = live ticket count in status
  "dynamicText": "🎫 {open} open tickets", // placeholders: {open}, {total}, {closed}
  "dynamicInterval": 5,          // update interval in minutes
  "text": "Support Tickets",     // used when dynamic: false
  "type": "WATCHING",            // PLAYING, WATCHING, LISTENING, STREAMING, COMPETING
  "status": "online"
}

User Notifications

"userNotifications": {
  "enabled": true   // Show a 🔕 "Notify me" button in new tickets.
                    // User opts in → receives a DM when staff first replies.
                    // Rate-limited to 1 DM per 30 minutes per ticket.
}

Canned Responses (Snippets)

Snippets are defined in a separate file, not in config.jsonc:

cp config/snippets.example.jsonc config/snippets.jsonc
{
  "snippets": [
    {
      "name": "welcome",
      "description": "Welcome message at the start of a ticket",
      "content": "Hey {user}! 👋 Thanks for opening a ticket. We'll be with you shortly.",
      "embed": {
        "title": "👋 Welcome",
        "color": "#5865F2"
      }
    },
    {
      "name": "docs",
      "description": "Link to the MSK-Scripts documentation",
      "content": "Hey {user}, check out our docs: https://docu.msk-scripts.de",
      "embed": null
    }
  ]
}

Available placeholders: {user} · {staff} · {type} · {priority}

Commands: /snippet send <name> · /snippet list

Snippets support autocomplete. Start typing the name or description to filter.

Staff Reminder

"staffReminder": { "enabled": true, "afterHours": 4, "pingRoles": true }

Rating System

"ratingSystem": { "enabled": true, "dmUser": true, "ratingsChannelId": "CHANNEL_ID" }

Startup Log Visibility

"showLog": true   // Show INFO log messages on startup (commands, events, components)
                  // Set to false for a cleaner output in production

Auto-Close

"autoClose": { "enabled": true, "inactiveHours": 48, "warnBeforeHours": 6, "excludeClaimed": true }

Reopen

Closed tickets can be reopened via a ♻️ Reopen button on the closed-ticket message and the /reopen command.

"reopenOption": {
  "enabled": true,           // Master switch for the reopen feature (button + /reopen)
  "button": true,            // Show the ♻️ Reopen button on the closed-ticket message
  "whoCanReopen": "STAFFONLY" // "EVERYONE" or "STAFFONLY"
}

Reopening restores the creator's channel access, moves the channel back to its ticket type's category and drops the closed- name prefix.

Transcript Design

The HTML transcript can be rendered in a modern, minimal MSK-branded style or the classic Discord-style layout, and in English or German.

"transcriptDesign": "modern",  // "modern" (default) or "classic"
"transcriptLang": "en"         // "en" or "de", falls back to English if omitted/unsupported

Both styles are fully self-contained (offline-safe): avatars and custom emojis are embedded as Base64, user mentions and the Created by / Claimed by / Closed by fields are shown as display names instead of raw IDs, and the header includes the closer and the close reason (the reason only when one was provided). Code blocks have a copy button, and transcriptLang localizes all transcript labels and the date format.

Predefined Priority per Ticket Type

Each ticket type may define a priority that new tickets of that type start with (instead of the default medium). It is reflected in the channel topic and opening embed, and can still be changed later via /priority.

"ticketTypes": [
  {
    "codeName": "support",
    "priority": "high",   // "low", "medium", "high" or "urgent", defaults to "medium" if omitted
    // ...
  }
]

Channel State Overview

State Channel Name Channel Topic Opening Embed
Ticket opened ticket-username 🟡 Medium Priority: 🟡 Medium
/priority urgent ticket-username 🔴 Urgent Priority: 🔴 Urgent
/claim ticket-username 🟡 Medium | 🙋 Claimed by @Staff + Claimed by field
/lock lock ticket-username unchanged lock notice posted
Ticket closed closed-ticket-username unchanged all buttons removed
Ticket reopened ticket-username restored reopen embed + ticket buttons restored

🗄️ Database Schema

The database is created automatically. By default this is a local SQLite file (data/tickets.db); set DATABASE_URL to use MySQL/MariaDB or PostgreSQL instead (see Installation). The same schema and migrations apply to every backend. Missing columns are added automatically on start.

Table Contents
tickets All tickets: status, type, priority, claim, lock, notify, reminder, transcript
blacklist Blocked users with reason and timestamp
staff_notes Private staff notes per ticket
ratings Ratings (1–5 ⭐) with optional comment

Columns added in recent updates:

Column Default Purpose
locked 0 Whether the ticket is currently locked
notify_on_reply 0 Whether the creator opted in to DM notifications
last_notify_sent NULL Timestamp of the last notification DM (30-min cooldown)

🌍 Adding a New Language

  1. Copy locales/en.json, e.g. as locales/fr.json
  2. Translate all strings
  3. Set "lang": "fr" in config/config.jsonc

📖 Documentation

Full documentation: docu.msk-scripts.de


🤝 Contributing

Contributions are welcome! Please read the Contributing Guidelines before opening an issue or pull request. By participating you agree to our Code of Conduct. Found a security issue? See SECURITY.md.


📝 License

AGPL-3.0-only. Source code must remain open and be published under the same license when distributed or hosted.

One additional term under Section 7(b) of the AGPL: the notice identifying the software as MSK Ticket Bot has to stay visible to the people using the bot. Premium and Premium+ servers are exempt and may remove it for as long as the subscription runs. See NOTICE for the exact wording.

About

Self-hosted Discord ticket bot with HTML transcripts, a web dashboard and 7 languages. Discord.js v14, runs on SQLite out of the box or on your own MySQL, MariaDB or PostgreSQL. No telemetry.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages