Skydata001 commited on
Commit
c1cc14b
·
verified ·
1 Parent(s): 8876987

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +189 -988
main.py CHANGED
@@ -1,1024 +1,225 @@
1
- """
2
- ╔══════════════════════════════════════════════════════════════╗
3
- ║ Discord Ticket Bot - Production Ready ║
4
- ║ Built with discord.py for Hugging Face ║
5
- ║ Author: Professional Discord Bot Developer ║
6
- ╚══════════════════════════════════════════════════════════════╝
7
-
8
- الوصف: نظام تذاكر متكامل ثنائي اللغة (عربي/إنجليزي)
9
- Description: Full bilingual ticket system (Arabic/English)
10
- """
11
-
12
  import discord
13
- from discord.ext import commands
14
  from discord import app_commands
15
- import asyncio
16
- import os
17
- import sys
18
- import traceback
19
- import logging
20
- import json
21
  import random
22
  import string
23
- import datetime
 
24
  import re
25
- from collections import defaultdict
26
-
27
- # ──────────────────────────────────────────────────────────────
28
- # LOGGING SETUP | إعداد السجل
29
- # ──────────────────────────────────────────────────────────────
30
- logging.basicConfig(
31
- level=logging.INFO,
32
- format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
33
- handlers=[
34
- logging.StreamHandler(sys.stdout),
35
- logging.FileHandler("bot.log", encoding="utf-8"),
36
- ],
37
- )
38
- log = logging.getLogger("TicketBot")
39
-
40
- # ──────────────────────────────────────────────────────────────
41
- # CONFIGURATION | الإعدادات
42
- # ──────────────────────────────────────────────────────────────
43
- TOKEN = os.getenv("TOKEN") # ← يُوضع في Environment Variables على Hugging Face
44
 
 
45
  OWNER_ID = 1429183440485486679
46
- ERROR_CHANNEL_ID = 1488536752691085552 # قناة إرسال الأخطاء
47
- TICKET_CHANNEL_ID = 1488536530019549344 # القناة التي تُنشأ فيها الـ Threads
48
- LOG_CHANNEL_ID = 1488536921813680218 # قناة اللوق
49
-
50
- # رتب الموظفين التي تُنبّه داخل التذكرة
51
- STAFF_ROLE_PING_IDS = [
52
- 1488187816201687181,
53
- 1488188612313874733,
54
- 1488537308700344490,
55
- 1488537566910218260,
56
  ]
 
57
 
58
- # رتب لديها صلاحية Claim و Close
59
- ALLOWED_ROLE_IDS = [
60
- 1488187501142216826,
61
- 1488187641424773140,
62
- 1488187816201687181,
63
- 1488188612313874733,
64
- 1488537308700344490,
65
- 1488537566910218260,
66
- ]
67
-
68
- TICKET_SLOWMODE = 10 # ثواني
69
- COOLDOWN_SECONDS = 60 # ثواني بعد إغلاق التذكرة
70
-
71
- # ──────────────────────────────────────────────────────────────
72
- # IN-MEMORY STORAGE | التخزين في الذاكرة
73
- # ──────────────────────────────────────────────────────────────
74
- # { user_id: thread_id }
75
- active_tickets: dict[int, int] = {}
76
-
77
- # { thread_id: {"user_id", "category", "opened_at", "claimed_by", "ticket_name"} }
78
- ticket_data: dict[int, dict] = {}
79
-
80
- # { user_id: datetime } - وقت انتهاء الـ Cooldown
81
- cooldowns: dict[int, datetime.datetime] = {}
82
-
83
- # { user_id: [timestamps] } - لكشف السبام
84
- spam_tracker: dict[int, list] = defaultdict(list)
85
-
86
- # ──────────────────────────────────────────────────────────────
87
- # HELPER FUNCTIONS | دوال مساعدة
88
- # ──────────────────────────────────────────────────────────────
89
-
90
- def generate_ticket_name() -> str:
91
- """توليد اسم عشوائي للتذكرة مثل ticket-4932-xa"""
92
- number = random.randint(1000, 9999)
93
- suffix = "".join(random.choices(string.ascii_lowercase, k=2))
94
- return f"ticket-{number}-{suffix}"
95
-
96
-
97
- def has_staff_role(member: discord.Member) -> bool:
98
- """التحقق إذا كان العضو لديه رتبة موظف"""
99
- role_ids = {r.id for r in member.roles}
100
- return bool(role_ids.intersection(ALLOWED_ROLE_IDS))
101
-
102
-
103
- def is_owner(user: discord.User | discord.Member) -> bool:
104
- """التحقق إذا كان المستخدم هو المالك"""
105
- return user.id == OWNER_ID
106
-
107
 
108
- def now_utc() -> datetime.datetime:
109
- return datetime.datetime.now(datetime.timezone.utc)
110
-
111
-
112
- def format_duration(delta: datetime.timedelta) -> str:
113
- """تنسيق المدة الزمنية | Format duration"""
114
- total = int(delta.total_seconds())
115
- hours, remainder = divmod(total, 3600)
116
- minutes, seconds = divmod(remainder, 60)
117
- if hours:
118
- return f"{hours}h {minutes}m {seconds}s"
119
- elif minutes:
120
- return f"{minutes}m {seconds}s"
121
- else:
122
- return f"{seconds}s"
123
-
124
-
125
- def is_spam(user_id: int) -> bool:
126
- """
127
- كشف السبام: أكثر من 5 رسائل في 5 ثواني
128
- Spam detection: more than 5 messages in 5 seconds
129
- """
130
- now = now_utc()
131
- tracker = spam_tracker[user_id]
132
- # إزالة الرسائل القديمة أكثر من 5 ثواني
133
- spam_tracker[user_id] = [t for t in tracker if (now - t).total_seconds() < 5]
134
- spam_tracker[user_id].append(now)
135
- return len(spam_tracker[user_id]) > 5
136
-
137
-
138
- def contains_link(content: str) -> bool:
139
- """كشف الروابط في الرسالة"""
140
- url_pattern = re.compile(
141
- r"(https?://|www\.|discord\.gg/|\.com|\.net|\.org|\.io)", re.IGNORECASE
142
- )
143
- return bool(url_pattern.search(content))
144
-
145
-
146
- def is_random_chars(content: str) -> bool:
147
- """
148
- كشف الحروف العشوائية: نسبة الحروف غير المعروفة عالية
149
- Detect random character spam
150
- """
151
- if len(content) < 10:
152
- return False
153
- # إذا كانت نسبة الأحرف غير الأبجدية أو غير العربية عالية جداً
154
- alnum_count = sum(1 for c in content if c.isalnum() or "\u0600" <= c <= "\u06ff")
155
- return alnum_count / len(content) < 0.3
156
-
157
-
158
- def count_emojis(content: str) -> int:
159
- """عدّ الإيموجي في الرسالة"""
160
- emoji_pattern = re.compile(
161
- "[\U00010000-\U0010ffff]|[\u2600-\u26FF]|[\u2700-\u27BF]",
162
- flags=re.UNICODE,
163
- )
164
- return len(emoji_pattern.findall(content))
165
-
166
-
167
- # ──────────────────────────────────────────────────────────────
168
- # BOT SETUP | إعداد البوت
169
- # ──────────────────────────────────────────────────────────────
170
- intents = discord.Intents.default()
171
- intents.message_content = True
172
- intents.members = True
173
- intents.guilds = True
174
-
175
- bot = commands.Bot(command_prefix="!", intents=intents, help_command=None)
176
 
 
 
 
 
177
 
178
- # ──────────────────────────────────────────────────────────────
179
- # CATEGORY SELECT MENU | قائمة اختيار الفئة
180
- # ──────────────────────────────────────────────────────────────
181
- class TicketCategorySelect(discord.ui.Select):
182
- def __init__(self):
183
- options = [
184
- discord.SelectOption(
185
- label="Complaint against staff | شكوى ضد موظف",
186
- description="Report a staff member / الإبلاغ عن موظف",
187
- value="complaint_staff",
188
- emoji="👮",
189
- ),
190
- discord.SelectOption(
191
- label="Complaint against user | شكوى ضد مستخدم",
192
- description="Report another user / الإبلاغ عن مستخدم",
193
- value="complaint_user",
194
- emoji="🚨",
195
- ),
196
- discord.SelectOption(
197
- label="Bug report | الإبلاغ عن خطأ",
198
- description="Report a bug / الإبلاغ عن مشكلة تقنية",
199
- value="bug_report",
200
- emoji="🐛",
201
- ),
202
- discord.SelectOption(
203
- label="Technical support | الدعم التقني",
204
- description="Get technical help / طلب مساعدة تقنية",
205
- value="technical_support",
206
- emoji="🔧",
207
- ),
208
- ]
209
- super().__init__(
210
- placeholder="📋 اختر نوع التذكرة | Choose ticket type",
211
- min_values=1,
212
- max_values=1,
213
- options=options,
214
- custom_id="ticket_category_select",
215
- )
216
 
217
- async def callback(self, interaction: discord.Interaction):
218
- await handle_ticket_creation(interaction, self.values[0])
219
 
 
 
 
220
 
 
221
  class TicketPanelView(discord.ui.View):
222
- """View الثابت لـ Panel التذاكر"""
223
-
224
  def __init__(self):
225
  super().__init__(timeout=None)
226
- self.add_item(TicketCategorySelect())
227
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
 
229
- # ──────────────────────────────────────────────────────────────
230
- # TICKET CONTROL BUTTONS | أزرار التحكم في التذكرة
231
- # ──────────────────────────────────────────────────────────────
232
- class TicketControlView(discord.ui.View):
233
- """أزرار Claim و Close داخل التذكرة"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
 
 
235
  def __init__(self):
236
  super().__init__(timeout=None)
237
 
238
- @discord.ui.button(
239
- label="✅ Claim Ticket | استلام التذكرة",
240
- style=discord.ButtonStyle.success,
241
- custom_id="ticket_claim",
242
- )
243
- async def claim_button(self, interaction: discord.Interaction, button: discord.ui.Button):
244
- if not has_staff_role(interaction.user):
245
- await interaction.response.send_message(
246
- "❌ **ليس لديك صلاحية لاستلام التذاكر.**\n"
247
- "❌ **You don't have permission to claim tickets.**",
248
- ephemeral=True,
249
- )
250
- return
251
-
252
- thread_id = interaction.channel.id
253
- data = ticket_data.get(thread_id)
254
- if not data:
255
- await interaction.response.send_message(
256
- "❌ بيانات التذكرة غير موجودة. | Ticket data not found.", ephemeral=True
257
- )
258
- return
259
-
260
- if data.get("claimed_by"):
261
- await interaction.response.send_message(
262
- f"⚠️ **هذه التذكرة مستلمة بالفعل من قِبل:** <@{data['claimed_by']}>\n"
263
- f"⚠️ **This ticket is already claimed by:** <@{data['claimed_by']}>",
264
- ephemeral=True,
265
- )
266
- return
267
-
268
- # تسجيل الـ Claim
269
- ticket_data[thread_id]["claimed_by"] = interaction.user.id
270
-
271
  # إرسال DM للمستخدم
272
- user_id = data["user_id"]
273
- guild = interaction.guild
274
- member = guild.get_member(user_id)
275
- if member:
276
- try:
277
- dm_embed = discord.Embed(
278
- title=" تم استلام تذكرتك | Your Ticket Has Been Claimed",
279
- description=(
280
- f"🇸🇦 **تم استلام تذكرتك من قِبل:** {interaction.user.display_name}\n\n"
281
- f"🇬🇧 **Your ticket has been claimed by:** {interaction.user.display_name}\n\n"
282
- f"📋 **التذكرة:** `{data['ticket_name']}`\n"
283
- f"⏰ **الوقت:** <t:{int(now_utc().timestamp())}:F>"
284
- ),
285
- color=discord.Color.green(),
286
- timestamp=now_utc(),
287
- )
288
- dm_embed.set_footer(text="Ticket System | نظام التذاكر")
289
- await member.send(embed=dm_embed)
290
- except discord.Forbidden:
291
- pass # DM مغلق
292
-
293
- # تحديث رسالة التحكم
294
- claim_embed = discord.Embed(
295
- title="✅ تم استلام التذكرة | Ticket Claimed",
296
- description=(
297
- f"🇸🇦 **تم استلام هذه التذكرة من قِبل:** {interaction.user.mention}\n"
298
- f"🇬🇧 **This ticket has been claimed by:** {interaction.user.mention}"
299
- ),
300
- color=discord.Color.green(),
301
- timestamp=now_utc(),
302
- )
303
- await interaction.response.send_message(embed=claim_embed)
304
-
305
- log.info(f"Ticket {data['ticket_name']} claimed by {interaction.user} ({interaction.user.id})")
306
-
307
- @discord.ui.button(
308
- label="🔒 Close Ticket | إغلاق التذكرة",
309
- style=discord.ButtonStyle.danger,
310
- custom_id="ticket_close",
311
- )
312
- async def close_button(self, interaction: discord.Interaction, button: discord.ui.Button):
313
- if not has_staff_role(interaction.user):
314
- await interaction.response.send_message(
315
- "❌ **ليس لديك صلاحية لإغلاق التذاكر.**\n"
316
- "❌ **You don't have permission to close tickets.**",
317
- ephemeral=True,
318
- )
319
- return
320
-
321
- # إرسال Modal لسبب الإغلاق
322
- modal = CloseReasonModal(interaction.channel)
323
- await interaction.response.send_modal(modal)
324
-
325
-
326
- # ──────────────────────────────────────────────────────────────
327
- # CLOSE REASON MODAL | نافذة سبب الإغلاق
328
- # ──────────────────────────────────────────────────────────────
329
- class CloseReasonModal(discord.ui.Modal, title="🔒 Close Ticket | إغلاق التذكرة"):
330
- reason = discord.ui.TextInput(
331
- label="سبب الإغلاق | Close Reason",
332
- placeholder="اكتب سبب إغلاق التذكرة... | Enter the reason for closing...",
333
- style=discord.TextStyle.paragraph,
334
- min_length=5,
335
- max_length=500,
336
- required=True,
337
- )
338
-
339
- def __init__(self, thread: discord.Thread):
340
- super().__init__()
341
- self.thread = thread
342
 
343
  async def on_submit(self, interaction: discord.Interaction):
344
- await interaction.response.defer(ephemeral=True)
345
- await close_ticket(interaction, self.thread, self.reason.value)
346
-
347
-
348
- # ──────────────────────────────────────────────────────────────
349
- # CORE: CREATE TICKET | إنشاء التذكرة
350
- # ──────────────────────────────────────────────────────────────
351
- async def handle_ticket_creation(interaction: discord.Interaction, category: str):
352
- """المنطق الرئيسي لإنشاء تذكرة جديدة"""
353
- user = interaction.user
354
- guild = interaction.guild
355
-
356
- # ── 1. التحقق من Cooldown ──
357
- if user.id in cooldowns:
358
- remaining = (cooldowns[user.id] - now_utc()).total_seconds()
359
- if remaining > 0:
360
- await interaction.response.send_message(
361
- f" **يجب الانتظار {int(remaining)} ثانية قبل فتح تذكرة جديدة.**\n"
362
- f" **Please wait {int(remaining)} seconds before opening a new ticket.**",
363
- ephemeral=True,
364
- )
365
- return
366
-
367
- # ── 2. التحقق من وجود تذكرة مفتوحة ──
368
- if user.id in active_tickets:
369
- thread_id = active_tickets[user.id]
370
- await interaction.response.send_message(
371
- f"⚠️ **لديك تذكرة مفتوحة بالفعل:** <#{thread_id}>\n"
372
- f"⚠️ **You already have an open ticket:** <#{thread_id}>",
373
- ephemeral=True,
374
- )
375
- return
376
-
377
- # ── 3. رسالة تأكيد للمستخدم ──
378
- category_labels = {
379
- "complaint_staff": "👮 شكوى ضد موظف | Complaint against staff",
380
- "complaint_user": "🚨 شكوى ضد مستخدم | Complaint against user",
381
- "bug_report": "🐛 الإبلاغ عن خطأ | Bug report",
382
- "technical_support": "🔧 الدعم التقني | Technical support",
383
- }
384
-
385
- await interaction.response.send_message(
386
- embed=discord.Embed(
387
- title="⏳ جاري إنشاء تذكرتك... | Creating your ticket...",
388
- description=(
389
- f"🇸🇦 **الفئة:** {category_labels.get(category, category)}\n"
390
- f"🇬🇧 **Category:** {category_labels.get(category, category)}\n\n"
391
- "يُرجى الانتظار... | Please wait..."
392
- ),
393
- color=discord.Color.yellow(),
394
- ),
395
- ephemeral=True,
396
- )
397
-
398
- # ── 4. الحصول على قناة التذاكر ──
399
- ticket_channel = guild.get_channel(TICKET_CHANNEL_ID)
400
- if not ticket_channel:
401
- try:
402
- ticket_channel = await guild.fetch_channel(TICKET_CHANNEL_ID)
403
- except Exception:
404
- await interaction.followup.send(
405
- "❌ لم يتم العثور على قناة التذاكر. تواصل مع الإدارة.\n"
406
- "❌ Ticket channel not found. Contact an admin.",
407
- ephemeral=True,
408
- )
409
- return
410
-
411
- # ── 5. إنشاء Thread خاص ──
412
- ticket_name = generate_ticket_name()
413
- opened_at = now_utc()
414
-
415
- try:
416
- thread = await ticket_channel.create_thread(
417
- name=ticket_name,
418
- type=discord.ChannelType.private_thread,
419
- auto_archive_duration=10080, # 7 أيام
420
- invitable=False, # لا يمكن للأعضاء دعوة آخرين
421
- reason=f"Ticket by {user} ({user.id}) - {category}",
422
- )
423
- except discord.Forbidden:
424
- await interaction.followup.send(
425
- "❌ **لا أملك صلاحية إنشاء Thread. تواصل مع الإدارة.**\n"
426
- "❌ **Missing permission to create threads. Contact an admin.**",
427
- ephemeral=True,
428
- )
429
- return
430
- except Exception as e:
431
- log.error(f"Error creating thread: {e}")
432
- await interaction.followup.send(
433
- "❌ حدث خطأ عند إنشاء التذكرة. | An error occurred creating the ticket.",
434
- ephemeral=True,
435
- )
436
- return
437
-
438
- # ── 6. إضافة المستخدم للـ Thread ──
439
- try:
440
- await thread.add_user(user)
441
- except Exception as e:
442
- log.warning(f"Could not add user to thread: {e}")
443
-
444
- # ── 7. ضبط Slow Mode ──
445
- try:
446
- await thread.edit(slowmode_delay=TICKET_SLOWMODE)
447
- except Exception:
448
- pass
449
-
450
- # ── 8. تسجيل بيانات التذكرة ──
451
- ticket_data[thread.id] = {
452
- "user_id": user.id,
453
- "category": category,
454
- "opened_at": opened_at,
455
- "claimed_by": None,
456
- "ticket_name": ticket_name,
457
- "messages": [], # لتتبع المحادثة
458
- }
459
- active_tickets[user.id] = thread.id
460
-
461
- # ── 9. بناء رسالة الترحيب داخل التذكرة ──
462
- welcome_embed = discord.Embed(
463
- title=f"🎫 {ticket_name}",
464
- description=(
465
- "────────────────────────────────\n"
466
- f"🇸🇦 **مرحباً {user.mention}!**\n"
467
- f"تم إنشاء تذكرتك بنجاح. يُرجى شرح مشكلتك وسنقوم بمساعدتك في أقرب وقت.\n\n"
468
- f"🇬🇧 **Welcome {user.mention}!**\n"
469
- f"Your ticket has been created. Please describe your issue and we'll assist you shortly.\n"
470
- "────────────────────────────────\n"
471
- f"📋 **الفئة | Category:** {category_labels.get(category, category)}\n"
472
- f"⏰ **وُفتح في | Opened at:** <t:{int(opened_at.timestamp())}:F>"
473
- ),
474
- color=discord.Color.blue(),
475
- timestamp=opened_at,
476
- )
477
- welcome_embed.set_footer(text="Your ticket has been created, please wait... | تذكرتك جاهزة، يُرجى الانتظار...")
478
- welcome_embed.set_thumbnail(url=guild.icon.url if guild.icon else None)
479
-
480
- # بناء Ping للموظفين
481
- staff_pings = " ".join(f"<@&{rid}>" for rid in STAFF_ROLE_PING_IDS)
482
-
483
- # إرسال رسالة الترحيب + Ping الموظفين + أزرار التحكم
484
- control_view = TicketControlView()
485
- await thread.send(
486
- content=f"**{staff_pings}**",
487
- embed=welcome_embed,
488
- view=control_view,
489
- )
490
-
491
- # ── 10. DM تأكيد للمستخدم ──
492
- dm_embed = discord.Embed(
493
- title="✅ تم إنشاء تذكرتك | Your Ticket Has Been Created",
494
  description=(
495
- f"🇸🇦 **تم إنشاء تذكرتك بنجاح في سيرفر {guild.name}!**\n"
496
- f"يمكنك الوصول إليها عبر: <#{thread.id}>\n\n"
497
- f"🇬🇧 **Your ticket has been successfully created in {guild.name}!**\n"
498
- f"You can access it via: <#{thread.id}>\n\n"
499
- f"📋 **الاسم | Name:** `{ticket_name}`\n"
500
- f"📌 **الفئة | Category:** {category_labels.get(category, category)}\n"
501
- f" **الوقت | Time:** <t:{int(opened_at.timestamp())}:F>"
502
- ),
503
- color=discord.Color.green(),
504
- timestamp=opened_at,
505
- )
506
- dm_embed.set_footer(text=f"{guild.name} | Ticket System")
507
- try:
508
- await user.send(embed=dm_embed)
509
- except discord.Forbidden:
510
- pass # DM مغلق
511
-
512
- # ── 11. تحديث رسالة التأكيد ──
513
- await interaction.followup.send(
514
- embed=discord.Embed(
515
- title="✅ تم إنشاء التذكرة | Ticket Created",
516
- description=(
517
- f"🇸🇦 **تذكرتك جاهزة:** <#{thread.id}>\n"
518
- f"🇬🇧 **Your ticket is ready:** <#{thread.id}>"
519
- ),
520
- color=discord.Color.green(),
521
  ),
522
- ephemeral=True,
523
  )
 
 
524
 
525
- log.info(f"Ticket '{ticket_name}' created by {user} ({user.id}) - Category: {category}")
 
 
526
 
527
-
528
- # ──────────────────────────────────────────────────────────────
529
- # CORE: CLOSE TICKET | إغلاق التذكرة
530
- # ──────────────────────────────────────────────────────────────
531
- async def close_ticket(
532
- interaction: discord.Interaction,
533
- thread: discord.Thread,
534
- reason: str,
535
- ):
536
- """المنطق الرئيسي لإغلاق التذكرة وإرسال اللوق"""
537
- thread_id = thread.id
538
- data = ticket_data.get(thread_id)
539
-
540
- if not data:
541
- await interaction.followup.send(
542
- "❌ بيانات التذكرة غير موجودة. | Ticket data not found.", ephemeral=True
543
- )
544
- return
545
-
546
- closed_at = now_utc()
547
- opened_at = data["opened_at"]
548
- duration = closed_at - opened_at
549
- user_id = data["user_id"]
550
- guild = interaction.guild
551
-
552
- # ── 1. إنشاء ملف نص المحادثة ──
553
- transcript_lines = [
554
- f"╔══════════════════════════════════════════╗",
555
- f"║ TICKET TRANSCRIPT | سجل التذكرة ║",
556
- f"╚══════════════════════════════════════════╝",
557
- f"",
558
- f"Ticket Name | اسم التذكرة : {data['ticket_name']}",
559
- f"User | المستخدم : {guild.get_member(user_id) or user_id} ({user_id})",
560
- f"Staff | الموظف : {interaction.user} ({interaction.user.id})",
561
- f"Category | الفئة : {data['category']}",
562
- f"Opened At | وقت الفتح : {opened_at.strftime('%Y-%m-%d %H:%M:%S UTC')}",
563
- f"Closed At | وقت الإغلاق : {closed_at.strftime('%Y-%m-%d %H:%M:%S UTC')}",
564
- f"Duration | المدة : {format_duration(duration)}",
565
- f"Close Reason | سبب الإغلاق : {reason}",
566
- f"",
567
- f"══════════════════ MESSAGES | الرسائل ══════════════════",
568
- f"",
569
- ]
570
-
571
- # جمع رسائل الـ Thread
572
- messages_log = []
573
- try:
574
- async for msg in thread.history(limit=500, oldest_first=True):
575
- if msg.author.bot:
576
- continue
577
- ts = msg.created_at.strftime("%Y-%m-%d %H:%M:%S UTC")
578
- content = msg.content or "[Attachment/Embed]"
579
- messages_log.append(f"[{ts}] {msg.author} ({msg.author.id}): {content}")
580
- except Exception as e:
581
- log.warning(f"Error fetching thread history: {e}")
582
-
583
- transcript_lines.extend(messages_log)
584
- transcript_lines.append("")
585
- transcript_lines.append("══════════════════════════════════════════════════════")
586
- transcript_text = "\n".join(transcript_lines)
587
- transcript_bytes = transcript_text.encode("utf-8")
588
-
589
- transcript_file = discord.File(
590
- fp=__import__("io").BytesIO(transcript_bytes),
591
- filename=f"{data['ticket_name']}-transcript.txt",
592
- )
593
-
594
- # ── 2. إرسال اللوق إلى قناة السجل ──
595
- log_channel = guild.get_channel(LOG_CHANNEL_ID)
596
- if log_channel:
597
- log_embed = discord.Embed(
598
- title="📁 تذكرة مُغلقة | Ticket Closed",
599
- color=discord.Color.red(),
600
- timestamp=closed_at,
601
- )
602
- log_embed.add_field(
603
- name="🎫 التذكرة | Ticket",
604
- value=f"`{data['ticket_name']}`",
605
- inline=True,
606
- )
607
- log_embed.add_field(
608
- name="👤 المستخدم | User",
609
- value=f"<@{user_id}> (`{user_id}`)",
610
- inline=True,
611
- )
612
- log_embed.add_field(
613
- name="👮 الموظف | Staff",
614
- value=f"{interaction.user.mention} (`{interaction.user.id}`)",
615
- inline=True,
616
- )
617
- log_embed.add_field(
618
- name="⏰ وقت الفتح | Opened At",
619
- value=f"<t:{int(opened_at.timestamp())}:F>",
620
- inline=True,
621
- )
622
- log_embed.add_field(
623
- name="🔒 وقت الإغلاق | Closed At",
624
- value=f"<t:{int(closed_at.timestamp())}:F>",
625
- inline=True,
626
- )
627
- log_embed.add_field(
628
- name="⏱️ المدة | Duration",
629
- value=format_duration(duration),
630
- inline=True,
631
- )
632
- log_embed.add_field(
633
- name="📌 الفئة | Category",
634
- value=data["category"],
635
- inline=True,
636
- )
637
- log_embed.add_field(
638
- name="📝 سبب الإغلاق | Close Reason",
639
- value=reason,
640
- inline=False,
641
- )
642
- log_embed.set_footer(text="Ticket System | نظام التذاكر")
643
-
644
- try:
645
- await log_channel.send(embed=log_embed, file=transcript_file)
646
- except Exception as e:
647
- log.error(f"Error sending log: {e}")
648
-
649
- # ── 3. إشعار المستخدم بالإغلاق ──
650
- member = guild.get_member(user_id)
651
- if member:
652
- close_dm = discord.Embed(
653
- title="🔒 تم إغلاق تذكرتك | Your Ticket Has Been Closed",
654
- description=(
655
- f"🇸🇦 **تم إغلاق تذكرتك `{data['ticket_name']}` من قِبل:** {interaction.user.display_name}\n"
656
- f"**السبب:** {reason}\n\n"
657
- f"🇬🇧 **Your ticket `{data['ticket_name']}` has been closed by:** {interaction.user.display_name}\n"
658
- f"**Reason:** {reason}"
659
- ),
660
- color=discord.Color.red(),
661
- timestamp=closed_at,
662
- )
663
- close_dm.add_field(name="⏱️ مدة التذكرة | Duration", value=format_duration(duration))
664
- close_dm.set_footer(text="Thank you for contacting us | شكراً لتواصلك معنا")
665
- try:
666
- await member.send(embed=close_dm)
667
- except discord.Forbidden:
668
- pass
669
-
670
- # ── 4. إرسال رسالة إغلاق داخل الـ Thread ──
671
- close_embed = discord.Embed(
672
- title="🔒 تم إغلاق التذكرة | Ticket Closed",
673
- description=(
674
- f"🇸🇦 **تم إغلاق هذه التذكرة من قِبل:** {interaction.user.mention}\n"
675
- f"**السبب:** {reason}\n\n"
676
- f"🇬🇧 **This ticket has been closed by:** {interaction.user.mention}\n"
677
- f"**Reason:** {reason}"
678
- ),
679
- color=discord.Color.red(),
680
- timestamp=closed_at,
681
- )
682
- close_embed.add_field(name="⏱️ مدة التذكرة | Duration", value=format_duration(duration))
683
- try:
684
- await thread.send(embed=close_embed)
685
- except Exception:
686
- pass
687
-
688
- # ── 5. تنظيف البيانات وضبط Cooldown ──
689
- if user_id in active_tickets:
690
- del active_tickets[user_id]
691
- if thread_id in ticket_data:
692
- del ticket_data[thread_id]
693
-
694
- # ضبط الـ Cooldown
695
- cooldowns[user_id] = now_utc() + datetime.timedelta(seconds=COOLDOWN_SECONDS)
696
-
697
- # ── 6. أرشفة الـ Thread ──
698
- try:
699
- await thread.edit(archived=True, locked=True)
700
- except Exception as e:
701
- log.warning(f"Error archiving thread: {e}")
702
-
703
- await interaction.followup.send(
704
- "✅ **تم إغلاق التذكرة بنجاح. | Ticket closed successfully.**",
705
- ephemeral=True,
706
- )
707
-
708
- log.info(f"Ticket '{data['ticket_name']}' closed by {interaction.user} - Reason: {reason}")
709
-
710
-
711
- # ───────────────────────────────���──────────────────────────────
712
- # ANTI-SPAM: MESSAGE FILTER | فلتر الرسائل
713
- # ──────────────────────────────────────────────────────────────
714
  @bot.event
715
- async def on_message(message: discord.Message):
716
- """فلتر الرسائل داخل التذاكر"""
717
- if message.author.bot:
718
- return
719
-
720
- # التحقق هل الرسالة داخل Thread تذكرة
721
- if not isinstance(message.channel, discord.Thread):
722
- await bot.process_commands(message)
723
- return
724
-
725
- thread_id = message.channel.id
726
- if thread_id not in ticket_data:
727
- await bot.process_commands(message)
728
- return
729
-
730
- data = ticket_data[thread_id]
731
- user_id = message.author.id
732
- content = message.content or ""
733
-
734
- # ── فلتر المحتوى الممنوع ──
735
-
736
- # 1. الروابط
737
- if contains_link(content):
738
- try:
739
- await message.delete()
740
- await message.channel.send(
741
- f"⛔ {message.author.mention} **الروابط ممنوعة داخل التذاكر. | Links are not allowed in tickets.**",
742
- delete_after=5,
743
- )
744
- except Exception:
745
- pass
746
- return
747
-
748
- # 2. الملفات والمرفقات غير المسموحة
749
- for attachment in message.attachments:
750
- fname = attachment.filename.lower()
751
- # السماح بالصور والفيديو فقط
752
- allowed_extensions = (".png", ".jpg", ".jpeg", ".gif", ".webp", ".mp4", ".mov", ".mkv", ".avi")
753
- if not fname.endswith(allowed_extensions):
754
- try:
755
- await message.delete()
756
- await message.channel.send(
757
- f"⛔ {message.author.mention} **هذا النوع من الملفات ممنوع. | This file type is not allowed.**\n"
758
- f"✅ المسموح: صور وفيديو فقط | Allowed: Images and videos only",
759
- delete_after=6,
760
- )
761
- except Exception:
762
- pass
763
- return
764
-
765
- # 3. Voice Messages
766
- if message.flags.voice:
767
- try:
768
- await message.delete()
769
- await message.channel.send(
770
- f"⛔ {message.author.mention} **الرسائل الصوتية ممنوعة. | Voice messages are not allowed.**",
771
- delete_after=5,
772
- )
773
- except Exception:
774
- pass
775
- return
776
-
777
- # 4. سبام الإيموجي (أكثر من 10 إيموجي)
778
- if count_emojis(content) > 10:
779
- try:
780
- await message.delete()
781
- await message.channel.send(
782
- f"⛔ {message.author.mention} **سبام الإيموجي ممنوع. | Emoji spam is not allowed.**",
783
- delete_after=5,
784
- )
785
- except Exception:
786
- pass
787
- return
788
-
789
- # 5. سبام الحروف العشوائية
790
- if is_random_chars(content) and len(content) > 15:
791
- try:
792
- await message.delete()
793
- await message.channel.send(
794
- f"⛔ {message.author.mention} **الحروف العشوائية ممنوعة. | Random character spam is not allowed.**",
795
- delete_after=5,
796
- )
797
- except Exception:
798
- pass
799
- return
800
-
801
- # 6. سبام الرسائل (سرعة الإرسال)
802
- if is_spam(user_id):
803
- try:
804
  await message.delete()
805
- await message.channel.send(
806
- f"⛔ {message.author.mention} **أنت ترسل رسائل بسرعة كبيرة! | You are sending messages too fast!**",
807
- delete_after=5,
808
- )
809
- except Exception:
810
- pass
811
- return
812
-
813
- # حفظ الرسالة في السجل
814
- ticket_data[thread_id]["messages"].append({
815
- "author": str(message.author),
816
- "author_id": message.author.id,
817
- "content": content,
818
- "timestamp": now_utc().isoformat(),
819
- })
820
-
821
- await bot.process_commands(message)
822
-
823
-
824
- # ──────────────────────────────────────────────────────────────
825
- # SLASH COMMANDS | أوامر Slash
826
- # ──────────────────────────────────────────────────────────────
827
- @bot.tree.command(name="setup-ticket-panel", description="إعداد لوحة التذاكر | Setup ticket panel (Owner only)")
828
- async def setup_ticket_panel(interaction: discord.Interaction):
829
- """أمر إعداد Panel التذاكر - للمالك فقط"""
830
- if not is_owner(interaction.user):
831
- await interaction.response.send_message(
832
- "❌ **هذا الأمر للمالك فقط. | This command is for the owner only.**",
833
- ephemeral=True,
834
- )
835
- return
836
-
837
- panel_embed = discord.Embed(
838
- title="🎫 نظام التذاكر | Ticket System",
839
- description=(
840
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
841
- "📋 **قوانين فتح التذكرة | Ticket Rules**\n"
842
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
843
- "❌ **ممنوع | Prohibited:**\n"
844
- "• فتح تذكرة لأسباب غير مهمة أو تافهة\n"
845
- "• Opening tickets for unimportant or trivial reasons\n"
846
- "• إرسال روابط أو ملفات داخل التذكرة\n"
847
- "• Sending links or files inside tickets\n"
848
- "• السبام أو الرسائل العشوائية\n"
849
- "• Spam or random messages\n\n"
850
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
851
- "✅ **الأسباب المسموح بها | Allowed Reasons:**\n"
852
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
853
- "👮 **شكوى ضد موظف | Staff Complaint**\n"
854
- " ← إذا كان موظف تصرف بشكل خاطئ\n"
855
- " ← If a staff member acted inappropriately\n\n"
856
- "🚨 **شكوى ضد مستخدم | User Complaint**\n"
857
- " ← إذا كان مستخدم ينتهك القوانين\n"
858
- " ← If a user is violating rules\n\n"
859
- "🐛 **الإبلاغ عن خطأ | Bug Report**\n"
860
- " ← مشكلة تقنية في السيرفر أو البوت\n"
861
- " ← Technical issue with the server or bot\n\n"
862
- "🔧 **الدعم التقني | Technical Support**\n"
863
- " ← طلب المساعدة في مشكلة تقنية\n"
864
- " ← Requesting help with a technical issue\n\n"
865
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
866
- "⬇️ **اختر نوع تذكرتك من القائمة أدناه:**\n"
867
- "⬇️ **Choose your ticket type from the menu below:**"
868
- ),
869
- color=discord.Color.from_rgb(88, 101, 242),
870
- timestamp=now_utc(),
871
- )
872
- panel_embed.set_footer(
873
- text="Ticket System | نظام التذاكر • لا تفتح تذكرة إلا عند الحاجة الفعلية"
874
- )
875
- if interaction.guild.icon:
876
- panel_embed.set_thumbnail(url=interaction.guild.icon.url)
877
-
878
- view = TicketPanelView()
879
- await interaction.response.send_message(embed=panel_embed, view=view)
880
- log.info(f"Ticket panel set up by {interaction.user} ({interaction.user.id}) in #{interaction.channel.name}")
881
-
882
-
883
- @bot.tree.command(name="help", description="كيفية فتح تذكرة | How to open a ticket")
884
- async def help_command(interaction: discord.Interaction):
885
- """أمر المساعدة"""
886
- await interaction.response.send_message(
887
- embed=discord.Embed(
888
- title="📖 المساعدة | Help",
889
- description=(
890
- f"🇸🇦 **لفتح تذكرة، اذهب إلى:** <#{TICKET_CHANNEL_ID}>\n\n"
891
- f"🇬🇧 **To open a ticket, go to:** <#{TICKET_CHANNEL_ID}>"
892
- ),
893
- color=discord.Color.blue(),
894
- timestamp=now_utc(),
895
- ),
896
- ephemeral=True,
897
- )
898
-
899
-
900
- # ──────────────────────────────────────────────────────────────
901
- # BOT EVENTS | أحداث البوت
902
- # ──────────────────────────────────────────────────────────────
903
- @bot.event
904
- async def on_ready():
905
- """عند تشغيل البوت"""
906
- log.info(f"✅ Bot logged in as {bot.user} ({bot.user.id})")
907
- log.info(f"✅ Connected to {len(bot.guilds)} guild(s)")
908
-
909
- # إعادة تسجيل الـ Views الثابتة لتعمل بعد إعادة التشغيل
910
- bot.add_view(TicketPanelView())
911
- bot.add_view(TicketControlView())
912
-
913
- # مزامنة Slash Commands
914
- try:
915
- synced = await bot.tree.sync()
916
- log.info(f"✅ Synced {len(synced)} slash command(s)")
917
- except Exception as e:
918
- log.error(f"❌ Failed to sync commands: {e}")
919
-
920
- # تغيير الـ Status
921
- await bot.change_presence(
922
- activity=discord.Activity(
923
- type=discord.ActivityType.watching,
924
- name="🎫 Ticket System | نظام التذاكر",
925
- ),
926
- status=discord.Status.online,
927
- )
928
-
929
-
930
- @bot.event
931
- async def on_error(event: str, *args, **kwargs):
932
- """معالجة الأخطاء العامة"""
933
- error_text = traceback.format_exc()
934
- log.error(f"Error in event '{event}': {error_text}")
935
- await send_error_to_channel(f"**Event Error: `{event}`**\n```\n{error_text[:1800]}\n```")
936
-
937
-
938
- @bot.tree.error
939
- async def on_app_command_error(interaction: discord.Interaction, error: app_commands.AppCommandError):
940
- """معالجة أخطاء Slash Commands"""
941
- error_msg = str(error)
942
- log.error(f"Slash command error: {error_msg}")
943
-
944
- if not interaction.response.is_done():
945
- await interaction.response.send_message(
946
- f"❌ **حدث خطأ. | An error occurred.**\n```{error_msg[:500]}```",
947
- ephemeral=True,
948
- )
949
- else:
950
- await interaction.followup.send(
951
- f"❌ **حدث خطأ. | An error occurred.**\n```{error_msg[:500]}```",
952
- ephemeral=True,
953
- )
954
-
955
- await send_error_to_channel(
956
- f"**Slash Command Error**\n"
957
- f"User: {interaction.user} (`{interaction.user.id}`)\n"
958
- f"Command: `/{interaction.command.name if interaction.command else 'unknown'}`\n"
959
- f"```\n{error_msg[:1500]}\n```"
960
- )
961
-
962
-
963
- async def send_error_to_channel(message: str):
964
- """إرسال الأخطاء إلى قناة الأخطاء مع Ping المالك"""
965
- try:
966
- for guild in bot.guilds:
967
- error_channel = guild.get_channel(ERROR_CHANNEL_ID)
968
- if error_channel:
969
- await error_channel.send(
970
- f"<@{OWNER_ID}> 🚨 **حدث خطأ في البوت! | Bot Error!**\n{message[:1900]}"
971
- )
972
- break
973
- except Exception as e:
974
- log.error(f"Could not send error to channel: {e}")
975
 
 
 
 
 
976
 
977
- # ──────────────────────────────────────────────────────────────
978
- # ANTI-CRASH SYSTEM | نظام منع الانهيار
979
- # ──────────────────────────────────────────────────────────────
980
- async def main():
981
- """الدالة الرئيسية مع نظام إعادة التشغيل التلقائي"""
982
- if not TOKEN:
983
- log.critical("❌ TOKEN not found! Set TOKEN environment variable.")
984
- sys.exit(1)
985
-
986
- retry_delay = 5 # ثواني قبل إعادة الاتصال
987
-
988
- while True:
989
- try:
990
- log.info("🚀 Starting bot...")
991
- async with bot:
992
- await bot.start(TOKEN)
993
- except discord.LoginFailure:
994
- log.critical("❌ Invalid TOKEN. Please check your token.")
995
- sys.exit(1)
996
- except discord.HTTPException as e:
997
- log.error(f"HTTP error: {e}. Retrying in {retry_delay}s...")
998
- await asyncio.sleep(retry_delay)
999
- except Exception as e:
1000
- error_text = traceback.format_exc()
1001
- log.error(f"Unexpected crash: {error_text}")
1002
- # محاولة إرسال الخطأ
1003
- try:
1004
- async with bot:
1005
- await send_error_to_channel(
1006
- f"**CRASH DETECTED! | تم اكتشاف انهيار!**\n```\n{error_text[:1500]}\n```"
1007
- )
1008
- except Exception:
1009
- pass
1010
- log.info(f"🔄 Restarting in {retry_delay} seconds...")
1011
- await asyncio.sleep(retry_delay)
1012
- retry_delay = min(retry_delay * 2, 60) # زيادة التأخير تدريجياً حتى 60 ثانية
1013
- else:
1014
- break
1015
-
1016
-
1017
- # ──────────────────────────────────────────────────────────────
1018
- # ENTRY POINT | نقطة الدخول
1019
- # ──────────────────────────────────────────────────────────────
1020
- if __name__ == "__main__":
1021
- try:
1022
- asyncio.run(main())
1023
- except KeyboardInterrupt:
1024
- log.info("👋 Bot stopped by user.")
 
 
 
 
 
 
 
 
 
 
 
 
1
  import discord
 
2
  from discord import app_commands
3
+ from discord.ext import commands
4
+ import datetime
 
 
 
 
5
  import random
6
  import string
7
+ import io
8
+ import asyncio
9
  import re
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
+ # --- الإعدادات الثابتة ---
12
  OWNER_ID = 1429183440485486679
13
+ ERROR_LOG_CHANNEL_ID = 1488536752691085552
14
+ TICKETS_CHANNEL_ID = 1488536530019549344
15
+ LOG_CHANNEL_ID = 1488536921813680218
16
+ STAFF_ROLES_IDS = [
17
+ 1488187501142216826, 1488187641424773140, 1488187816201687181,
18
+ 1488188612313874733, 1488537308700344490, 1488537566910218260
 
 
 
 
19
  ]
20
+ PING_ROLES = "<@&1488187816201687181> <@&1488188612313874733> <@&1488537308700344490> <@&1488537566910218260>"
21
 
22
+ # --- نظام الحماية والقيود ---
23
+ active_tickets = {} # user_id: thread_id
24
+ cooldowns = {} # user_id: datetime
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
+ class MyBot(commands.Bot):
27
+ def __init__(self):
28
+ intents = discord.Intents.default()
29
+ intents.message_content = True
30
+ intents.members = True
31
+ super().__init__(command_prefix="!", intents=intents)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
+ async def setup_hook(self):
34
+ self.add_view(TicketPanelView())
35
+ self.add_view(TicketControls())
36
+ await self.tree.sync()
37
 
38
+ async def on_error(self, event, *args, **kwargs):
39
+ channel = self.get_channel(ERROR_LOG_CHANNEL_ID)
40
+ if channel:
41
+ await channel.send(f"⚠️ **Crash Detected / خطأ في النظام**\n`{event}`\n<@{OWNER_ID}>")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
+ bot = MyBot()
 
44
 
45
+ # --- أدوات مساعدة ---
46
+ def is_staff(interaction: discord.Interaction):
47
+ return any(role.id in STAFF_ROLES_IDS for role in interaction.user.roles) or interaction.user.id == OWNER_ID
48
 
49
+ # --- نظام واجهة التذاكر ---
50
  class TicketPanelView(discord.ui.View):
 
 
51
  def __init__(self):
52
  super().__init__(timeout=None)
 
53
 
54
+ @discord.ui.select(
55
+ custom_id="ticket_select",
56
+ placeholder="Choose ticket type / اختر نوع التذكرة",
57
+ options=[
58
+ discord.SelectOption(label="Complaint against staff / شكوى ضد إداري", value="staff_complaint", emoji="⚖️"),
59
+ discord.SelectOption(label="Complaint against user / شكوى ضد لاعب", value="user_complaint", emoji="👥"),
60
+ discord.SelectOption(label="Bug report / بلاغ عن ثغرة", value="bug_report", emoji="🐛"),
61
+ discord.SelectOption(label="Technical support / دعم فني", value="tech_support", emoji="🛠️"),
62
+ ]
63
+ )
64
+ async def select_callback(self, interaction: discord.Interaction, select: discord.ui.Select):
65
+ user_id = interaction.user.id
66
+
67
+ # تحقق من التذكرة المفتوحة
68
+ if user_id in active_tickets:
69
+ return await interaction.response.send_message("❌ You already have an open ticket! / لديك تذكرة مفتوحة بالفعل!", ephemeral=True)
70
+
71
+ # تحقق من الـ Cooldown
72
+ if user_id in cooldowns:
73
+ diff = (datetime.datetime.now() - cooldowns[user_id]).total_seconds()
74
+ if diff < 60:
75
+ return await interaction.response.send_message(f"⏳ Wait {int(60-diff)}s / انتظر {int(60-diff)} ثانية", ephemeral=True)
76
 
77
+ await interaction.response.defer(ephemeral=True)
78
+
79
+ # إنشاء Thread
80
+ channel = bot.get_channel(TICKETS_CHANNEL_ID)
81
+ ticket_id = ''.join(random.choices(string.ascii_lowercase + string.digits, k=4))
82
+ thread_name = f"ticket-{random.randint(1000,9999)}-{ticket_id}"
83
+
84
+ thread = await channel.create_thread(
85
+ name=thread_name,
86
+ type=discord.ChannelType.private_thread,
87
+ invitable=False
88
+ )
89
+
90
+ active_tickets[user_id] = thread.id
91
+ await thread.add_user(interaction.user)
92
+ await thread.edit(slowmode_delay=10)
93
+
94
+ # رسالة الترحيب
95
+ embed = discord.Embed(
96
+ title="Ticket Created / تم إنشاء التذكرة",
97
+ description=f"Welcome {interaction.user.mention}\n{PING_ROLES}\nYour ticket has been created, please wait...\n\nمرحباً بك، تم إنشاء تذكرتك، يرجى الانتظار...",
98
+ color=discord.Color.blue()
99
+ )
100
+ await thread.send(embed=embed, view=TicketControls())
101
+
102
+ # إرسال DM
103
+ try:
104
+ await interaction.user.send(f"✅ Your ticket has been created: {thread.jump_url}\nتم إنشاء تذكرتك بنجاح.")
105
+ except: pass
106
+
107
+ await interaction.followup.send(f"✅ Ticket Created: {thread.mention}", ephemeral=True)
108
 
109
+ class TicketControls(discord.ui.View):
110
  def __init__(self):
111
  super().__init__(timeout=None)
112
 
113
+ @discord.ui.button(label="Claim / استلام", style=discord.ButtonStyle.green, custom_id="claim_btn")
114
+ async def claim(self, interaction: discord.Interaction, button: discord.ui.Button):
115
+ if not is_staff(interaction):
116
+ return await interaction.response.send_message("❌ Staff only / للموظفين فقط", ephemeral=True)
117
+
118
+ button.disabled = True
119
+ await interaction.response.edit_message(view=self)
120
+ await interaction.followup.send(f"📌 Ticket claimed by / تم استلام التذكرة من قبل: {interaction.user.mention}")
121
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  # إرسال DM للمستخدم
123
+ thread_owner_id = next((k for k, v in active_tickets.items() if v == interaction.channel_id), None)
124
+ if thread_owner_id:
125
+ user = await bot.fetch_user(thread_owner_id)
126
+ try: await user.send(f"👋 Your ticket has been claimed by **{interaction.user.name}**\nتم استلام تذكرتك بواسطة الموظف.")
127
+ except: pass
128
+
129
+ @discord.ui.button(label="Close / إغلاق", style=discord.ButtonStyle.red, custom_id="close_btn")
130
+ async def close(self, interaction: discord.Interaction, button: discord.ui.Button):
131
+ if not is_staff(interaction):
132
+ return await interaction.response.send_message("❌ Staff only / للموظفين فقط", ephemeral=True)
133
+
134
+ await interaction.response.send_modal(CloseModal())
135
+
136
+ class CloseModal(discord.ui.Modal, title="Close Ticket / إغلاق التذكرة"):
137
+ reason = discord.ui.TextInput(label="Reason / السبب", placeholder="Enter closing reason...", min_length=5, required=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
  async def on_submit(self, interaction: discord.Interaction):
140
+ thread = interaction.channel
141
+ messages = [msg async for msg in thread.history(limit=None, oldest_first=True)]
142
+
143
+ # إنشاء ملف سجل
144
+ transcript = ""
145
+ for m in messages:
146
+ transcript += f"[{m.created_at.strftime('%Y-%m-%d %H:%M')}] {m.author}: {m.content}\n"
147
+
148
+ file = discord.File(io.BytesIO(transcript.encode()), filename=f"{thread.name}.txt")
149
+
150
+ # إرسال اللوق
151
+ log_channel = bot.get_channel(LOG_CHANNEL_ID)
152
+ user_id = next((k for k, v in active_tickets.items() if v == thread.id), "Unknown")
153
+
154
+ embed = discord.Embed(title="Ticket Closed / إغلاق تذكرة", color=discord.Color.red())
155
+ embed.add_field(name="User / المستخدم", value=f"<@{user_id}>")
156
+ embed.add_field(name="Staff / الموظف", value=interaction.user.mention)
157
+ embed.add_field(name="Reason / السبب", value=self.reason.value)
158
+ embed.add_field(name="Open Time / وقت الفتح", value=thread.created_at.strftime('%Y-%m-%d %H:%M'))
159
+
160
+ await log_channel.send(embed=embed, file=file)
161
+
162
+ # تنظيف البيانات
163
+ if user_id != "Unknown":
164
+ active_tickets.pop(int(user_id), None)
165
+ cooldowns[int(user_id)] = datetime.datetime.now()
166
+
167
+ await interaction.response.send_message("Closing... / جارِ الإغلاق...")
168
+ await thread.delete()
169
+
170
+ # --- الأوامر ---
171
+ @bot.tree.command(name="setup-ticket-panel", description="Setup the ticket panel (Owner Only)")
172
+ async def setup_ticket(interaction: discord.Interaction):
173
+ if interaction.user.id != OWNER_ID:
174
+ return await interaction.response.send_message("❌ Access Denied", ephemeral=True)
175
+
176
+ embed = discord.Embed(
177
+ title="Support System / نظام الدعم الفني",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  description=(
179
+ "**Rules / القوانين:**\n"
180
+ "❌ No spamming tickets / يمنع فتح تذاكر عشوائية\n"
181
+ " Use for complaints or support / استخدم التذاكر للشكاوى والدعم فقط\n\n"
182
+ "**Options / الخيارات:**\n"
183
+ " Staff Complaint / شكوى إداري\n"
184
+ " User Complaint / شكوى لاعب\n"
185
+ " Bug Report / بلاغ ثغرة\n"
186
+ "• Technical Support / دعم فني"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  ),
188
+ color=discord.Color.green()
189
  )
190
+ await interaction.response.send_message("Panel Sent!", ephemeral=True)
191
+ await interaction.channel.send(embed=embed, view=TicketPanelView())
192
 
193
+ @bot.tree.command(name="help", description="Show help information")
194
+ async def help_cmd(interaction: discord.Interaction):
195
+ await interaction.response.send_message(f"To open a ticket, go to <#{TICKETS_CHANNEL_ID}>\nلفتح تذكرة، توجه إلى القناة المخصصة.", ephemeral=True)
196
 
197
+ # --- نظام الحماية (Anti-Spam & Content Filter) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  @bot.event
199
+ async def on_message(message):
200
+ if message.author.bot: return
201
+ if not isinstance(message.channel, discord.Thread): return
202
+ if message.channel.parent_id != TICKETS_CHANNEL_ID: return
203
+
204
+ # منع الروابط، الملفات، والبصمات الصوتية
205
+ if re.search(r'http[s]?://', message.content) or message.attachments or message.flags.voice:
206
+ # السماح فقط بالصور والفيديو
207
+ allowed = True
208
+ if message.attachments:
209
+ for att in message.attachments:
210
+ if not att.content_type or (not att.content_type.startswith('image/') and not att.content_type.startswith('video/')):
211
+ allowed = False
212
+
213
+ if not allowed or re.search(r'http[s]?://', message.content) or message.flags.voice:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  await message.delete()
215
+ return await message.channel.send(f"{message.author.mention} ❌ Only text, images, and videos are allowed.\nمسموح فقط بالنصوص، الصور، والفيديو.", delete_after=5)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
 
217
+ # منع السبام (أحرف عشوائية طويلة)
218
+ if len(message.content) > 50 and len(set(message.content)) < 10:
219
+ await message.delete()
220
+ return await message.channel.send("❌ Random characters/spam detected.", delete_after=5)
221
 
222
+ import os
223
+ from dotenv import load_dotenv
224
+ load_dotenv()
225
+ bot.run(os.getenv('TOKEN'))