- Katılım
- 9 Şub 2024
- Mesajlar
- 925
- Tepkime puanı
- 1,765
- Puanları
- 93
jhaeger
Discord Botunda Ticket (Destek Talebi) Sistemi Nasıl Kurulur?
discord.js v14 ile butonlu panel, özel kanal ve kayıt (transcript) sistemi
discord.js v14 ile butonlu panel, özel kanal ve kayıt (transcript) sistemi
Ticket sistemi nedir?
Üye panelden bir butona basar, bot sadece o üyenin ve destek ekibinin görebileceği özel bir kanal açar. Sorun çözülünce ticket kapatılır, konuşmanın kaydı log kanalına gönderilir. Şunlar için kullanılır:
- Destek ve şikayet talepleri
- Satış, ödeme ve başvuru işlemleri
- Yetkililerle birebir ve gizli görüşme
Sistem nasıl çalışır?
- Yönetici
/ticketpanelkomutunu yazar, bot kanala "Ticket Aç" butonlu bir mesaj atar. - Üye butona basınca bot belirlenen kategoride özel kanal oluşturur. Kanalı sadece üye, destek rolü ve bot görür.
- Kanal içinde "Ticketi Kapat" butonu bulunur.
- Kapatılınca konuşmanın metin kaydı log kanalına dosya olarak gider ve kanal silinir.
Gerekenler
- Node.js 18 veya üstü, discord.js v14 (
npm i discord.js dotenv) - Developer Portal > Bot sekmesinde Message Content Intent açık olmalı (kayıt için mesaj içeriği okunur)
- Botun sunucuda şu yetkileri olmalı: Kanalları Yönet, Kanalı Görüntüle, Mesaj Gönder, Bağlantı Yerleştir, Dosya Ekle, Mesaj Geçmişini Oku
- Ticketların açılacağı bir kategori, bir destek ekibi rolü ve bir log kanalı (ID'lerini alın: Geliştirici Modu > sağ tık > ID'yi Kopyala)
- Token'ı koda yazmayın,
.envdosyasında tutun
Kod
JavaScript:
require('dotenv').config();
const {
Client, GatewayIntentBits, Events, EmbedBuilder, ActionRowBuilder,
ButtonBuilder, ButtonStyle, ChannelType, PermissionFlagsBits,
AttachmentBuilder, MessageFlags
} = require('discord.js');
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent
]
});
const config = {
categoryId: 'TICKET_KATEGORI_ID',
supportRoleId: 'DESTEK_EKIBI_ROL_ID',
logChannelId: 'LOG_KANAL_ID'
};
client.once(Events.ClientReady, async () => {
// Sadece yöneticilerin kullanabildiği panel komutu
await client.application.commands.create({
name: 'ticketpanel',
description: 'Ticket panelini bu kanala gönderir',
defaultMemberPermissions: PermissionFlagsBits.Administrator
});
console.log(`${client.user.tag} aktif!`);
});
/* ---------- TICKET AÇ ---------- */
async function openTicket(interaction) {
await interaction.deferReply({ flags: MessageFlags.Ephemeral }); // kanal oluşturma 3 sn'yi aşabilir
const { guild, user } = interaction;
// Kullanıcının zaten açık ticket'ı var mı? (kanal konusuna kullanıcı ID'sini yazıyoruz)
const existing = guild.channels.cache.find(
(c) => c.parentId === config.categoryId && c.topic === user.id
);
if (existing) {
return interaction.editReply(`Zaten açık bir ticketın var: ${existing}`);
}
const channel = await guild.channels.create({
name: `ticket-${user.username}`,
type: ChannelType.GuildText,
parent: config.categoryId,
topic: user.id,
permissionOverwrites: [
{ id: guild.roles.everyone.id, deny: [PermissionFlagsBits.ViewChannel] },
{
id: user.id,
allow: [
PermissionFlagsBits.ViewChannel,
PermissionFlagsBits.SendMessages,
PermissionFlagsBits.ReadMessageHistory,
PermissionFlagsBits.AttachFiles
]
},
{
id: config.supportRoleId,
allow: [
PermissionFlagsBits.ViewChannel,
PermissionFlagsBits.SendMessages,
PermissionFlagsBits.ReadMessageHistory,
PermissionFlagsBits.AttachFiles
]
},
{
id: client.user.id,
allow: [
PermissionFlagsBits.ViewChannel,
PermissionFlagsBits.SendMessages,
PermissionFlagsBits.ReadMessageHistory,
PermissionFlagsBits.ManageChannels
]
}
]
});
const embed = new EmbedBuilder()
.setColor(0x5865f2)
.setTitle('Destek Talebi')
.setDescription(
`Merhaba ${user}, ticketın oluşturuldu.\n` +
`Sorununu yaz, destek ekibi en kısa sürede yanıt verecek.\n\n` +
`Bittiğinde aşağıdaki butonla ticketı kapatabilirsin.`
)
.setTimestamp();
const row = new ActionRowBuilder().addComponents(
new ButtonBuilder()
.setCustomId('ticket_close')
.setLabel('Ticketi Kapat')
.setEmoji('🔒')
.setStyle(ButtonStyle.Danger)
);
await channel.send({
content: `${user} <@&${config.supportRoleId}>`,
embeds: [embed],
components: [row]
});
await interaction.editReply(`Ticketın açıldı: ${channel}`);
}
/* ---------- KAYIT (TRANSCRIPT) ---------- */
async function buildTranscript(channel) {
const all = [];
let lastId;
while (all.length < 1000) {
const batch = await channel.messages.fetch({ limit: 100, before: lastId });
if (batch.size === 0) break;
all.push(...batch.values());
lastId = batch.last().id;
}
return all
.reverse()
.map((m) => {
const time = m.createdAt.toLocaleString('tr-TR');
return `[${time}] ${m.author.tag}: ${m.content || '(embed veya dosya)'}`;
})
.join('\n');
}
/* ---------- TICKET KAPAT ---------- */
async function closeTicket(interaction) {
const channel = interaction.channel;
const isOwner = channel.topic === interaction.user.id;
const isSupport = interaction.member.roles.cache.has(config.supportRoleId);
if (!isOwner && !isSupport) {
return interaction.reply({
content: 'Bu ticketı kapatma yetkin yok.',
flags: MessageFlags.Ephemeral
});
}
await interaction.reply('Ticket 5 saniye içinde kapatılıyor...');
const logChannel = interaction.guild.channels.cache.get(config.logChannelId);
if (logChannel) {
const text = await buildTranscript(channel);
const file = new AttachmentBuilder(Buffer.from(text || 'Mesaj yok', 'utf-8'), {
name: `${channel.name}.txt`
});
await logChannel.send({
embeds: [
new EmbedBuilder()
.setColor(0xed4245)
.setTitle('Ticket Kapatıldı')
.addFields(
{ name: 'Kanal', value: channel.name, inline: true },
{ name: 'Kapatan', value: `${interaction.user}`, inline: true }
)
.setTimestamp()
],
files: [file]
});
}
setTimeout(() => channel.delete().catch(() => {}), 5000);
}
/* ---------- ETKİLEŞİMLER ---------- */
client.on(Events.InteractionCreate, async (interaction) => {
try {
// /ticketpanel komutu
if (interaction.isChatInputCommand() && interaction.commandName === 'ticketpanel') {
const embed = new EmbedBuilder()
.setColor(0x57f287)
.setTitle('Destek Merkezi')
.setDescription('Yardıma mı ihtiyacın var? Aşağıdaki butona basarak destek talebi oluştur.');
const row = new ActionRowBuilder().addComponents(
new ButtonBuilder()
.setCustomId('ticket_open')
.setLabel('Ticket Aç')
.setEmoji('🎫')
.setStyle(ButtonStyle.Primary)
);
await interaction.channel.send({ embeds: [embed], components: [row] });
return interaction.reply({ content: 'Panel gönderildi.', flags: MessageFlags.Ephemeral });
}
// Butonlar
if (interaction.isButton()) {
if (interaction.customId === 'ticket_open') return await openTicket(interaction);
if (interaction.customId === 'ticket_close') return await closeTicket(interaction);
}
} catch (err) {
console.error('Ticket hatası:', err);
const msg = { content: 'Bir hata oluştu, lütfen yetkililere haber ver.', flags: MessageFlags.Ephemeral };
if (interaction.deferred || interaction.replied) interaction.followUp(msg).catch(() => {});
else interaction.reply(msg).catch(() => {});
}
});
client.login(process.env.TOKEN);
Nasıl kullanılır?
- Kodu
index.jsolarak kaydedin,.envdosyasınaTOKEN=...yazın. configbölümündeki üç ID'yi kendi sunucunuza göre doldurun.- Botu
node index.jsile başlatın. - Panel isteyeceğiniz kanalda
/ticketpanelyazın. - Test için başka bir hesapla butona basın, mesaj yazın ve ticketı kapatıp log kanalındaki dosyayı kontrol edin.
Sık yapılan hatalar
- Missing Permissions: Botta Kanalları Yönet yetkisi yoktur veya kategoride botun izni kısıtlanmıştır. Bot, ticket kanalına verdiği izinlere (Dosya Ekle gibi) kendisi de sahip olmak zorundadır.
- Unknown Interaction: Butona basınca cevap 3 saniyeden geç gelmiştir. Kodda
deferReplybu yüzden kullanılıyor. Botun iki yerde birden açık olmadığından da emin olun. - Invalid Form Body (parent_id): Kategori ID'si yanlıştır veya girdiğiniz ID bir kategori değil, normal bir kanala aittir.
- Kayıt dosyası boş veya mesajlar görünmüyor: Message Content Intent kapalıdır. Developer Portal'dan açıp botu yeniden başlatın.
- Used disallowed intents: Aynı intent sorunu, kodda istenmiş ama portalda açılmamıştır.
- Kanal açılmıyor: Bir kategoride en fazla 50, sunucuda en fazla 500 kanal olabilir. Çok ticket açılıyorsa eski kanalların silindiğinden emin olun.
- Gizlilik: Konuşma kayıtları saklandığı için sunucu kurallarında bunu belirtmeniz iyi olur.
Geliştirme fikirleri
- Panelde seçim menüsü ile kategori seçtirmek (Destek, Satış, Şikayet)
- Ticket açılırken modal (form) ile konu sormak
- "Ticketi Üstlen" butonu ile hangi yetkilinin ilgilendiğini göstermek
- Kapatırken sebep sormak ve kullanıcıya transcript'i DM ile göndermek
- Ticket numarası vermek ve ticketları veritabanında tutmak: "Discord Botunda Veritabanı Kullanmak" rehberine bakabilirsiniz
- Log kanalı ve rolleri
/ayarlakomutuyla değiştirtmek
Sorusu olan veya hata alan olursa konunun altına yazsın, birlikte çözelim.




