import discord from discord import app_commands from discord.ext import commands import datetime import random import string import io import asyncio import re # --- الإعدادات الثابتة --- OWNER_ID = 1429183440485486679 ERROR_LOG_CHANNEL_ID = 1488536752691085552 TICKETS_CHANNEL_ID = 1488536530019549344 LOG_CHANNEL_ID = 1488536921813680218 STAFF_ROLES_IDS = [ 1488187501142216826, 1488187641424773140, 1488187816201687181, 1488188612313874733, 1488537308700344490, 1488537566910218260 ] PING_ROLES = "<@&1488187816201687181> <@&1488188612313874733> <@&1488537308700344490> <@&1488537566910218260>" # --- نظام الحماية والقيود --- active_tickets = {} # user_id: thread_id cooldowns = {} # user_id: datetime class MyBot(commands.Bot): def __init__(self): intents = discord.Intents.default() intents.message_content = True intents.members = True super().__init__(command_prefix="!", intents=intents) async def setup_hook(self): self.add_view(TicketPanelView()) self.add_view(TicketControls()) await self.tree.sync() async def on_error(self, event, *args, **kwargs): channel = self.get_channel(ERROR_LOG_CHANNEL_ID) if channel: await channel.send(f"⚠️ **Crash Detected / خطأ في النظام**\n`{event}`\n<@{OWNER_ID}>") bot = MyBot() # --- أدوات مساعدة --- def is_staff(interaction: discord.Interaction): return any(role.id in STAFF_ROLES_IDS for role in interaction.user.roles) or interaction.user.id == OWNER_ID # --- نظام واجهة التذاكر --- class TicketPanelView(discord.ui.View): def __init__(self): super().__init__(timeout=None) @discord.ui.select( custom_id="ticket_select", placeholder="Choose ticket type / اختر نوع التذكرة", options=[ discord.SelectOption(label="Complaint against staff / شكوى ضد إداري", value="staff_complaint", emoji="⚖️"), discord.SelectOption(label="Complaint against user / شكوى ضد لاعب", value="user_complaint", emoji="👥"), discord.SelectOption(label="Bug report / بلاغ عن ثغرة", value="bug_report", emoji="🐛"), discord.SelectOption(label="Technical support / دعم فني", value="tech_support", emoji="🛠️"), ] ) async def select_callback(self, interaction: discord.Interaction, select: discord.ui.Select): user_id = interaction.user.id # تحقق من التذكرة المفتوحة if user_id in active_tickets: return await interaction.response.send_message("❌ You already have an open ticket! / لديك تذكرة مفتوحة بالفعل!", ephemeral=True) # تحقق من الـ Cooldown if user_id in cooldowns: diff = (datetime.datetime.now() - cooldowns[user_id]).total_seconds() if diff < 60: return await interaction.response.send_message(f"⏳ Wait {int(60-diff)}s / انتظر {int(60-diff)} ثانية", ephemeral=True) await interaction.response.defer(ephemeral=True) # إنشاء Thread channel = bot.get_channel(TICKETS_CHANNEL_ID) ticket_id = ''.join(random.choices(string.ascii_lowercase + string.digits, k=4)) thread_name = f"ticket-{random.randint(1000,9999)}-{ticket_id}" thread = await channel.create_thread( name=thread_name, type=discord.ChannelType.private_thread, invitable=False ) active_tickets[user_id] = thread.id await thread.add_user(interaction.user) await thread.edit(slowmode_delay=10) # رسالة الترحيب embed = discord.Embed( title="Ticket Created / تم إنشاء التذكرة", description=f"Welcome {interaction.user.mention}\n{PING_ROLES}\nYour ticket has been created, please wait...\n\nمرحباً بك، تم إنشاء تذكرتك، يرجى الانتظار...", color=discord.Color.blue() ) await thread.send(embed=embed, view=TicketControls()) # إرسال DM try: await interaction.user.send(f"✅ Your ticket has been created: {thread.jump_url}\nتم إنشاء تذكرتك بنجاح.") except: pass await interaction.followup.send(f"✅ Ticket Created: {thread.mention}", ephemeral=True) class TicketControls(discord.ui.View): def __init__(self): super().__init__(timeout=None) @discord.ui.button(label="Claim / استلام", style=discord.ButtonStyle.green, custom_id="claim_btn") async def claim(self, interaction: discord.Interaction, button: discord.ui.Button): if not is_staff(interaction): return await interaction.response.send_message("❌ Staff only / للموظفين فقط", ephemeral=True) button.disabled = True await interaction.response.edit_message(view=self) await interaction.followup.send(f"📌 Ticket claimed by / تم استلام التذكرة من قبل: {interaction.user.mention}") # إرسال DM للمستخدم thread_owner_id = next((k for k, v in active_tickets.items() if v == interaction.channel_id), None) if thread_owner_id: user = await bot.fetch_user(thread_owner_id) try: await user.send(f"👋 Your ticket has been claimed by **{interaction.user.name}**\nتم استلام تذكرتك بواسطة الموظف.") except: pass @discord.ui.button(label="Close / إغلاق", style=discord.ButtonStyle.red, custom_id="close_btn") async def close(self, interaction: discord.Interaction, button: discord.ui.Button): if not is_staff(interaction): return await interaction.response.send_message("❌ Staff only / للموظفين فقط", ephemeral=True) await interaction.response.send_modal(CloseModal()) class CloseModal(discord.ui.Modal, title="Close Ticket / إغلاق التذكرة"): reason = discord.ui.TextInput(label="Reason / السبب", placeholder="Enter closing reason...", min_length=5, required=True) async def on_submit(self, interaction: discord.Interaction): thread = interaction.channel messages = [msg async for msg in thread.history(limit=None, oldest_first=True)] # إنشاء ملف سجل transcript = "" for m in messages: transcript += f"[{m.created_at.strftime('%Y-%m-%d %H:%M')}] {m.author}: {m.content}\n" file = discord.File(io.BytesIO(transcript.encode()), filename=f"{thread.name}.txt") # إرسال اللوق log_channel = bot.get_channel(LOG_CHANNEL_ID) user_id = next((k for k, v in active_tickets.items() if v == thread.id), "Unknown") embed = discord.Embed(title="Ticket Closed / إغلاق تذكرة", color=discord.Color.red()) embed.add_field(name="User / المستخدم", value=f"<@{user_id}>") embed.add_field(name="Staff / الموظف", value=interaction.user.mention) embed.add_field(name="Reason / السبب", value=self.reason.value) embed.add_field(name="Open Time / وقت الفتح", value=thread.created_at.strftime('%Y-%m-%d %H:%M')) await log_channel.send(embed=embed, file=file) # تنظيف البيانات if user_id != "Unknown": active_tickets.pop(int(user_id), None) cooldowns[int(user_id)] = datetime.datetime.now() await interaction.response.send_message("Closing... / جارِ الإغلاق...") await thread.delete() # --- الأوامر --- @bot.tree.command(name="setup-ticket-panel", description="Setup the ticket panel (Owner Only)") async def setup_ticket(interaction: discord.Interaction): if interaction.user.id != OWNER_ID: return await interaction.response.send_message("❌ Access Denied", ephemeral=True) embed = discord.Embed( title="Support System / نظام الدعم الفني", description=( "**Rules / القوانين:**\n" "❌ No spamming tickets / يمنع فتح تذاكر عشوائية\n" "✅ Use for complaints or support / استخدم التذاكر للشكاوى والدعم فقط\n\n" "**Options / الخيارات:**\n" "• Staff Complaint / شكوى إداري\n" "• User Complaint / شكوى لاعب\n" "• Bug Report / بلاغ ثغرة\n" "• Technical Support / دعم فني" ), color=discord.Color.green() ) await interaction.response.send_message("Panel Sent!", ephemeral=True) await interaction.channel.send(embed=embed, view=TicketPanelView()) @bot.tree.command(name="help", description="Show help information") async def help_cmd(interaction: discord.Interaction): await interaction.response.send_message(f"To open a ticket, go to <#{TICKETS_CHANNEL_ID}>\nلفتح تذكرة، توجه إلى القناة المخصصة.", ephemeral=True) # --- نظام الحماية (Anti-Spam & Content Filter) --- @bot.event async def on_message(message): if message.author.bot: return if not isinstance(message.channel, discord.Thread): return if message.channel.parent_id != TICKETS_CHANNEL_ID: return # منع الروابط، الملفات، والبصمات الصوتية if re.search(r'http[s]?://', message.content) or message.attachments or message.flags.voice: # السماح فقط بالصور والفيديو allowed = True if message.attachments: for att in message.attachments: if not att.content_type or (not att.content_type.startswith('image/') and not att.content_type.startswith('video/')): allowed = False if not allowed or re.search(r'http[s]?://', message.content) or message.flags.voice: await message.delete() return await message.channel.send(f"{message.author.mention} ❌ Only text, images, and videos are allowed.\nمسموح فقط بالنصوص، الصور، والفيديو.", delete_after=5) # منع السبام (أحرف عشوائية طويلة) if len(message.content) > 50 and len(set(message.content)) < 10: await message.delete() return await message.channel.send("❌ Random characters/spam detected.", delete_after=5) import os from dotenv import load_dotenv load_dotenv() bot.run(os.getenv('TOKEN'))