| import discord |
| from discord.ext import commands |
| from discord import app_commands |
| from datetime import datetime |
| import asyncio |
| import io |
| import string |
| import random |
| from database import Database |
|
|
| CYBER_BLUE = discord.Color.from_rgb(52, 152, 219) |
| CYBER_GREEN = discord.Color.from_rgb(46, 204, 113) |
| CYBER_RED = discord.Color.from_rgb(231, 76, 60) |
| CYBER_ORANGE = discord.Color.from_rgb(230, 126, 34) |
| CYBER_PURPLE = discord.Color.from_rgb(155, 89, 182) |
| CYBER_GREY = discord.Color.from_rgb(149, 165, 166) |
|
|
| |
| class TicketCreateModal(discord.ui.Modal, title="Destek Talebi Formu"): |
| def __init__(self, category_name, category_emoji, yetkili_rol_id, kategori_id): |
| super().__init__() |
| self.category_name = category_name |
| self.category_emoji = category_emoji |
| self.yetkili_rol_id = yetkili_rol_id |
| self.kategori_id = kategori_id |
|
|
| self.reason = discord.ui.TextInput( |
| label="Destek Sebebi", |
| style=discord.TextStyle.paragraph, |
| placeholder="Lütfen destek talebinizin detaylarını yazınız...", |
| required=True, |
| max_length=500 |
| ) |
| self.priority = discord.ui.TextInput( |
| label="Öncelik Durumu", |
| style=discord.TextStyle.short, |
| placeholder="Düşük / Normal / Yüksek / Kritik", |
| default="Normal", |
| required=True, |
| max_length=20 |
| ) |
| self.add_item(self.reason) |
| self.add_item(self.priority) |
|
|
| async def on_submit(self, interaction: discord.Interaction): |
| await interaction.response.defer(ephemeral=True) |
|
|
| guild = interaction.guild |
| member = interaction.user |
|
|
| |
| row = await Database.fetch_one("SELECT COUNT(*) as count FROM ticket_log") |
| ticket_no = (row['count'] if row else 0) + 1 |
|
|
| |
| prio_text = self.priority.value.strip().lower() |
| prio_emoji = "🟡" |
| if "düşük" in prio_text or "dusuk" in prio_text: |
| prio_emoji = "🟢" |
| prio_name = "Düşük" |
| elif "yüksek" in prio_text or "yuksek" in prio_text: |
| prio_emoji = "🟠" |
| prio_name = "Yüksek" |
| elif "kritik" in prio_text: |
| prio_emoji = "🔴" |
| prio_name = "Kritik" |
| else: |
| prio_name = "Normal" |
|
|
| kategori = guild.get_channel(self.kategori_id) if self.kategori_id else None |
|
|
| |
| overwrites = { |
| guild.default_role: discord.PermissionOverwrite(view_channel=False), |
| member: 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) |
| } |
| if self.yetkili_rol_id: |
| rol = guild.get_role(self.yetkili_rol_id) |
| if rol: |
| overwrites[rol] = discord.PermissionOverwrite(view_channel=True, send_messages=True, read_message_history=True, manage_messages=True) |
|
|
| |
| try: |
| channel = await guild.create_text_channel( |
| name=f"ticket-{ticket_no}", |
| category=kategori, |
| overwrites=overwrites, |
| reason=f"Destek Talebi #{ticket_no}" |
| ) |
| except Exception as e: |
| await interaction.followup.send(f"❌ Kanal oluşturulurken hata meydana geldi: {e}", ephemeral=True) |
| return |
|
|
| |
| await Database.execute( |
| "INSERT INTO ticket_log (guild_id, kanal_id, user_id, durum, timestamp) VALUES (?, ?, ?, ?, ?)", |
| (guild.id, channel.id, member.id, "acildi", datetime.now().isoformat()) |
| ) |
|
|
| |
| embed = discord.Embed( |
| title="Araz Ticket", |
| description=f"{member.mention} tarafından ticket talebi **{datetime.now().strftime('%d.%m.%Y %H:%M')}** tarihinde oluşturuldu. <@&{self.yetkili_rol_id}> rolüne sahip yetkililer sizinle ilgilenecek.\n\n**Bilet Açılış Nedeni:**\n{self.reason.value}", |
| color=CYBER_BLUE, |
| timestamp=datetime.now() |
| ) |
| if interaction.client.user.display_avatar: |
| embed.set_thumbnail(url=interaction.client.user.display_avatar.url) |
|
|
| embed.add_field(name="Destek ID", value=f"#{ticket_no}", inline=True) |
| embed.add_field(name="Destek Kategorisi", value=f"{self.category_emoji} {self.category_name}", inline=True) |
| embed.add_field(name="Öncelik", value=f"{prio_emoji} {prio_name}", inline=True) |
| embed.add_field(name="Ortalama Yanıt Süresi", value="1 dakika - 2 dakika", inline=False) |
| embed.add_field(name="Destek Durumu", value="⚪ Yetkili bekleniyor...", inline=False) |
|
|
| |
| mentions = f"{member.mention}" |
| if self.yetkili_rol_id: |
| mentions += f" <@&{self.yetkili_rol_id}>" |
|
|
| view = TicketControlView() |
| await channel.send(content=mentions, embed=embed, view=view) |
| await interaction.followup.send(f"✅ Destek talebiniz oluşturuldu: {channel.mention}", ephemeral=True) |
|
|
|
|
| |
| class TicketPanelView(discord.ui.View): |
| def __init__(self): |
| super().__init__(timeout=None) |
|
|
| @discord.ui.button(label="Genel Destek", style=discord.ButtonStyle.primary, custom_id="ticket_cat_genel", emoji="📩") |
| async def cat_genel(self, interaction: discord.Interaction, button: discord.ui.Button): |
| cfg = await Database.fetch_one("SELECT * FROM ticket_ayarlar WHERE guild_id = ?", (interaction.guild.id,)) |
| if not cfg or not cfg.get("aktif"): |
| await interaction.response.send_message("❌ Ticket sistemi bu sunucuda aktif değil.", ephemeral=True) |
| return |
| modal = TicketCreateModal("Genel Destek", "📩", cfg.get("yetkili_rol_id"), cfg.get("kategori_id")) |
| await interaction.response.send_modal(modal) |
|
|
| @discord.ui.button(label="Yetkili Alım", style=discord.ButtonStyle.success, custom_id="ticket_cat_yetkili", emoji="🔥") |
| async def cat_yetkili(self, interaction: discord.Interaction, button: discord.ui.Button): |
| cfg = await Database.fetch_one("SELECT * FROM ticket_ayarlar WHERE guild_id = ?", (interaction.guild.id,)) |
| if not cfg or not cfg.get("aktif"): |
| await interaction.response.send_message("❌ Ticket sistemi bu sunucuda aktif değil.", ephemeral=True) |
| return |
| modal = TicketCreateModal("Yetkili Alım", "🔥", cfg.get("yetkili_rol_id"), cfg.get("kategori_id")) |
| await interaction.response.send_modal(modal) |
|
|
| @discord.ui.button(label="Ortaklık & Sponsorluk", style=discord.ButtonStyle.secondary, custom_id="ticket_cat_ortaklik", emoji="🤝") |
| async def cat_ortaklik(self, interaction: discord.Interaction, button: discord.ui.Button): |
| cfg = await Database.fetch_one("SELECT * FROM ticket_ayarlar WHERE guild_id = ?", (interaction.guild.id,)) |
| if not cfg or not cfg.get("aktif"): |
| await interaction.response.send_message("❌ Ticket sistemi bu sunucuda aktif değil.", ephemeral=True) |
| return |
| modal = TicketCreateModal("Ortaklık & Sponsorluk", "🤝", cfg.get("yetkili_rol_id"), cfg.get("kategori_id")) |
| await interaction.response.send_modal(modal) |
|
|
|
|
| |
| class PrioritySelect(discord.ui.Select): |
| def __init__(self): |
| options = [ |
| discord.SelectOption(label="Düşük", value="Düşük", emoji="🟢"), |
| discord.SelectOption(label="Normal", value="Normal", emoji="🟡"), |
| discord.SelectOption(label="Yüksek", value="Yüksek", emoji="🟠"), |
| discord.SelectOption(label="Kritik", value="Kritik", emoji="🔴") |
| ] |
| super().__init__(placeholder="Öncelik seviyesini değiştir...", min_values=1, max_values=1, options=options, custom_id="ticket_prio_select") |
|
|
| async def callback(self, interaction: discord.Interaction): |
| embed = interaction.message.embeds[0] |
| prio_val = self.values[0] |
| |
| prio_emojis = { |
| "Düşük": "🟢", |
| "Normal": "🟡", |
| "Yüksek": "🟠", |
| "Kritik": "🔴" |
| } |
| |
| |
| for idx, field in enumerate(embed.fields): |
| if field.name == "Öncelik": |
| embed.set_field_at(idx, name="Öncelik", value=f"{prio_emojis[prio_val]} {prio_val}", inline=True) |
| break |
| |
| await interaction.response.edit_message(embed=embed) |
|
|
|
|
| |
| class ManageMembersModal(discord.ui.Modal, title="Üye Ekle / Çıkar"): |
| def __init__(self, action): |
| super().__init__() |
| self.action = action |
| self.user_id = discord.ui.TextInput( |
| label="Kullanıcı ID veya Mention", |
| placeholder="Eklenecek veya çıkarılacak üyenin ID numarasını giriniz...", |
| required=True |
| ) |
| self.add_item(self.user_id) |
|
|
| async def on_submit(self, interaction: discord.Interaction): |
| |
| val = self.user_id.value.strip() |
| uid = "" |
| for char in val: |
| if char.isdigit(): |
| uid += char |
| try: |
| member_id = int(uid) |
| member = interaction.guild.get_member(member_id) |
| if not member: |
| member = await interaction.guild.fetch_member(member_id) |
| except: |
| await interaction.response.send_message("❌ Geçersiz kullanıcı ID girdiniz.", ephemeral=True) |
| return |
|
|
| if self.action == "add": |
| await interaction.channel.set_permissions(member, view_channel=True, send_messages=True, read_message_history=True, attach_files=True) |
| await interaction.response.send_message(f"✅ {member.mention} destek kanalına başarıyla eklendi.", ephemeral=False) |
| else: |
| await interaction.channel.set_permissions(member, overwrite=None) |
| await interaction.response.send_message(f"❌ {member.mention} destek kanalından çıkarıldı.", ephemeral=False) |
|
|
|
|
| |
| class AssignTicketModal(discord.ui.Modal, title="Talebi Devret"): |
| def __init__(self): |
| super().__init__() |
| self.target = discord.ui.TextInput( |
| label="Yetkili Kullanıcı veya Rol ID", |
| placeholder="Talebin devredileceği kişinin veya rolün ID numarasını yazınız...", |
| required=True |
| ) |
| self.add_item(self.target) |
|
|
| async def on_submit(self, interaction: discord.Interaction): |
| val = self.target.value.strip() |
| target_id = "" |
| for char in val: |
| if char.isdigit(): |
| target_id += char |
| try: |
| tid = int(target_id) |
| role_or_member = interaction.guild.get_role(tid) or interaction.guild.get_member(tid) |
| if not role_or_member: |
| role_or_member = await interaction.guild.fetch_member(tid) |
| except: |
| await interaction.response.send_message("❌ Geçersiz Kullanıcı veya Rol ID.", ephemeral=True) |
| return |
|
|
| await interaction.channel.set_permissions(role_or_member, view_channel=True, send_messages=True, read_message_history=True, manage_messages=True) |
| await interaction.response.send_message(f"🔄 Destek talebi başarıyla {role_or_member.mention} üzerine devredildi.", ephemeral=False) |
|
|
|
|
| |
| class InternalNoteModal(discord.ui.Modal, title="İç Not Ekle"): |
| def __init__(self): |
| super().__init__() |
| self.note = discord.ui.TextInput( |
| label="Notunuz", |
| style=discord.TextStyle.paragraph, |
| placeholder="Sadece yetkililerin göreceği dahili bir not yazın...", |
| required=True |
| ) |
| self.add_item(self.note) |
|
|
| async def on_submit(self, interaction: discord.Interaction): |
| embed = discord.Embed( |
| title="📝 Dahili Yetkili Notu", |
| description=self.note.value, |
| color=CYBER_PURPLE, |
| timestamp=datetime.now() |
| ) |
| embed.set_footer(text=f"Notu Eklendi: {interaction.user.display_name}") |
| await interaction.response.send_message(embed=embed) |
|
|
|
|
| |
| class TicketControlView(discord.ui.View): |
| def __init__(self): |
| super().__init__(timeout=None) |
| self.add_item(PrioritySelect()) |
|
|
| @discord.ui.button(label="Ticket Claim", style=discord.ButtonStyle.primary, custom_id="ticket_ctrl_claim", emoji="📥", row=0) |
| async def btn_claim(self, interaction: discord.Interaction, button: discord.ui.Button): |
| embed = interaction.message.embeds[0] |
| |
| for idx, field in enumerate(embed.fields): |
| if field.name == "Destek Durumu": |
| embed.set_field_at(idx, name="Destek Durumu", value=f"📥 Alındı (Yetkili: {interaction.user.mention})", inline=False) |
| break |
| await interaction.response.edit_message(embed=embed) |
| await interaction.followup.send(f"📥 {interaction.user.mention} talebi üstlendi.", ephemeral=False) |
|
|
| @discord.ui.button(label="Beklemede", style=discord.ButtonStyle.danger, custom_id="ticket_ctrl_pending", emoji="🔴", row=0) |
| async def btn_pending(self, interaction: discord.Interaction, button: discord.ui.Button): |
| embed = interaction.message.embeds[0] |
| for idx, field in enumerate(embed.fields): |
| if field.name == "Destek Durumu": |
| embed.set_field_at(idx, name="Destek Durumu", value="🔴 Beklemede...", inline=False) |
| break |
| await interaction.response.edit_message(embed=embed) |
|
|
| @discord.ui.button(label="İnceleniyor", style=discord.ButtonStyle.secondary, custom_id="ticket_ctrl_investigating", emoji="🔍", row=0) |
| async def btn_investigating(self, interaction: discord.Interaction, button: discord.ui.Button): |
| embed = interaction.message.embeds[0] |
| for idx, field in enumerate(embed.fields): |
| if field.name == "Destek Durumu": |
| embed.set_field_at(idx, name="Destek Durumu", value="🔍 İnceleniyor...", inline=False) |
| break |
| await interaction.response.edit_message(embed=embed) |
|
|
| @discord.ui.button(label="Çözüldü", style=discord.ButtonStyle.success, custom_id="ticket_ctrl_solved", emoji="✅", row=0) |
| async def btn_solved(self, interaction: discord.Interaction, button: discord.ui.Button): |
| embed = interaction.message.embeds[0] |
| for idx, field in enumerate(embed.fields): |
| if field.name == "Destek Durumu": |
| embed.set_field_at(idx, name="Destek Durumu", value="✅ Çözüldü.", inline=False) |
| break |
| await interaction.response.edit_message(embed=embed) |
|
|
| @discord.ui.button(style=discord.ButtonStyle.secondary, custom_id="ticket_ctrl_reset", emoji="🔄", row=0) |
| async def btn_reset(self, interaction: discord.Interaction, button: discord.ui.Button): |
| embed = interaction.message.embeds[0] |
| for idx, field in enumerate(embed.fields): |
| if field.name == "Destek Durumu": |
| embed.set_field_at(idx, name="Destek Durumu", value="⚪ Yetkili bekleniyor...", inline=False) |
| break |
| await interaction.response.edit_message(embed=embed) |
|
|
| @discord.ui.button(label="Talebi Kapat", style=discord.ButtonStyle.danger, custom_id="ticket_ctrl_close", emoji="🗑️", row=1) |
| async def btn_close(self, interaction: discord.Interaction, button: discord.ui.Button): |
| |
| await Database.execute( |
| "INSERT INTO ticket_log (guild_id, kanal_id, user_id, durum, timestamp) VALUES (?, ?, ?, ?, ?)", |
| (interaction.guild.id, interaction.channel.id, interaction.user.id, "kapandi", datetime.now().isoformat()) |
| ) |
| await interaction.response.send_message("🔒 Ticket kapatılıyor... Kanal **5 saniye** sonra tamamen silinecek.") |
| await asyncio.sleep(5) |
| try: |
| await interaction.channel.delete(reason=f"Ticket kapatıldı: {interaction.user}") |
| except: |
| pass |
|
|
| @discord.ui.button(label="Bildirim Al", style=discord.ButtonStyle.secondary, custom_id="ticket_ctrl_notif", emoji="🔔", row=1) |
| async def btn_notif(self, interaction: discord.Interaction, button: discord.ui.Button): |
| |
| await interaction.response.send_message("🔔 Bilet güncellemelerinde size bildirim gönderilecek şekilde işaretlendi.", ephemeral=True) |
|
|
| @discord.ui.button(label="Üyeleri Yönet", style=discord.ButtonStyle.secondary, custom_id="ticket_ctrl_members", emoji="👤", row=1) |
| async def btn_members(self, interaction: discord.Interaction, button: discord.ui.Button): |
| |
| select_view = discord.ui.View(timeout=60) |
| btn_add = discord.ui.Button(label="Üye Ekle", style=discord.ButtonStyle.success, emoji="➕") |
| btn_rem = discord.ui.Button(label="Üye Çıkar", style=discord.ButtonStyle.danger, emoji="➖") |
|
|
| async def add_cb(inter): |
| await inter.response.send_modal(ManageMembersModal("add")) |
| async def rem_cb(inter): |
| await inter.response.send_modal(ManageMembersModal("remove")) |
|
|
| btn_add.callback = add_cb |
| btn_rem.callback = rem_cb |
| select_view.add_item(btn_add) |
| select_view.add_item(btn_rem) |
|
|
| await interaction.response.send_message("Lütfen yapmak istediğiniz işlemi seçin:", view=select_view, ephemeral=True) |
|
|
| @discord.ui.button(label="Transcript", style=discord.ButtonStyle.secondary, custom_id="ticket_ctrl_transcript", emoji="📄", row=1) |
| async def btn_transcript(self, interaction: discord.Interaction, button: discord.ui.Button): |
| await interaction.response.defer() |
| |
| chat_log = [] |
| async for message in interaction.channel.history(limit=500, oldest_first=True): |
| time_str = message.created_at.strftime("%d.%m.%Y %H:%M") |
| chat_log.append(f"[{time_str}] {message.author.name}: {message.content}") |
|
|
| log_content = "\n".join(chat_log) |
| file_data = io.BytesIO(log_content.encode("utf-8")) |
| discord_file = discord.File(fp=file_data, filename=f"transcript-{interaction.channel.name}.txt") |
|
|
| |
| cfg = await Database.fetch_one("SELECT log_kanal_id FROM ticket_ayarlar WHERE guild_id = ?", (interaction.guild.id,)) |
| if cfg and cfg.get('log_kanal_id'): |
| log_chan = interaction.guild.get_channel(cfg['log_kanal_id']) |
| if log_chan: |
| try: |
| await log_chan.send(content=f"📄 **{interaction.channel.name}** biletine ait konuşma geçmişi:", file=discord_file) |
| file_data.seek(0) |
| except: |
| pass |
|
|
| await interaction.followup.send(content="📄 Konuşma dökümü (Transcript) oluşturuldu:", file=discord_file) |
|
|
| @discord.ui.button(label="Devret", style=discord.ButtonStyle.secondary, custom_id="ticket_ctrl_assign", emoji="🔄", row=2) |
| async def btn_assign(self, interaction: discord.Interaction, button: discord.ui.Button): |
| await interaction.response.send_modal(AssignTicketModal()) |
|
|
| @discord.ui.button(label="İç Not", style=discord.ButtonStyle.secondary, custom_id="ticket_ctrl_internal", emoji="📝", row=2) |
| async def btn_internal(self, interaction: discord.Interaction, button: discord.ui.Button): |
| await interaction.response.send_modal(InternalNoteModal()) |
|
|
|
|
| class Ticket(commands.Cog): |
| def __init__(self, bot): |
| self.bot = bot |
|
|
| @commands.hybrid_command(name="ticket-kurulum", description="Ticket sistemini kurar.") |
| @commands.has_permissions(administrator=True) |
| @app_commands.describe( |
| kategori="Ticket kanallarının oluşturulacağı kategori", |
| yetkili_rol="Ticket'lara erişimi olacak yetkili rol", |
| log_kanal="Ticket log kanalı", |
| panel_kanal="Ticket panel butonunun gönderileceği kanal" |
| ) |
| async def ticket_kurulum( |
| self, ctx: commands.Context, |
| kategori: discord.CategoryChannel, |
| yetkili_rol: discord.Role, |
| log_kanal: discord.TextChannel, |
| panel_kanal: discord.TextChannel |
| ): |
| if ctx.interaction: |
| await ctx.defer() |
|
|
| await Database.execute( |
| """INSERT OR REPLACE INTO ticket_ayarlar |
| (guild_id, kategori_id, log_kanal_id, yetkili_rol_id, panel_kanal_id, aktif) |
| VALUES (?, ?, ?, ?, ?, 1)""", |
| (ctx.guild.id, kategori.id, log_kanal.id, yetkili_rol.id, panel_kanal.id) |
| ) |
|
|
| |
| embed = discord.Embed( |
| title="🛠️ Destek Talebi Oluştur", |
| description="Bilet oluşturmadan önce aşağıdaki kuralları okuyunuz. Aşağıdaki butonlardan size uygun kategoriyi seçerek destek talebi oluşturabilirsiniz.\n\n" |
| "**Genel Kurallar:**\n" |
| "• Biletinizi **24 saat** içerisinde kontrol etmek zorundasınız.\n" |
| "• Yönetim üyelerine özel mesaj atmaktan kaçının.\n" |
| "• Sorunlarınızı bilet aracılığıyla iletişim kurun.\n\n" |
| "**Sorun Bildirimi:**\n" |
| "• Sorunu detaylı bir şekilde açıklayın.\n" |
| "• Gerekli bilgileri eksiksiz paylaşın.\n\n" |
| "3 kategori mevcut • Ortalama yanıt süresi: 1 dakika - 2 dakika", |
| color=CYBER_BLUE, |
| timestamp=datetime.now() |
| ) |
| embed.set_author(name="Araz Ticket", icon_url=ctx.guild.icon.url if ctx.guild.icon else None) |
| embed.set_thumbnail(url=ctx.guild.icon.url if ctx.guild.icon else "") |
|
|
| view = TicketPanelView() |
| panel_msg = await panel_kanal.send(embed=embed, view=view) |
|
|
| await Database.execute( |
| "UPDATE ticket_ayarlar SET panel_mesaj_id = ? WHERE guild_id = ?", |
| (panel_msg.id, ctx.guild.id) |
| ) |
|
|
| await ctx.send(f"✅ Ticket sistemi kuruldu! Panel {panel_kanal.mention} kanalına gönderildi.") |
|
|
| @commands.hybrid_command(name="ticket-kapat", description="Ticket sistemini devre dışı bırakır.") |
| @commands.has_permissions(administrator=True) |
| async def ticket_kapat(self, ctx: commands.Context): |
| if ctx.interaction: |
| await ctx.defer() |
| await Database.execute("UPDATE ticket_ayarlar SET aktif = 0 WHERE guild_id = ?", (ctx.guild.id,)) |
| await ctx.send("✅ Ticket sistemi devre dışı bırakıldı.") |
|
|
| @commands.hybrid_command(name="ticket-log", description="Ticket log kayıtlarını gösterir.") |
| @commands.has_permissions(manage_guild=True) |
| async def ticket_log(self, ctx: commands.Context): |
| if ctx.interaction: |
| await ctx.defer() |
| records = await Database.fetch_all( |
| "SELECT * FROM ticket_log WHERE guild_id = ? ORDER BY id DESC LIMIT 20", |
| (ctx.guild.id,) |
| ) |
| if not records: |
| await ctx.send("ℹ️ Ticket log kaydı bulunmamaktadır.") |
| return |
|
|
| embed = discord.Embed(title="🎫 Ticket Log Kayıtları", color=CYBER_BLUE, timestamp=datetime.now()) |
| log_str = "" |
| for r in records: |
| tarih = datetime.fromisoformat(r['timestamp']).strftime("%d.%m.%Y %H:%M") |
| log_str += f"• `{tarih}` | <@{r['user_id']}> | Kanal: `{r['kanal_id']}` | **{r['durum']}**\n" |
| embed.description = log_str[:4000] |
| await ctx.send(embed=embed) |
|
|
|
|
| async def setup(bot): |
| await bot.add_cog(Ticket(bot)) |
| bot.add_view(TicketPanelView()) |
| bot.add_view(TicketControlView()) |
|
|