Ticket bot (Python)
Opens a private support channel for each member who runs /ticket, with a button to close it.
Python 3.13
♥ 0 likes
Environment variables you'll need
| Key | What it is |
|---|---|
DISCORD_TOKEN | Your bot's token from the Discord Developer Portal. Paste it on the Secrets tab; we store it encrypted. |
TICKET_CATEGORY_ID | Optional. Category the ticket channels are created in |
SUPPORT_ROLE_ID | Optional. Role that can see and close every ticket |
Files
README.md
# Ticket bot
A Python bot built on discord.py. Your bot token is set on the Secrets tab as
DISCORD_TOKEN and reaches the bot as an environment variable.
## Commands
- `/ticket [reason]` opens a private channel only you, the bot and the support role can see. One open ticket per member.
- The Close button (or `/close` inside the ticket) deletes the channel.
## Setup
1. No privileged gateway intents are needed. Leave Message Content, Server Members
and Presence off in the Discord Developer Portal.
2. Invite the bot with the `bot` and `applications.commands` scopes and these
permissions: View Channels, Manage Channels, Send Messages, Read Message History.
3. Set these values on the Secrets tab. The ones marked optional can stay empty:
- `TICKET_CATEGORY_ID`: Optional. Category the ticket channels are created in.
- `SUPPORT_ROLE_ID`: Optional. Role that can see and close every ticket.
4. Press Deploy. The console shows a line with "Logged in as" once the bot is online.
main.py
import os
import discord
from discord import app_commands
def env_id(name):
value = os.environ.get(name, "").strip()
return int(value) if value.isdigit() else None
CATEGORY_ID = env_id("TICKET_CATEGORY_ID")
SUPPORT_ROLE_ID = env_id("SUPPORT_ROLE_ID")
def ticket_name(user_id):
return f"ticket-{user_id}"
def can_close(interaction):
channel = interaction.channel
member = interaction.user
if channel.name == ticket_name(member.id):
return True
if SUPPORT_ROLE_ID and any(r.id == SUPPORT_ROLE_ID for r in member.roles):
return True
return channel.permissions_for(member).manage_channels
async def close_ticket(interaction):
channel = interaction.channel
if interaction.guild is None or not channel.name.startswith("ticket-"):
await interaction.response.send_message(
"This only works inside a ticket channel.", ephemeral=True)
return
if not can_close(interaction):
await interaction.response.send_message(
"Only the ticket owner or support staff can close this ticket.", ephemeral=True)
return
await interaction.response.send_message("Closing this ticket.")
await channel.delete(reason=f"Ticket closed by {interaction.user}")
class CloseView(discord.ui.View):
def __init__(self):
super().__init__(timeout=None) # persistent, survives restarts
@discord.ui.button(label="Close ticket", style=discord.ButtonStyle.danger,
custom_id="ticket:close")
async def close(self, interaction, button):
await close_ticket(interaction)
class TicketBot(discord.Client):
def __init__(self):
super().__init__(intents=discord.Intents.default())
self.tree = app_commands.CommandTree(self)
async def setup_hook(self):
self.add_view(CloseView())
await self.tree.sync()
bot = TicketBot()
@bot.tree.command(name="ticket", description="Open a private support ticket")
@app_commands.describe(reason="What do you need help with?")
@app_commands.guild_only()
async def ticket(interaction: discord.Interaction, reason: str | None = None):
guild = interaction.guild
user = interaction.user
name = ticket_name(user.id)
existing = discord.utils.get(guild.text_channels, name=name)
if existing is not None:
await interaction.response.send_message(
f"You already have an open ticket: {existing.mention}", ephemeral=True)
return
await interaction.response.defer(ephemeral=True, thinking=True)
overwrites = {
guild.default_role: discord.PermissionOverwrite(view_channel=False),
user: discord.PermissionOverwrite(view_channel=True, send_messages=True,
read_message_history=True, attach_files=True),
guild.me: discord.PermissionOverwrite(view_channel=True, send_messages=True,
read_message_history=True,
manage_channels=True),
}
support = guild.get_role(SUPPORT_ROLE_ID) if SUPPORT_ROLE_ID else None
if support is not None:
overwrites[support] = discord.PermissionOverwrite(
view_channel=True, send_messages=True, read_message_history=True)
category = guild.get_channel(CATEGORY_ID) if CATEGORY_ID else None
if not isinstance(category, discord.CategoryChannel):
category = None
channel = await guild.create_text_channel(
name, overwrites=overwrites, category=category,
topic=f"Ticket for {user} ({user.id})", reason=f"Ticket opened by {user}")
text = f"{user.mention} opened a ticket."
if reason:
text += f"\nReason: {reason}"
text += "\nSupport will be with you soon. Press the button or use /close when done."
await channel.send(text, view=CloseView(),
allowed_mentions=discord.AllowedMentions(users=[user]))
await interaction.followup.send(f"Your ticket is ready: {c
requirements.txt
discord.py