Configuration
๐ ๏ธ Configuration Referenceโ
Startup Log Visibilityโ
"showLog": true // Show INFO log messages on startup (commands, events, components)
// Set to false for a cleaner output in production
Panel Interaction Typeโ
"panel": {
"interactionType": "BUTTON" // "BUTTON" (default) or "SELECT_MENU"
}
| Mode | Behaviour |
|---|---|
"BUTTON" | A green button is shown. Clicking it opens an ephemeral select menu, always fresh, no Discord caching issue. |
"SELECT_MENU" | The select menu is shown directly in the panel. After every use it automatically resets, so users never need to restart Discord to open a second ticket of the same type. |
Panel Logo & Bannerโ
"panel": {
"logo": { "enabled": true, "file": "logo.png" },
"banner": { "enabled": true, "file": "banner.png" }
}
Supported formats: PNG, JPG, GIF, WEBP. Run /setup again after adding or changing images.
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 |
/unclaim | ticket-username | ๐ก Medium | field removed |
/lock lock | ticket-username | unchanged | lock notice posted |
| Ticket closed | closed-ticket-username | unchanged | all buttons removed |
| Reopen | ticket-username | restored | reopen embed + ticket buttons restored |
Note on rate-limits: Discord limits channel topic changes to 2 per 10 minutes. A warning is shown in the ticket and the update appears automatically once the limit resets.
Ticket Typesโ
{
"codeName": "support",
"name": "Support",
"description": "...",
"emoji": "๐ก",
"color": "#ff0000", // Hex color or "" to use mainColor
"categoryId": "123456789",
"priority": "medium", // Predefined start priority: "low", "medium", "high" or "urgent" (defaults to "medium")
"ticketNameOption": "", // USERNAME, USERID, TICKETCOUNT or ""
"customDescription": "...", // Variables: REASON1, REASON2, USERNAME, USERID
"cantAccess": ["roleId"],
"requiredRoles": [], // Empty = everyone, otherwise one of these roles is needed
"staffRoles": [], // Type-specific staff roles, empty = global staff roles
"askQuestions": true,
"questions": [
{ "label": "Question", "placeholder": "...", "style": "SHORT", "maxLength": 500 }
]
}
staffRolesreplaces the global roles. A type with its ownstaffRolesis handled by those roles only: they see the channel, can claim, lock, move and close, and the staff reminder pings them. The globalrolesWhoHaveAccessToTheTicketsdo not act on such a ticket. If the global staff role should keep access, add it to the type'sstaffRoles. Administrators always have access.
requiredRoleslimits who can open a type. Empty (the default) means everyone may open it. With roles listed, a member needs at least one of them, otherwise the bot refuses with "This category is only open to members with a specific role." Use it for a category like script support that only sponsors may open, while order or invoice types stay open to all.cantAccessstill applies on top and wins: a member with a blocked role cannot open the type even if they hold a required role. There is no exception for staff or administrators, so give your team one of the roles if they should be able to open the type themselves. With theBUTTONpanel, the type list a member sees only offers the types they may open; if just one is left it opens directly. TheSELECT_MENUpanel is the same for everyone, so there the refusal message is shown after picking.
Open Restrictionsโ
"rolesWhoCanNotCreateTickets": [], // roles that cannot open any ticket
"maxTicketOpened": 2 // open tickets per user, 0 = unlimited
Together with cantAccess and requiredRoles per type and the /blacklist, these are checked on every way a ticket can be opened, and the open limit cannot be bypassed by clicking quickly several times.
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
}
The bot checks all open tickets every 15 minutes. Each ticket is only reminded once.
Closingโ
"closeOption": {
"closeButton": true,
"dmUser": true, // send the creator a DM when the ticket closes
"createTranscript": true,
"transcriptToUser": true, // false = transcript only in the log channel, not in the DM
"askReason": true,
"whoCanCloseTicket": "EVERYONE", // or "STAFFONLY"
"closeTicketCategoryId": "CATEGORY_ID_HERE"
}
Without an MSK API key the transcript is a self-contained HTML file with avatars, custom emojis and images (up to 5 MB) embedded. With a key it is uploaded and shared as a link, see Service Setup.
Rating Systemโ
"ratingSystem": {
"enabled": true,
"dmUser": true, // ask in the DM instead of the channel
"ratingsChannelId": "CHANNEL_ID_HERE",
"commentRoles": [] // who may use "Comment Feedback"; empty = staff
}
Only the ticket creator can rate. The post shows the ticket category and the user's avatar. Staff reply to it via right click โ Apps โ Comment Feedback; the comment is added to the embed, and a second comment by the same person replaces the first.
Auto-Closeโ
"autoClose": {
"enabled": true,
"inactiveHours": 48,
"warnBeforeHours": 6,
"excludeClaimed": true
}
/autoclose pause excludes a single ticket from the warning, the auto-close and the staff reminder. /autoclose resume starts a fresh inactivity window.
Update Notificationโ
"updateNotification": {
"enabled": true, // default on, also when the block is missing
"intervalHours": 1 // minimum 0.25
}
Posts an embed to logsChannelId when a newer release is available on GitHub. Each version is announced once, also across restarts. Without logsChannelId the check does not run.
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"
}
| Field | Description |
|---|---|
enabled | Master switch. When false, the button is hidden and /reopen replies that it is disabled. |
button | Whether the โป๏ธ Reopen button is shown on the closed-ticket message. |
whoCanReopen | "STAFFONLY" (default) requires staff. "EVERYONE" allows anyone who can see the channel. |
Reopening restores the creator's channel access, moves the channel back to its ticket type's category and drops the closed- name prefix.
Note: Closed channels are usually only visible to staff (the creator's view is removed on close), so
"EVERYONE"mainly matters if you keep closed channels visible to users.
Transcript Design & Languageโ
The generated HTML transcript can be rendered in two styles and any of the built-in languages:
"transcriptDesign": "modern", // "modern" (default) or "classic"
"transcriptLang": "en" // en, de, fr, es, pt, pl, hu. Falls back to English if omitted/unsupported
| Value | Look |
|---|---|
"modern" | Minimal, MSK-branded layout (default, also used if the key is absent). |
"classic" | The original Discord-style dark layout. |
transcriptLang localizes all transcript labels (header fields, section title, footer, copy-button tooltip) and the date format. Seven languages are built in (en, de, fr, es, pt, pl, hu); any other value falls back to English. The bot's own messages use the separate lang key, which accepts the same set.
Both styles are fully self-contained / offline-safe (no external requests):
- Avatars and custom emojis are embedded as Base64 (custom emojis fall back to
:name:text if the image can't be fetched at generation time). - User mentions and the header fields Created by / Claimed by / Closed by are shown as display names instead of raw user IDs (unresolvable IDs fall back to the ID).
- The header also shows Closed by and the close reason, the reason only when one was provided.
- Fenced code blocks show their language as a small label (e.g.
LUA) and have a copy button that copies the block to the clipboard. No syntax colouring (kept dependency-free).
Statisticsโ
/stats shows server-wide numbers. /stats @user shows a detailed profile split into ๐ค As a User and ๐ก๏ธ As Staff.