Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ jobs:
- name: Generate i18n files
run: bun run i18n

- name: Check ticket bans with an isolated database
run: bun run ticketban:test

- name: Run typecheck, biome, drizzle, and build checks
shell: bash
run: |
Expand Down
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ Ticket Bot is a open-source Discord bot that allows you to easily manage support

The documentation is available [here](https://github.com/Sayrix/Ticket-Bot/wiki)

### Ticket bans

Members with the **Ban Members** or **Administrator** permission can prevent a user from opening any ticket type:

- `/ticketban user:@user duration:7d` bans them for seven days. Durations accept `s`, `m`, `h`, `d`, and `w`, including combinations such as `1d12h`.
- `/ticketban user:@user` bans them for life. You can also set `duration:lifetime` or `duration:permanent` explicitly.
- `/ticketunban user:@user` lifts a ticket ban early.

Running `/ticketban` again replaces the user's current ban. Bans persist across restarts, and timed bans expire automatically, including while the bot is offline. Existing tickets and invitations are unaffected.

Before starting this version, run `bun run drizzle:push` to add the `ticket_bans` table to your bot's database.

## 💬 Discord

Ask questions and get support on our [Discord server](https://discord.gg/VasYV6MEJy).
Expand Down Expand Up @@ -47,4 +59,4 @@ to end users interacting with the bot (such as bot embeds, the bot's
software is powered by Ticket-Bot, including a link to the original
project repository or to its website.

This notice must not be removed, obscured, or replaced.
This notice must not be removed, obscured, or replaced.
20 changes: 20 additions & 0 deletions i18n/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,24 @@ const en: BaseTranslation = {
}
},
commands: {
ticketban: {
description: "Ban a user from opening tickets",
options: {
user: { description: "The user whose ticket ban to manage" },
duration: { description: "Duration (e.g. 30m, 2h, 7d, 1d12h); omit or use lifetime for a lifetime ban" }
},
not_allowed: "You need the Ban Members permission to manage ticket bans in this server.",
choose_user: "Choose a user whose ticket ban to manage.",
invalid_duration: "Enter a positive duration using s, m, h, d, or w (e.g. 30m, 7d, 1d12h), or lifetime.",
success_lifetime: "<@{userId:string}> is banned from opening tickets for life.",
success_until:
"<@{userId:string}> is banned from opening tickets until <t:{timestamp:number}:F> (<t:{timestamp:number}:R>)."
},
ticketunban: {
description: "Lift a user's ticket ban",
success: "<@{userId:string}> can open tickets again.",
not_banned: "<@{userId:string}> has no active ticket ban."
},
add: {
description: "Add someone to the current ticket",
options: {
Expand Down Expand Up @@ -107,6 +125,8 @@ const en: BaseTranslation = {
select_placeholder: "Select a ticket type"
},
open: {
banned_lifetime: "You are banned from opening tickets for life.",
banned_until: "You are banned from opening tickets until <t:{timestamp:number}:F> (<t:{timestamp:number}:R>).",
not_allowed_type: "You are not allowed to create that ticket type.",
unavailable_type: "That ticket type is not available from this panel.",
max_open_reached: "You already have the maximum number of open tickets ({limit:number}).",
Expand Down
19 changes: 19 additions & 0 deletions i18n/fr/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,23 @@ const fr: Translation = {
}
},
commands: {
ticketban: {
description: "Bannir un utilisateur de l'ouverture de tickets",
options: {
user: { description: "L'utilisateur dont vous souhaitez gérer le bannissement des tickets" },
duration: { description: "Durée (ex. 30m, 2h, 7d, 1d12h) ; omettez ou utilisez lifetime pour un bannissement à vie" }
},
not_allowed: "Vous devez avoir la permission Bannir des membres pour gérer les bannissements des tickets sur ce serveur.",
choose_user: "Choisissez un utilisateur dont vous souhaitez gérer le bannissement des tickets.",
invalid_duration: "Indiquez une durée positive avec s, m, h, d ou w (ex. 30m, 7d, 1d12h), ou lifetime.",
success_lifetime: "<@{userId}> ne peut plus ouvrir de tickets à vie.",
success_until: "<@{userId}> ne peut plus ouvrir de tickets jusqu'au <t:{timestamp}:F> (<t:{timestamp}:R>)."
},
ticketunban: {
description: "Lever le bannissement des tickets d'un utilisateur",
success: "<@{userId}> peut à nouveau ouvrir des tickets.",
not_banned: "<@{userId}> n'a aucun bannissement des tickets actif."
},
add: {
description: "Ajouter quelqu'un au ticket actuel",
options: {
Expand Down Expand Up @@ -107,6 +124,8 @@ const fr: Translation = {
select_placeholder: "Sélectionner un type de ticket"
},
open: {
banned_lifetime: "Vous ne pouvez plus ouvrir de tickets à vie.",
banned_until: "Vous ne pouvez plus ouvrir de tickets jusqu'au <t:{timestamp}:F> (<t:{timestamp}:R>).",
not_allowed_type: "Vous n'êtes pas autorisé à créer ce type de ticket.",
unavailable_type: "Ce type de ticket n'est pas disponible depuis ce panneau.",
max_open_reached: "Vous avez déjà atteint le nombre maximum de tickets ouverts ({limit}).",
Expand Down
130 changes: 130 additions & 0 deletions i18n/i18n-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,65 @@ type RootTranslation = {
};
};
commands: {
ticketban: {
/**
* B​a​n​ ​a​ ​u​s​e​r​ ​f​r​o​m​ ​o​p​e​n​i​n​g​ ​t​i​c​k​e​t​s
*/
description: string;
options: {
user: {
/**
* T​h​e​ ​u​s​e​r​ ​w​h​o​s​e​ ​t​i​c​k​e​t​ ​b​a​n​ ​t​o​ ​m​a​n​a​g​e
*/
description: string;
};
duration: {
/**
* D​u​r​a​t​i​o​n​ ​(​e​.​g​.​ ​3​0​m​,​ ​2​h​,​ ​7​d​,​ ​1​d​1​2​h​)​;​ ​o​m​i​t​ ​o​r​ ​u​s​e​ ​l​i​f​e​t​i​m​e​ ​f​o​r​ ​a​ ​l​i​f​e​t​i​m​e​ ​b​a​n
*/
description: string;
};
};
/**
* Y​o​u​ ​n​e​e​d​ ​t​h​e​ ​B​a​n​ ​M​e​m​b​e​r​s​ ​p​e​r​m​i​s​s​i​o​n​ ​t​o​ ​m​a​n​a​g​e​ ​t​i​c​k​e​t​ ​b​a​n​s​ ​i​n​ ​t​h​i​s​ ​s​e​r​v​e​r​.
*/
not_allowed: string;
/**
* C​h​o​o​s​e​ ​a​ ​u​s​e​r​ ​w​h​o​s​e​ ​t​i​c​k​e​t​ ​b​a​n​ ​t​o​ ​m​a​n​a​g​e​.
*/
choose_user: string;
/**
* E​n​t​e​r​ ​a​ ​p​o​s​i​t​i​v​e​ ​d​u​r​a​t​i​o​n​ ​u​s​i​n​g​ ​s​,​ ​m​,​ ​h​,​ ​d​,​ ​o​r​ ​w​ ​(​e​.​g​.​ ​3​0​m​,​ ​7​d​,​ ​1​d​1​2​h​)​,​ ​o​r​ ​l​i​f​e​t​i​m​e​.
*/
invalid_duration: string;
/**
* <​@​{​u​s​e​r​I​d​}​>​ ​i​s​ ​b​a​n​n​e​d​ ​f​r​o​m​ ​o​p​e​n​i​n​g​ ​t​i​c​k​e​t​s​ ​f​o​r​ ​l​i​f​e​.
* @param {string} userId
*/
success_lifetime: RequiredParams<"userId">;
/**
* <​@​{​u​s​e​r​I​d​}​>​ ​i​s​ ​b​a​n​n​e​d​ ​f​r​o​m​ ​o​p​e​n​i​n​g​ ​t​i​c​k​e​t​s​ ​u​n​t​i​l​ ​<​t​:​{​t​i​m​e​s​t​a​m​p​}​:​F​>​ ​(​<​t​:​{​t​i​m​e​s​t​a​m​p​}​:​R​>​)​.
* @param {number} timestamp
* @param {string} userId
*/
success_until: RequiredParams<"timestamp" | "timestamp" | "userId">;
};
ticketunban: {
/**
* L​i​f​t​ ​a​ ​u​s​e​r​'​s​ ​t​i​c​k​e​t​ ​b​a​n
*/
description: string;
/**
* <​@​{​u​s​e​r​I​d​}​>​ ​c​a​n​ ​o​p​e​n​ ​t​i​c​k​e​t​s​ ​a​g​a​i​n​.
* @param {string} userId
*/
success: RequiredParams<"userId">;
/**
* <​@​{​u​s​e​r​I​d​}​>​ ​h​a​s​ ​n​o​ ​a​c​t​i​v​e​ ​t​i​c​k​e​t​ ​b​a​n​.
* @param {string} userId
*/
not_banned: RequiredParams<"userId">;
};
add: {
/**
* A​d​d​ ​s​o​m​e​o​n​e​ ​t​o​ ​t​h​e​ ​c​u​r​r​e​n​t​ ​t​i​c​k​e​t
Expand Down Expand Up @@ -304,6 +363,15 @@ type RootTranslation = {
select_placeholder: string;
};
open: {
/**
* Y​o​u​ ​a​r​e​ ​b​a​n​n​e​d​ ​f​r​o​m​ ​o​p​e​n​i​n​g​ ​t​i​c​k​e​t​s​ ​f​o​r​ ​l​i​f​e​.
*/
banned_lifetime: string;
/**
* Y​o​u​ ​a​r​e​ ​b​a​n​n​e​d​ ​f​r​o​m​ ​o​p​e​n​i​n​g​ ​t​i​c​k​e​t​s​ ​u​n​t​i​l​ ​<​t​:​{​t​i​m​e​s​t​a​m​p​}​:​F​>​ ​(​<​t​:​{​t​i​m​e​s​t​a​m​p​}​:​R​>​)​.
* @param {number} timestamp
*/
banned_until: RequiredParams<"timestamp" | "timestamp">;
/**
* Y​o​u​ ​a​r​e​ ​n​o​t​ ​a​l​l​o​w​e​d​ ​t​o​ ​c​r​e​a​t​e​ ​t​h​a​t​ ​t​i​c​k​e​t​ ​t​y​p​e​.
*/
Expand Down Expand Up @@ -1004,6 +1072,60 @@ export type TranslationFunctions = {
};
};
commands: {
ticketban: {
/**
* Ban a user from opening tickets
*/
description: () => LocalizedString;
options: {
user: {
/**
* The user whose ticket ban to manage
*/
description: () => LocalizedString;
};
duration: {
/**
* Duration (e.g. 30m, 2h, 7d, 1d12h); omit or use lifetime for a lifetime ban
*/
description: () => LocalizedString;
};
};
/**
* You need the Ban Members permission to manage ticket bans in this server.
*/
not_allowed: () => LocalizedString;
/**
* Choose a user whose ticket ban to manage.
*/
choose_user: () => LocalizedString;
/**
* Enter a positive duration using s, m, h, d, or w (e.g. 30m, 7d, 1d12h), or lifetime.
*/
invalid_duration: () => LocalizedString;
/**
* <@{userId}> is banned from opening tickets for life.
*/
success_lifetime: (arg: { userId: string }) => LocalizedString;
/**
* <@{userId}> is banned from opening tickets until <t:{timestamp}:F> (<t:{timestamp}:R>).
*/
success_until: (arg: { timestamp: number; userId: string }) => LocalizedString;
};
ticketunban: {
/**
* Lift a user's ticket ban
*/
description: () => LocalizedString;
/**
* <@{userId}> can open tickets again.
*/
success: (arg: { userId: string }) => LocalizedString;
/**
* <@{userId}> has no active ticket ban.
*/
not_banned: (arg: { userId: string }) => LocalizedString;
};
add: {
/**
* Add someone to the current ticket
Expand Down Expand Up @@ -1253,6 +1375,14 @@ export type TranslationFunctions = {
select_placeholder: () => LocalizedString;
};
open: {
/**
* You are banned from opening tickets for life.
*/
banned_lifetime: () => LocalizedString;
/**
* You are banned from opening tickets until <t:{timestamp}:F> (<t:{timestamp}:R>).
*/
banned_until: (arg: { timestamp: number }) => LocalizedString;
/**
* You are not allowed to create that ticket type.
*/
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"node:start": "node dist/src/index.js",
"migrate:v3": "bun src/migrate-v3-db.ts",
"migrate:v3:config": "bun src/migrate-v3-config.ts",
"migrate:v3:test": "bun scripts/verify-migrate-v3.mjs"
"migrate:v3:test": "bun scripts/verify-migrate-v3.mjs",
"ticketban:test": "bun scripts/verify-ticketban.mjs"
},
"dependencies": {
"@discordjs/core": "2.6.0",
Expand Down
2 changes: 2 additions & 0 deletions scripts/verify-migrate-v3.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ async function verifyBasicMigration() {
await runMigration(["--source", sourceUrl, "--target", targetUrl, ...(firstPanelKey ? ["--panel-key", firstPanelKey] : [])]);

await withClient(targetUrl, async (target) => {
const bans = await target.execute("SELECT * FROM ticket_bans");
assert(bans.rows.length === 0, "basic migration should create the ticket bans table without banning anyone.");
const tickets = await target.execute("SELECT * FROM tickets ORDER BY id ASC");
assert(tickets.rows.length === 2, "basic migration should copy two tickets.");
assert(Number(tickets.rows[0].id) === 10, "basic migration should preserve ticket IDs.");
Expand Down
Loading
Loading