Spaces:
Sleeping
Sleeping
Upload 4 files
Browse files- Dockerfile +30 -0
- bot.py +457 -0
- keep_alive.py +17 -0
- requirements.txt +5 -0
Dockerfile
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.10-slim
|
| 2 |
+
|
| 3 |
+
# === تثبيت المتطلبات النظامية مع caching ===
|
| 4 |
+
RUN apt-get update && \
|
| 5 |
+
apt-get install -y --no-install-recommends \
|
| 6 |
+
ffmpeg gcc libffi-dev python3-dev curl && \
|
| 7 |
+
apt-get clean && \
|
| 8 |
+
rm -rf /var/lib/apt/lists/*
|
| 9 |
+
|
| 10 |
+
# === مستخدم آمن ===
|
| 11 |
+
RUN useradd -m -u 1000 user
|
| 12 |
+
|
| 13 |
+
# === مجلد البيانات ===
|
| 14 |
+
RUN mkdir -p /data && chown -R 1000:1000 /data
|
| 15 |
+
|
| 16 |
+
WORKDIR /app
|
| 17 |
+
|
| 18 |
+
# === تثبيت المكتبات مع caching أفضل (مهم جداً) ===
|
| 19 |
+
COPY --chown=user requirements.txt .
|
| 20 |
+
RUN pip install --no-cache-dir --upgrade pip && \
|
| 21 |
+
pip install --no-cache-dir -r requirements.txt
|
| 22 |
+
|
| 23 |
+
# === نسخ الكود ===
|
| 24 |
+
COPY --chown=user . .
|
| 25 |
+
|
| 26 |
+
USER user
|
| 27 |
+
|
| 28 |
+
EXPOSE 7860
|
| 29 |
+
|
| 30 |
+
CMD ["python", "bot.py"]
|
bot.py
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import discord
|
| 2 |
+
from discord.ext import commands
|
| 3 |
+
from discord import app_commands
|
| 4 |
+
from discord.ui import Button, View, Select
|
| 5 |
+
import asyncio
|
| 6 |
+
import random
|
| 7 |
+
import os
|
| 8 |
+
from datetime import datetime
|
| 9 |
+
|
| 10 |
+
# --- الإعدادات الأساسية (الثوابت) ---
|
| 11 |
+
TARGET_GUILD_ID = 1463693244897693779 # أيدي السيرفر المسموح
|
| 12 |
+
OWNER_ROLE_ID = 1492516779598155877 # أيدي رتبة المالك للتحكم
|
| 13 |
+
|
| 14 |
+
# --- الأذكار والتذكيرات (القائمة الكاملة بدون اختصار) ---
|
| 15 |
+
REMINDERS = [
|
| 16 |
+
"🌸 سبحان الله وبحمده، سبحان الله العظيم",
|
| 17 |
+
"✨ اللهم صل وسلم على نبينا محمد",
|
| 18 |
+
"📿 أستغفر الله العظيم وأتوب إليه",
|
| 19 |
+
"🕊️ لا حول ولا قوة إلا بالله",
|
| 20 |
+
"🌿 سبحان الله، والحمد لله، ولا إله إلا الله، والله أكبر",
|
| 21 |
+
"🌸 سبحان الله",
|
| 22 |
+
"✨ الحمد لله",
|
| 23 |
+
"📿 الله أكبر",
|
| 24 |
+
"🕊️ لا إله إلا الله",
|
| 25 |
+
"🌿 اللهم اغفر لي",
|
| 26 |
+
"🌸 اللهم ارحمني",
|
| 27 |
+
"✨ اللهم تب علي",
|
| 28 |
+
"📿 اللهم ارزقني",
|
| 29 |
+
"🕊️ حسبي الله ونعم الوكيل",
|
| 30 |
+
"🌿 توكلت على الله",
|
| 31 |
+
"🌸 رب اغفر لي ولوالدي",
|
| 32 |
+
"✨ اللهم اجعلني من الصالحين",
|
| 33 |
+
"📿 اللهم اهدني",
|
| 34 |
+
"🕊️ اللهم ثبتني",
|
| 35 |
+
"🌿 اللهم ارزقني الجنة",
|
| 36 |
+
"🌸 اللهم أجرني من النار",
|
| 37 |
+
"✨ يا رب",
|
| 38 |
+
"📿 يا الله",
|
| 39 |
+
"🕊️ يا حي يا قيوم برحمتك أستغيث",
|
| 40 |
+
"🌿 اللهم إني أسألك العافية",
|
| 41 |
+
"🌸 اللهم إني أسألك الهدى والتقى",
|
| 42 |
+
"✨ اللهم إني أعوذ بك من الهم والحزن",
|
| 43 |
+
"📿 اللهم إني أعوذ بك من العجز والكسل",
|
| 44 |
+
"🕊️ اللهم إني أعوذ بك من الجبن والبخل",
|
| 45 |
+
"🌿 اللهم إني أعوذ بك من غلبة الدين وقهر الرجال",
|
| 46 |
+
"🌸 اللهم اجعل القرآن ربيع قلبي",
|
| 47 |
+
"✨ اللهم نور قلبي",
|
| 48 |
+
"📿 اللهم طهر قلبي",
|
| 49 |
+
"🕊️ اللهم أصلح حالي",
|
| 50 |
+
"🌿 اللهم اجعلني من الذاكرين",
|
| 51 |
+
"🌸 اللهم اجعلني من الشاكرين",
|
| 52 |
+
"✨ اللهم اجعلني من الصابرين",
|
| 53 |
+
"📿 اللهم اجعلني من التوابين",
|
| 54 |
+
"🕊️ اللهم اجعلني من المتقين",
|
| 55 |
+
"🌿 اللهم اجعلني من المحسنين",
|
| 56 |
+
"🌸 اللهم ارزقني الإخلاص",
|
| 57 |
+
"✨ اللهم ارزقني الصدق",
|
| 58 |
+
"📿 اللهم ارزقني حسن الخاتمة",
|
| 59 |
+
"🕊️ اللهم اجعل قبري روضة من رياض الجنة",
|
| 60 |
+
"🌿 اللهم اجعلني من أهل الجنة",
|
| 61 |
+
"🌸 اللهم ارزقني الفردوس الأعلى",
|
| 62 |
+
"✨ اللهم لا تحرمني من رحمتك",
|
| 63 |
+
"📿 اللهم لا تكلني إلى نفسي",
|
| 64 |
+
"🕊️ اللهم ارحم موتانا",
|
| 65 |
+
"🌿 اللهم اشف مرضانا",
|
| 66 |
+
"🌸 اللهم فرج همومنا",
|
| 67 |
+
"✨ اللهم اقض ديوننا",
|
| 68 |
+
"📿 اللهم وسع رزقنا",
|
| 69 |
+
"🕊️ اللهم بارك لنا",
|
| 70 |
+
"🌿 اللهم احفظنا",
|
| 71 |
+
"🌸 اللهم انصرنا",
|
| 72 |
+
"✨ اللهم اجعلنا من عبادك الصالحين",
|
| 73 |
+
"📿 اللهم ارزقنا رضاك",
|
| 74 |
+
"🕊️ اللهم اجعلنا من أهل الذكر",
|
| 75 |
+
"🌿 اللهم ارزقنا حبك",
|
| 76 |
+
"🌸 اللهم ارزقنا حب نبيك",
|
| 77 |
+
"✨ اللهم اجعلنا من المتوكلين",
|
| 78 |
+
"📿 اللهم ارزقنا الصبر",
|
| 79 |
+
"🕊️ اللهم ارزقنا اليقين",
|
| 80 |
+
"🌿 اللهم اجعلنا من الذاكرين الله كثيرا",
|
| 81 |
+
"🌸 اللهم اجعلنا من القانتين",
|
| 82 |
+
"✨ اللهم ارزقنا حسن الظن بك",
|
| 83 |
+
"📿 اللهم اجعلنا من المقبولين",
|
| 84 |
+
"🕊️ اللهم ارزقنا لذة النظر إلى وجهك",
|
| 85 |
+
"🌿 اللهم اجعلنا من الفائزين",
|
| 86 |
+
"🌸 اللهم اجعلنا من أهل الفردوس",
|
| 87 |
+
"✨ اللهم اجعلنا من عتقائك من النار",
|
| 88 |
+
"📿 اللهم اجعلنا من المستغفرين",
|
| 89 |
+
"🕊️ اللهم اجعلنا من الراجعين إليك",
|
| 90 |
+
"🌿 اللهم اجعلنا من المنيبين",
|
| 91 |
+
"🌸 اللهم ارزقنا التوبة النصوح",
|
| 92 |
+
"✨ اللهم تقبل منا",
|
| 93 |
+
"📿 اللهم اغفر لنا",
|
| 94 |
+
"🕊️ اللهم ارحمنا",
|
| 95 |
+
"🌿 اللهم تب علينا",
|
| 96 |
+
"🌸 اللهم اهدنا",
|
| 97 |
+
"✨ اللهم احفظنا من كل سوء",
|
| 98 |
+
"📿 اللهم اجعلنا من الصالحين",
|
| 99 |
+
"🕊️ اللهم ارزقنا الجنة بغير حساب",
|
| 100 |
+
"🌿 ا��لهم اجعلنا من أهل القرآن",
|
| 101 |
+
"🌸 اللهم اجعل القرآن شفيعاً لنا",
|
| 102 |
+
"✨ اللهم اجعل القرآن حجة لنا",
|
| 103 |
+
"📿 اللهم اجعلنا من الذين يستمعون القول فيتبعون أحسنه",
|
| 104 |
+
"🕊️ اللهم اجعلنا من أولي الألباب",
|
| 105 |
+
"🌿 اللهم ارزقنا العلم النافع",
|
| 106 |
+
"🌸 اللهم ارزقنا العمل الصالح",
|
| 107 |
+
"✨ اللهم ارزقنا الإيمان الكامل",
|
| 108 |
+
"📿 اللهم اجعلنا من أهل التقوى",
|
| 109 |
+
"🕊️ اللهم اجعلنا من الصادقين",
|
| 110 |
+
"🌿 اللهم اجعلنا من المخلصين",
|
| 111 |
+
"🌸 اللهم اجعلنا من عبادك المقربين",
|
| 112 |
+
"✨ اللهم اجعلنا من الذاكرين الشاكرين",
|
| 113 |
+
"📿 اللهم اجعلنا من الصابرين المحتسبين"
|
| 114 |
+
]
|
| 115 |
+
|
| 116 |
+
# --- قائمة 30 قارئ (بدون اختصار) ---
|
| 117 |
+
RECITERS = [
|
| 118 |
+
{"name": "إذاعة تلاوات منوعة", "desc": "بث مباشر لتلاوات مختارة", "url": "https://qurango.net/radio/tarateel"},
|
| 119 |
+
{"name": "عبدالباسط عبدالصمد", "desc": "تلاوة مجودة بصوت خاشع", "url": "https://qurango.net/radio/abdulbasit_abdulsamad_mjawwad"},
|
| 120 |
+
{"name": "مشاري العفاسي", "desc": "تلاوة هادئة ومريحة", "url": "https://qurango.net/radio/mishary_alafasi"},
|
| 121 |
+
{"name": "ياسر الدوسري", "desc": "تلاوات مميزة من الحرم", "url": "https://qurango.net/radio/yasser_aldosari"},
|
| 122 |
+
{"name": "ماهر المعيقلي", "desc": "تلاوة عذبة وشهيرة", "url": "https://qurango.net/radio/maher_al_muaiqly"},
|
| 123 |
+
{"name": "أحمد بن علي العجمي", "desc": "تلاوة خاشعة", "url": "https://qurango.net/radio/ahmad_alajmy"},
|
| 124 |
+
{"name": "سعد الغامدي", "desc": "تلاوة مريحة للقلب", "url": "https://qurango.net/radio/saad_alghamidi"},
|
| 125 |
+
{"name": "سعود الشريم", "desc": "من تلاوات الحرم المكي", "url": "https://qurango.net/radio/saud_alshuraim"},
|
| 126 |
+
{"name": "عبدالرحمن السديس", "desc": "تلاوة الحرم المكي الشريف", "url": "https://qurango.net/radio/abdulrahman_alsudaes"},
|
| 127 |
+
{"name": "محمود خليل الحصري", "desc": "تلاوة مرتلة دقيقة", "url": "https://qurango.net/radio/mahmoud_khalil_alhussary"},
|
| 128 |
+
{"name": "علي عبدالله جابر", "desc": "تلاوة مميزة", "url": "https://qurango.net/radio/ali_jaber"},
|
| 129 |
+
{"name": "محمد أيوب", "desc": "تلاوة حجازية", "url": "https://qurango.net/radio/mohammed_ayyub"},
|
| 130 |
+
{"name": "محمد صديق المنشاوي", "desc": "تلاوة خاشعة جداً", "url": "https://qurango.net/radio/mohammed_siddiq_alminshawi"},
|
| 131 |
+
{"name": "أبوبكر الشاطري", "desc": "تلاوة هادئة", "url": "https://qurango.net/radio/abubakr_alshatri"},
|
| 132 |
+
{"name": "عبدالله بصفر", "desc": "تلاوة عذبة", "url": "https://qurango.net/radio/abdullah_basfer"},
|
| 133 |
+
{"name": "عبدالمحسن القاسم", "desc": "إمام المسجد النبوي", "url": "https://qurango.net/radio/abdulmohsin_alqasim"},
|
| 134 |
+
{"name": "عبدالودود حنيف", "desc": "تلاوة رائعة", "url": "https://qurango.net/radio/abdulwadood_haneef"},
|
| 135 |
+
{"name": "علي عبدالرحمن الحذيفي", "desc": "من تلاوات المسجد النبوي", "url": "https://qurango.net/radio/ali_alhuthaifi"},
|
| 136 |
+
{"name": "فارس عباد", "desc": "تلاوة مؤثرة", "url": "https://qurango.net/radio/fares_abbad"},
|
| 137 |
+
{"name": "محمد جبريل", "desc": "تلاوة مشهورة", "url": "https://qurango.net/radio/mohammed_jibreel"},
|
| 138 |
+
{"name": "محمد الطبلاوي", "desc": "من كبار القراء", "url": "https://qurango.net/radio/mohammad_altablaway"},
|
| 139 |
+
{"name": "محمود علي البنا", "desc": "تلاوة كلاسيكية", "url": "https://qurango.net/radio/mahmoud_ali__albanna"},
|
| 140 |
+
{"name": "مصطفى إسماعيل", "desc": "تلاوة مجودة", "url": "https://qurango.net/radio/mustafa_ismail"},
|
| 141 |
+
{"name": "هاني الرفاعي", "desc": "تلاوة بكائية", "url": "https://qurango.net/radio/hani_arrifai"},
|
| 142 |
+
{"name": "يحيى حوا", "desc": "تلاوة هادئة", "url": "https://qurango.net/radio/yahya_hawwa"},
|
| 143 |
+
{"name": "خالد القحطاني", "desc": "تلاوة خاشعة", "url": "https://qurango.net/radio/khalid_alqahtani"},
|
| 144 |
+
{"name": "صلاح البدير", "desc": "إمام المسجد النبوي", "url": "https://qurango.net/radio/salah_albudair"},
|
| 145 |
+
{"name": "صلاح بو خاطر", "desc": "تلاوة ندية", "url": "https://qurango.net/radio/salah_bukhatir"},
|
| 146 |
+
{"name": "عبدالرشيد صوفي", "desc": "تلاوة مميزة بروايات", "url": "https://qurango.net/radio/abdulrasheed_soufi"},
|
| 147 |
+
{"name": "عبدالله عواد الجهني", "desc": "إمام الحرم المكي", "url": "https://qurango.net/radio/abdullah_aljuhani"}
|
| 148 |
+
]
|
| 149 |
+
|
| 150 |
+
# --- متغيرات التحكم بالبث ---
|
| 151 |
+
current_reciter_index = 0
|
| 152 |
+
current_volume = 1.0
|
| 153 |
+
|
| 154 |
+
FFMPEG_OPTIONS = {
|
| 155 |
+
'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5',
|
| 156 |
+
'options': '-vn'
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
# =======================================================
|
| 160 |
+
# تجهيز واجهة الأزرار (Control Panel)
|
| 161 |
+
# =======================================================
|
| 162 |
+
class ReciterSelect(Select):
|
| 163 |
+
def __init__(self):
|
| 164 |
+
# القائمة المنسدلة في ديسكورد تدعم 25 خيار كحد أقصى
|
| 165 |
+
options = []
|
| 166 |
+
for i, r in enumerate(RECITERS[:25]):
|
| 167 |
+
options.append(discord.SelectOption(label=r['name'], description=r['desc'][:50], value=str(i)))
|
| 168 |
+
super().__init__(placeholder="اختر القارئ من القائمة السريعة...", min_values=1, max_values=1, options=options, custom_id="select_reciter")
|
| 169 |
+
|
| 170 |
+
async def callback(self, interaction: discord.Interaction):
|
| 171 |
+
role = interaction.guild.get_role(OWNER_ROLE_ID)
|
| 172 |
+
if role not in interaction.user.roles:
|
| 173 |
+
return await interaction.response.send_message("❌ لا تملك صلاحية.", ephemeral=True)
|
| 174 |
+
|
| 175 |
+
global current_reciter_index
|
| 176 |
+
current_reciter_index = int(self.values[0])
|
| 177 |
+
vc = interaction.guild.voice_client
|
| 178 |
+
if vc: await play_current_reciter(vc)
|
| 179 |
+
|
| 180 |
+
await interaction.response.send_message(f"🎙️ تم التغيير إلى: {RECITERS[current_reciter_index]['name']}", ephemeral=True)
|
| 181 |
+
await log_event(interaction.guild, f"🎙️ **{interaction.user.name}** قام بتغيير القارئ إلى: {RECITERS[current_reciter_index]['name']}")
|
| 182 |
+
|
| 183 |
+
class ControlPanelView(View):
|
| 184 |
+
def __init__(self):
|
| 185 |
+
super().__init__(timeout=None)
|
| 186 |
+
self.add_item(ReciterSelect())
|
| 187 |
+
|
| 188 |
+
async def check_owner(self, interaction: discord.Interaction):
|
| 189 |
+
role = interaction.guild.get_role(OWNER_ROLE_ID)
|
| 190 |
+
if role and role in interaction.user.roles:
|
| 191 |
+
return True
|
| 192 |
+
await interaction.response.send_message("❌ عذراً، لا تملك الصلاحية (رتبة المالك المطلوبة).", ephemeral=True)
|
| 193 |
+
return False
|
| 194 |
+
|
| 195 |
+
@discord.ui.button(label="⏸️ / ▶️ إيقاف وتشغيل", style=discord.ButtonStyle.primary, custom_id="btn_toggle")
|
| 196 |
+
async def toggle_btn(self, interaction: discord.Interaction, button: Button):
|
| 197 |
+
if not await self.check_owner(interaction): return
|
| 198 |
+
vc = interaction.guild.voice_client
|
| 199 |
+
if vc:
|
| 200 |
+
if vc.is_paused():
|
| 201 |
+
vc.resume()
|
| 202 |
+
await interaction.response.send_message("▶️ تم استئناف البث.", ephemeral=True)
|
| 203 |
+
await log_event(interaction.guild, f"▶️ **{interaction.user.name}** قام باستئناف البث.")
|
| 204 |
+
elif vc.is_playing():
|
| 205 |
+
vc.pause()
|
| 206 |
+
await interaction.response.send_message("⏸️ تم إيقاف البث مؤقتاً.", ephemeral=True)
|
| 207 |
+
await log_event(interaction.guild, f"⏸️ **{interaction.user.name}** قام بإيقاف البث يدوياً.")
|
| 208 |
+
else:
|
| 209 |
+
await play_current_reciter(vc)
|
| 210 |
+
await interaction.response.send_message("▶️ تم تشغيل البث.", ephemeral=True)
|
| 211 |
+
else:
|
| 212 |
+
await interaction.response.send_message("❌ البوت غير متصل.", ephemeral=True)
|
| 213 |
+
|
| 214 |
+
@discord.ui.button(label="⏭️ التالي", style=discord.ButtonStyle.secondary, custom_id="btn_next")
|
| 215 |
+
async def next_btn(self, interaction: discord.Interaction, button: Button):
|
| 216 |
+
if not await self.check_owner(interaction): return
|
| 217 |
+
global current_reciter_index
|
| 218 |
+
current_reciter_index = (current_reciter_index + 1) % len(RECITERS)
|
| 219 |
+
vc = interaction.guild.voice_client
|
| 220 |
+
if vc: await play_current_reciter(vc)
|
| 221 |
+
await interaction.response.send_message(f"⏭️ تم الانتقال إلى: {RECITERS[current_reciter_index]['name']}", ephemeral=True)
|
| 222 |
+
await log_event(interaction.guild, f"⏭️ **{interaction.user.name}** انتقل للقارئ: {RECITERS[current_reciter_index]['name']}")
|
| 223 |
+
|
| 224 |
+
@discord.ui.button(label="⏮️ السابق", style=discord.ButtonStyle.secondary, custom_id="btn_prev")
|
| 225 |
+
async def prev_btn(self, interaction: discord.Interaction, button: Button):
|
| 226 |
+
if not await self.check_owner(interaction): return
|
| 227 |
+
global current_reciter_index
|
| 228 |
+
current_reciter_index = (current_reciter_index - 1) % len(RECITERS)
|
| 229 |
+
vc = interaction.guild.voice_client
|
| 230 |
+
if vc: await play_current_reciter(vc)
|
| 231 |
+
await interaction.response.send_message(f"⏮️ تم الرجوع إلى: {RECITERS[current_reciter_index]['name']}", ephemeral=True)
|
| 232 |
+
await log_event(interaction.guild, f"⏮️ **{interaction.user.name}** رجع للقارئ: {RECITERS[current_reciter_index]['name']}")
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
class QuranBot(commands.Bot):
|
| 236 |
+
def __init__(self):
|
| 237 |
+
intents = discord.Intents.default()
|
| 238 |
+
intents.message_content = True
|
| 239 |
+
intents.voice_states = True
|
| 240 |
+
intents.guilds = True
|
| 241 |
+
super().__init__(command_prefix="!", intents=intents)
|
| 242 |
+
|
| 243 |
+
async def setup_hook(self):
|
| 244 |
+
# تسجيل لوحة التحكم لتعمل الأزرار حتى بعد إعادة تشغيل البوت
|
| 245 |
+
self.add_view(ControlPanelView())
|
| 246 |
+
await self.tree.sync()
|
| 247 |
+
print("✅ تم مزامنة الأوامر والأزرار بنجاح.")
|
| 248 |
+
|
| 249 |
+
bot = QuranBot()
|
| 250 |
+
|
| 251 |
+
# =======================================================
|
| 252 |
+
# دوال مساعدة والحماية
|
| 253 |
+
# =======================================================
|
| 254 |
+
|
| 255 |
+
# دالة لتسجيل الأحداث (Logs) في القناة المخصصة
|
| 256 |
+
async def log_event(guild: discord.Guild, message: str):
|
| 257 |
+
log_channel = discord.utils.get(guild.text_channels, name="📜-سجلات-البوت")
|
| 258 |
+
time_now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 259 |
+
if log_channel:
|
| 260 |
+
try:
|
| 261 |
+
await log_channel.send(f"`[{time_now}]` {message}")
|
| 262 |
+
except: pass
|
| 263 |
+
print(f"[{time_now}] {message}")
|
| 264 |
+
|
| 265 |
+
# دالة للتحقق من الرتبة وأن الأمر يعمل في غرفة التحكم فقط
|
| 266 |
+
def is_owner_and_in_control():
|
| 267 |
+
async def predicate(interaction: discord.Interaction):
|
| 268 |
+
if not interaction.guild:
|
| 269 |
+
await interaction.response.send_message("❌ هذا الأمر مخصص للاستخدام داخل السيرفر فقط.", ephemeral=True)
|
| 270 |
+
return False
|
| 271 |
+
|
| 272 |
+
if interaction.channel.name != "🛠️ غرفة التحكم (للمالك)":
|
| 273 |
+
await interaction.response.send_message("❌ عذراً، لا يمكنك استخدام الأوامر إلا داخل 🛠️ غرفة التحكم.", ephemeral=True)
|
| 274 |
+
return False
|
| 275 |
+
|
| 276 |
+
role = interaction.guild.get_role(OWNER_ROLE_ID)
|
| 277 |
+
if role and role in interaction.user.roles:
|
| 278 |
+
return True
|
| 279 |
+
|
| 280 |
+
await interaction.response.send_message("❌ عذراً، لا تملك الصلاحية (رتبة المالك المطلوبة).", ephemeral=True)
|
| 281 |
+
return False
|
| 282 |
+
return app_commands.check(predicate)
|
| 283 |
+
|
| 284 |
+
async def play_current_reciter(vc: discord.VoiceClient):
|
| 285 |
+
if vc.is_playing() or vc.is_paused():
|
| 286 |
+
vc.stop()
|
| 287 |
+
reciter = RECITERS[current_reciter_index]
|
| 288 |
+
audio_source = discord.FFmpegPCMAudio(reciter["url"], **FFMPEG_OPTIONS)
|
| 289 |
+
vc.play(discord.PCMVolumeTransformer(audio_source, volume=current_volume))
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
# =======================================================
|
| 293 |
+
# هندسة القنوات والأحداث
|
| 294 |
+
# =======================================================
|
| 295 |
+
|
| 296 |
+
@bot.event
|
| 297 |
+
async def on_ready():
|
| 298 |
+
print(f"🤖 البوت {bot.user} يعمل الآن.")
|
| 299 |
+
for guild in bot.guilds:
|
| 300 |
+
if guild.id != TARGET_GUILD_ID:
|
| 301 |
+
print(f"👢 تم مغادرة سيرفر غريب: {guild.name}")
|
| 302 |
+
await guild.leave()
|
| 303 |
+
|
| 304 |
+
target_guild = bot.get_guild(TARGET_GUILD_ID)
|
| 305 |
+
if target_guild:
|
| 306 |
+
await check_and_create_channels(target_guild)
|
| 307 |
+
await log_event(target_guild, "🚀 **تم تشغيل البوت وهو جاهز للعمل.**")
|
| 308 |
+
|
| 309 |
+
async def check_and_create_channels(guild):
|
| 310 |
+
category_name = "إذاعة القرآن الكريم"
|
| 311 |
+
broadcast_vc_name = "🎧 استماع القرآن (بث عام)"
|
| 312 |
+
control_vc_name = "🛠️ غرفة التحكم (للمالك)"
|
| 313 |
+
log_channel_name = "📜-سجلات-البوت"
|
| 314 |
+
|
| 315 |
+
category = discord.utils.get(guild.categories, name=category_name)
|
| 316 |
+
broadcast_vc = discord.utils.get(guild.voice_channels, name=broadcast_vc_name)
|
| 317 |
+
control_vc = discord.utils.get(guild.voice_channels, name=control_vc_name)
|
| 318 |
+
log_tc = discord.utils.get(guild.text_channels, name=log_channel_name)
|
| 319 |
+
|
| 320 |
+
owner_role = guild.get_role(OWNER_ROLE_ID)
|
| 321 |
+
|
| 322 |
+
if not category:
|
| 323 |
+
category = await guild.create_category(category_name)
|
| 324 |
+
|
| 325 |
+
# 1. إنشاء قناة السجلات (Logs)
|
| 326 |
+
if not log_tc:
|
| 327 |
+
overwrites_log = {
|
| 328 |
+
guild.default_role: discord.PermissionOverwrite(view_channel=False),
|
| 329 |
+
bot.user: discord.PermissionOverwrite(view_channel=True, send_messages=True, read_message_history=True)
|
| 330 |
+
}
|
| 331 |
+
if owner_role: overwrites_log[owner_role] = discord.PermissionOverwrite(view_channel=True, read_message_history=True)
|
| 332 |
+
log_tc = await guild.create_text_channel(log_channel_name, category=category, overwrites=overwrites_log)
|
| 333 |
+
|
| 334 |
+
# 2. إنشاء قناة البث العام (منع مطلق للكتابة والتحدث للعامة)
|
| 335 |
+
if not broadcast_vc:
|
| 336 |
+
overwrites_broadcast = {
|
| 337 |
+
guild.default_role: discord.PermissionOverwrite(
|
| 338 |
+
view_channel=True, connect=True, read_message_history=True,
|
| 339 |
+
speak=False, send_messages=False, add_reactions=False, stream=False
|
| 340 |
+
),
|
| 341 |
+
bot.user: discord.PermissionOverwrite(connect=True, speak=True, send_messages=True, administrator=True)
|
| 342 |
+
}
|
| 343 |
+
broadcast_vc = await guild.create_voice_channel(broadcast_vc_name, category=category, overwrites=overwrites_broadcast)
|
| 344 |
+
|
| 345 |
+
# 3. إنشاء غرفة التحكم للمالك
|
| 346 |
+
if not control_vc:
|
| 347 |
+
overwrites_control = {
|
| 348 |
+
guild.default_role: discord.PermissionOverwrite(view_channel=False, connect=False),
|
| 349 |
+
bot.user: discord.PermissionOverwrite(view_channel=True, connect=True, send_messages=True)
|
| 350 |
+
}
|
| 351 |
+
if owner_role: overwrites_control[owner_role] = discord.PermissionOverwrite(view_channel=True, connect=True, send_messages=True)
|
| 352 |
+
control_vc = await guild.create_voice_channel(control_vc_name, category=category, overwrites=overwrites_control, user_limit=99)
|
| 353 |
+
|
| 354 |
+
# إرسال لوحة التحكم (الرسالة التفاعلية) داخل شات غرفة التحكم
|
| 355 |
+
embed = discord.Embed(
|
| 356 |
+
title="🎛️ لوحة تحكم الإذاعة الذكية",
|
| 357 |
+
description="استخدم الأزرار أدناه للتحكم السريع في البث، أو اختر القارئ من القائمة المنسدلة.\n\n*ملاحظة: يمكنك أيضاً استخدام الأوامر مثل `/volume` هنا فقط.*",
|
| 358 |
+
color=discord.Color.dark_theme()
|
| 359 |
+
)
|
| 360 |
+
embed.set_footer(text="SaaS Pro Dashboard")
|
| 361 |
+
await control_vc.send(embed=embed, view=ControlPanelView())
|
| 362 |
+
|
| 363 |
+
# اتصال البوت التلقائي بقناة البث العام
|
| 364 |
+
if not guild.voice_client:
|
| 365 |
+
vc = await broadcast_vc.connect()
|
| 366 |
+
await play_current_reciter(vc)
|
| 367 |
+
|
| 368 |
+
human_members = sum(1 for m in broadcast_vc.members if not m.bot)
|
| 369 |
+
if human_members == 0:
|
| 370 |
+
vc.pause()
|
| 371 |
+
await log_event(guild, "⏸️ القناة فارغة، تم وضع البث في وضع الاستعداد (توفير الموارد).")
|
| 372 |
+
else:
|
| 373 |
+
await log_event(guild, "▶️ تم البدء بالبث لوجود مستمعين في القناة.")
|
| 374 |
+
|
| 375 |
+
@bot.event
|
| 376 |
+
async def on_voice_state_update(member, before, after):
|
| 377 |
+
if member.bot: return
|
| 378 |
+
guild = member.guild
|
| 379 |
+
if guild.id != TARGET_GUILD_ID: return
|
| 380 |
+
bot_vc = guild.voice_client
|
| 381 |
+
if not bot_vc: return
|
| 382 |
+
|
| 383 |
+
# حالة دخول عضو
|
| 384 |
+
if after.channel and after.channel.id == bot_vc.channel.id:
|
| 385 |
+
if bot_vc.is_paused():
|
| 386 |
+
bot_vc.resume()
|
| 387 |
+
await log_event(guild, f"▶️ استئناف البث التلقائي بسبب دخول **{member.name}**.")
|
| 388 |
+
|
| 389 |
+
embed = discord.Embed(
|
| 390 |
+
title="أهلاً بك في مجلس الذكر 🕊️",
|
| 391 |
+
description=random.choice(REMINDERS),
|
| 392 |
+
color=discord.Color.gold()
|
| 393 |
+
)
|
| 394 |
+
embed.set_footer(text=f"القارئ الحالي: {RECITERS[current_reciter_index]['name']}")
|
| 395 |
+
try:
|
| 396 |
+
await after.channel.send(content=f"مرحباً بك {member.mention}", embed=embed, delete_after=60)
|
| 397 |
+
except: pass
|
| 398 |
+
|
| 399 |
+
# حالة خروج عضو
|
| 400 |
+
elif before.channel and before.channel.id == bot_vc.channel.id:
|
| 401 |
+
human_members = sum(1 for m in before.channel.members if not m.bot)
|
| 402 |
+
if human_members == 0 and bot_vc.is_playing():
|
| 403 |
+
bot_vc.pause()
|
| 404 |
+
await log_event(guild, "💤 توقف البث تلقائياً لخروج جميع المستمعين.")
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
# =======================================================
|
| 408 |
+
# أوامر السلاش (محصورة في غرفة التحكم)
|
| 409 |
+
# =======================================================
|
| 410 |
+
|
| 411 |
+
@bot.tree.command(name="play_radio", description="▶️ تشغيل بث القرآن إجبارياً")
|
| 412 |
+
@is_owner_and_in_control()
|
| 413 |
+
async def play_radio(interaction: discord.Interaction):
|
| 414 |
+
vc = interaction.guild.voice_client
|
| 415 |
+
if not vc: return await interaction.response.send_message("❌ البوت غير متصل.", ephemeral=True)
|
| 416 |
+
if vc.is_paused(): vc.resume()
|
| 417 |
+
else: await play_current_reciter(vc)
|
| 418 |
+
await interaction.response.send_message(f"▶️ تم التشغيل: **{RECITERS[current_reciter_index]['name']}**", ephemeral=True)
|
| 419 |
+
await log_event(interaction.guild, f"▶️ **{interaction.user.name}** شغل البث عبر أمر /play_radio")
|
| 420 |
+
|
| 421 |
+
@bot.tree.command(name="stop_radio", description="⏹️ إيقاف البث مؤقتاً")
|
| 422 |
+
@is_owner_and_in_control()
|
| 423 |
+
async def stop_radio(interaction: discord.Interaction):
|
| 424 |
+
vc = interaction.guild.voice_client
|
| 425 |
+
if vc and vc.is_playing():
|
| 426 |
+
vc.pause()
|
| 427 |
+
await interaction.response.send_message("⏹️ تم الإيقاف.", ephemeral=True)
|
| 428 |
+
await log_event(interaction.guild, f"⏹️ **{interaction.user.name}** أوقف البث عبر أمر /stop_radio")
|
| 429 |
+
else:
|
| 430 |
+
await interaction.response.send_message("⚠️ متوقف بالفعل.", ephemeral=True)
|
| 431 |
+
|
| 432 |
+
@bot.tree.command(name="volume", description="🔊 التحكم بدرجة الصوت (من 1 إلى 100)")
|
| 433 |
+
@app_commands.describe(level="درجة الصوت من 1 إلى 100")
|
| 434 |
+
@is_owner_and_in_control()
|
| 435 |
+
async def set_volume(interaction: discord.Interaction, level: int):
|
| 436 |
+
global current_volume
|
| 437 |
+
if level < 1 or level > 100:
|
| 438 |
+
return await interaction.response.send_message("❌ يرجى إدخال رقم بين 1 و 100.", ephemeral=True)
|
| 439 |
+
|
| 440 |
+
current_volume = level / 100.0
|
| 441 |
+
vc = interaction.guild.voice_client
|
| 442 |
+
if vc and vc.source: vc.source.volume = current_volume
|
| 443 |
+
|
| 444 |
+
await interaction.response.send_message(f"🔊 تم تغيير درجة الصوت إلى: **{level}%**", ephemeral=True)
|
| 445 |
+
await log_event(interaction.guild, f"🔊 **{interaction.user.name}** غيّر درجة الصوت إلى {level}%")
|
| 446 |
+
|
| 447 |
+
# =======================================================
|
| 448 |
+
# التشغيل والـ Keep Alive
|
| 449 |
+
# =======================================================
|
| 450 |
+
try:
|
| 451 |
+
from keep_alive import keep_alive
|
| 452 |
+
keep_alive()
|
| 453 |
+
except Exception: pass
|
| 454 |
+
|
| 455 |
+
if __name__ == "__main__":
|
| 456 |
+
TOKEN = os.environ.get("DISCORD_TOKEN")
|
| 457 |
+
if TOKEN: bot.run(TOKEN)
|
keep_alive.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask import Flask
|
| 2 |
+
from threading import Thread
|
| 3 |
+
|
| 4 |
+
app = Flask(__name__)
|
| 5 |
+
|
| 6 |
+
@app.route('/')
|
| 7 |
+
def home():
|
| 8 |
+
# هذه الرسالة البسيطة تكفي لكي يعرف UptimeRobot أن البوت حي
|
| 9 |
+
return "🟢 البوت يعمل بنجاح! جاهز للبث 24/7"
|
| 10 |
+
|
| 11 |
+
def run():
|
| 12 |
+
# المنفذ 7860 هو المنفذ المعتمد في Hugging Face Spaces
|
| 13 |
+
app.run(host='0.0.0.0', port=7860)
|
| 14 |
+
|
| 15 |
+
def keep_alive():
|
| 16 |
+
t = Thread(target=run)
|
| 17 |
+
t.start()
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
discord.py[voice]
|
| 2 |
+
PyNaCl
|
| 3 |
+
flask
|
| 4 |
+
supabase
|
| 5 |
+
google-generativeai
|