LOADING JASON DISCORD HOST V1
LOADING JASON DISCORD HOST V1
Jason Discord Host V1
Ticket Bot creates a private text channel per request, hidden from @everyone and visible to the member plus an optional support role. Closing deletes the channel after a short delay. No database — state lives in the channel topic.
Enable: None beyond Guilds.
require("dotenv").config();
const {
Client,
GatewayIntentBits,
EmbedBuilder,
SlashCommandBuilder,
PermissionFlagsBits,
ChannelType,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
Events,
MessageFlags,
} = require("discord.js");
const TOKEN = process.env.DISCORD_TOKEN;
const GUILD_ID = process.env.GUILD_ID?.trim();
const TICKET_CATEGORY_ID = process.env.TICKET_CATEGORY_ID?.trim();
const SUPPORT_ROLE_ID = process.env.SUPPORT_ROLE_ID?.trim();
const CREATE_ID = "jdh_create_ticket";
const CLOSE_ID = "jdh_close_ticket";
if (!TOKEN) {
console.error("Missing DISCORD_TOKEN. Copy .env.example to .env and add your bot token.");
process.exit(1);
}
const client = new Client({
intents: [GatewayIntentBits.Guilds],
});
const commands = [
new SlashCommandBuilder().setName("ping").setDescription("Check that Ticket Bot is online."),
new SlashCommandBuilder()
.setName("ticket-setup")
.setDescription("Post a support-ticket panel in this channel.")
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild),
].map((c) => c.toJSON());
function panelEmbed() {
return new EmbedBuilder()
.setColor(0x22f0ff)
.setTitle("Need help?")
.setDescription(
"Click **Create ticket** to open a private channel with the support team. Please describe your issue after the channel appears.",
)
.setFooter({ text: "Jason Discord Host V1 · Ticket Bot" });
}
function ticketOpenedEmbed(user) {
return new EmbedBuilder()
.setColor(0x9b5cff)
.setTitle("Ticket opened")
.setDescription(`${user} thanks for reaching out. Support will reply here. Use the button below when you are done.`)
.setTimestamp();
}
async function registerCommands() {
if (GUILD_ID) {
const guild = await client.guilds.fetch(GUILD_ID);
await guild.commands.set(commands);
console.log(`Registered guild commands in ${guild.name}`);
return;
}
await client.application.commands.set(commands);
console.log("Registered global slash commands.");
}
client.once(Events.ClientReady, async () => {
console.log(`Ticket Bot online as ${client.user.tag}`);
try {
await registerCommands();
} catch (error) {
console.error("Failed to register commands:", error);
}
});
client.on(Events.InteractionCreate, async (interaction) => {
try {
if (interaction.isChatInputCommand()) {
if (interaction.commandName === "ping") {
await interaction.reply({ content: `Pong — ${client.ws.ping}ms`, flags: MessageFlags.Ephemeral });
return;
}
if (interaction.commandName === "ticket-setup") {
const row = new ActionRowBuilder().addComponents(
new ButtonBuilder().setCustomId(CREATE_ID).setLabel("Create ticket").setStyle(ButtonStyle.Primary),
);
await interaction.reply({ embeds: [panelEmbed()], components: [row] });
}
return;
}
if (!interaction.isButton()) return;
if (interaction.customId === CREATE_ID) {
if (!interaction.inGuild()) {
await interaction.reply({ content: "Tickets only work in a server.", flags: MessageFlags.Ephemeral });
return;
}
const slug = interaction.user.id.slice(-6);
const existing = interaction.guild.channels.cache.find(
(channel) => channel.name === `ticket-${slug}` || channel.topic === `ticket:${interaction.user.id}`,
);
if (existing) {
await interaction.reply({
content: `You already have an open ticket: ${existing}`,
flags: MessageFlags.Ephemeral,
});
return;
}
const supportRole = SUPPORT_ROLE_ID ? await interaction.guild.roles.fetch(SUPPORT_ROLE_ID).catch(() => null) : null;
const overwrites = [
{ id: interaction.guild.roles.everyone.id, deny: [PermissionFlagsBits.ViewChannel] },
{
id: interaction.user.id,
allow: [
PermissionFlagsBits.ViewChannel,
PermissionFlagsBits.SendMessages,
PermissionFlagsBits.ReadMessageHistory,
],
},
{
id: interaction.client.user.id,
allow: [
PermissionFlagsBits.ViewChannel,
PermissionFlagsBits.SendMessages,
PermissionFlagsBits.ManageChannels,
PermissionFlagsBits.ReadMessageHistory,
],
},
];
if (supportRole) {
overwrites.push({
id: supportRole.id,
allow: [
PermissionFlagsBits.ViewChannel,
PermissionFlagsBits.SendMessages,
PermissionFlagsBits.ReadMessageHistory,
],
});
}
const channel = await interaction.guild.channels.create({
name: `ticket-${slug}`,
type: ChannelType.GuildText,
parent: TICKET_CATEGORY_ID || undefined,
topic: `ticket:${interaction.user.id}`,
permissionOverwrites: overwrites,
reason: `Ticket opened by ${interaction.user.tag}`,
});
const closeRow = new ActionRowBuilder().addComponents(
new ButtonBuilder().setCustomId(CLOSE_ID).setLabel("Close ticket").setStyle(ButtonStyle.Danger),
);
await channel.send({
content: `${interaction.user}${supportRole ? ` ${supportRole}` : ""}`,
embeds: [ticketOpenedEmbed(interaction.user)],
components: [closeRow],
});
await interaction.reply({ content: `Ticket created: ${channel}`, flags: MessageFlags.Ephemeral });
return;
}
if (interaction.customId === CLOSE_ID) {
if (!interaction.channel || !interaction.guild) return;
const isTicket = interaction.channel.topic?.startsWith("ticket:");
if (!isTicket) {
await interaction.reply({ content: "This is not a ticket channel.", flags: MessageFlags.Ephemeral });
return;
}
const openerId = interaction.channel.topic.slice("ticket:".length);
const isStaff =
interaction.memberPermissions?.has(PermissionFlagsBits.ManageChannels) ||
(SUPPORT_ROLE_ID && interaction.member.roles.cache.has(SUPPORT_ROLE_ID));
if (interaction.user.id !== openerId && !isStaff) {
await interaction.reply({ content: "Only the ticket owner or staff can close this.", flags: MessageFlags.Ephemeral });
return;
}
await interaction.reply({ content: "Closing this ticket in 5 seconds…" });
setTimeout(() => {
interaction.channel.delete("Ticket closed").catch(() => null);
}, 5000);
}
} catch (error) {
console.error(error);
const message = error.message || "Could not handle that ticket action. Check Manage Channels permission.";
if (interaction.deferred || interaction.replied) {
await interaction.followUp({ content: message, flags: MessageFlags.Ephemeral }).catch(() => null);
} else {
await interaction.reply({ content: message, flags: MessageFlags.Ephemeral }).catch(() => null);
}
}
});
client.login(TOKEN).catch((error) => {
console.error("Login failed. Check DISCORD_TOKEN.", error);
process.exit(1);
});